##// END OF EJS Templates
Improve test suite robustness by cleaning up stale processes when possible.
Improve test suite robustness by cleaning up stale processes when possible.

File last commit:

r2337:ab18149a
r2399:7e73e5f7
Show More
component.py
346 lines | 13.3 KiB | text/x-python | PythonLexer
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 #!/usr/bin/env python
# encoding: utf-8
"""
A lightweight component system for IPython.
Authors:
* Brian Granger
* Fernando Perez
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2008-2009 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
Brian Granger
Improvement to how config is handled in Components....
r2184 from copy import deepcopy
Brian Granger
Continuing a massive refactor of everything.
r2205 import datetime
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 from weakref import WeakValueDictionary
Brian Granger
A number of changes to how traitlets and components work....
r2229 from IPython.utils.importstring import import_item
Brian Granger
Massive refactoring of of the core....
r2245 from IPython.config.loader import Config
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 from IPython.utils.traitlets import (
Brian Granger
Improvements to component.py....
r2180 HasTraitlets, TraitletError, MetaHasTraitlets, Instance, This
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 )
#-----------------------------------------------------------------------------
# Helper classes for Components
#-----------------------------------------------------------------------------
Brian Granger
Fixing subtle bug in the traitlets with This....
r2183 class ComponentError(Exception):
pass
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 class MetaComponentTracker(type):
"""A metaclass that tracks instances of Components and its subclasses."""
def __init__(cls, name, bases, d):
super(MetaComponentTracker, cls).__init__(name, bases, d)
cls.__instance_refs = WeakValueDictionary()
cls.__numcreated = 0
def __call__(cls, *args, **kw):
Brian Granger
More work on componentizing everything....
r2243 """Called when a class is called (instantiated)!!!
Brian Granger
First prototype of component, traitlets and a config loader.
r2157
Brian Granger
Improvements to component.py....
r2180 When a Component or subclass is instantiated, this is called and
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 the instance is saved in a WeakValueDictionary for tracking.
"""
Brian Granger
Created context manager for the things injected into __builtin__.
r2227 instance = cls.__new__(cls, *args, **kw)
Brian Granger
More work on componentizing everything....
r2243
# Register the instance before __init__ is called so get_instances
# works inside __init__ methods!
indices = cls.register_instance(instance)
# This is in a try/except because of the __init__ method fails, the
# instance is discarded and shouldn't be tracked.
try:
if isinstance(instance, cls):
cls.__init__(instance, *args, **kw)
except:
# Unregister the instance because __init__ failed!
cls.unregister_instances(indices)
raise
else:
return instance
def register_instance(cls, instance):
"""Register instance with cls and its subclasses."""
# indices is a list of the keys used to register the instance
# with. This list is needed if the instance needs to be unregistered.
indices = []
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 for c in cls.__mro__:
if issubclass(cls, c) and issubclass(c, Component):
c.__numcreated += 1
Brian Granger
More work on componentizing everything....
r2243 indices.append(c.__numcreated)
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 c.__instance_refs[c.__numcreated] = instance
Brian Granger
More work on componentizing everything....
r2243 else:
break
return indices
def unregister_instances(cls, indices):
"""Unregister instance with cls and its subclasses."""
for c, index in zip(cls.__mro__, indices):
try:
del c.__instance_refs[index]
except KeyError:
pass
Brian Granger
Created context manager for the things injected into __builtin__.
r2227
Brian Granger
More work on componentizing everything....
r2243 def clear_instances(cls):
"""Clear all instances tracked by cls."""
cls.__instance_refs.clear()
cls.__numcreated = 0
Brian Granger
First prototype of component, traitlets and a config loader.
r2157
Brian Granger
A number of changes to how traitlets and components work....
r2229 def get_instances(cls, name=None, root=None, klass=None):
Brian Granger
Improvements to component.py....
r2180 """Get all instances of cls and its subclasses.
Brian Granger
First prototype of component, traitlets and a config loader.
r2157
Brian Granger
Improvements to component.py....
r2180 Parameters
----------
name : str
Limit to components with this name.
root : Component or subclass
Limit to components having this root.
Brian Granger
A number of changes to how traitlets and components work....
r2229 klass : class or str
Limits to instances of the class or its subclasses. If a str
is given ut must be in the form 'foo.bar.MyClass'. The str
form of this argument is useful for forward declarations.
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 """
Brian Granger
A number of changes to how traitlets and components work....
r2229 if klass is not None:
if isinstance(klass, basestring):
klass = import_item(klass)
Brian Granger
More work on componentizing everything....
r2243 # Limit search to instances of klass for performance
if issubclass(klass, Component):
return klass.get_instances(name=name, root=root)
Brian Granger
Improvements to component.py....
r2180 instances = cls.__instance_refs.values()
if name is not None:
instances = [i for i in instances if i.name == name]
Brian Granger
A number of changes to how traitlets and components work....
r2229 if klass is not None:
instances = [i for i in instances if isinstance(i, klass)]
Brian Granger
Improvements to component.py....
r2180 if root is not None:
instances = [i for i in instances if i.root == root]
return instances
Brian Granger
Created context manager for the things injected into __builtin__.
r2227 def get_instances_by_condition(cls, call, name=None, root=None,
Brian Granger
A number of changes to how traitlets and components work....
r2229 klass=None):
Brian Granger
Improvements to component.py....
r2180 """Get all instances of cls, i such that call(i)==True.
Brian Granger
Created context manager for the things injected into __builtin__.
r2227 This also takes the ``name`` and ``root`` and ``classname``
arguments of :meth:`get_instance`
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 """
Brian Granger
A number of changes to how traitlets and components work....
r2229 return [i for i in cls.get_instances(name, root, klass) if call(i)]
Brian Granger
First prototype of component, traitlets and a config loader.
r2157
Brian Granger
More work on componentizing everything....
r2243 def masquerade_as(instance, cls):
"""Let instance masquerade as an instance of cls.
Sometimes, such as in testing code, it is useful to let a class
masquerade as another. Python, being duck typed, allows this by
default. But, instances of components are tracked by their class type.
Brian Granger
Major work on the documentation....
r2277 After calling this, ``cls.get_instances()`` will return ``instance``. This
does not, however, cause ``isinstance(instance, cls)`` to return ``True``.
Brian Granger
More work on componentizing everything....
r2243
Parameters
----------
instance : an instance of a Component or Component subclass
The instance that will pretend to be a cls.
cls : subclass of Component
The Component subclass that instance will pretend to be.
"""
cls.register_instance(instance)
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 class ComponentNameGenerator(object):
"""A Singleton to generate unique component names."""
def __init__(self, prefix):
self.prefix = prefix
self.i = 0
def __call__(self):
count = self.i
self.i += 1
return "%s%s" % (self.prefix, count)
ComponentNameGenerator = ComponentNameGenerator('ipython.component')
class MetaComponent(MetaHasTraitlets, MetaComponentTracker):
pass
#-----------------------------------------------------------------------------
# Component implementation
#-----------------------------------------------------------------------------
class Component(HasTraitlets):
__metaclass__ = MetaComponent
Brian Granger
Improvements to component.py....
r2180 # Traitlets are fun!
Brian Granger
Massive refactoring of of the core....
r2245 config = Instance(Config,(),{})
Brian Granger
Fixing subtle bug in the traitlets with This....
r2183 parent = This()
root = This()
Brian Granger
Continuing a massive refactor of everything.
r2205 created = None
Brian Granger
First prototype of component, traitlets and a config loader.
r2157
def __init__(self, parent, name=None, config=None):
Brian Granger
Fixing subtle bug in the traitlets with This....
r2183 """Create a component given a parent and possibly and name and config.
Brian Granger
First prototype of component, traitlets and a config loader.
r2157
Parameters
----------
parent : Component subclass
The parent in the component graph. The parent is used
to get the root of the component graph.
name : str
The unique name of the component. If empty, then a unique
one will be autogenerated.
Brian Granger
Massive refactoring of of the core....
r2245 config : Config
Brian Granger
Fixing subtle bug in the traitlets with This....
r2183 If this is empty, self.config = parent.config, otherwise
self.config = config and root.config is ignored. This argument
should only be used to *override* the automatic inheritance of
parent.config. If a caller wants to modify parent.config
(not override), the caller should make a copy and change
attributes and then pass the copy to this argument.
Notes
-----
Subclasses of Component must call the :meth:`__init__` method of
:class:`Component` *before* doing anything else and using
:func:`super`::
class MyComponent(Component):
def __init__(self, parent, name=None, config=None):
super(MyComponent, self).__init__(parent, name, config)
# Then any other code you need to finish initialization.
This ensures that the :attr:`parent`, :attr:`name` and :attr:`config`
attributes are handled properly.
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 """
super(Component, self).__init__()
Brian Granger
Improvements to component.py....
r2180 self._children = []
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 if name is None:
Brian Granger
Improvements to component.py....
r2180 self.name = ComponentNameGenerator()
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 else:
Brian Granger
Improvements to component.py....
r2180 self.name = name
self.root = self # This is the default, it is set when parent is set
Brian Granger
Massive refactoring of of the core....
r2245 self.parent = parent
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 if config is not None:
Brian Granger
Changed Component.__init__ so that config is not deepcopy'd....
r2274 self.config = config
# We used to deepcopy, but for now we are trying to just save
# by reference. This *could* have side effects as all components
bgranger
Work on examples and fixed a super subtle bug in Component....
r2337 # will share config. In fact, I did find such a side effect in
# _config_changed below. If a config attribute value was a mutable type
# all instances of a component were getting the same copy, effectively
# making that a class attribute.
Brian Granger
Changed Component.__init__ so that config is not deepcopy'd....
r2274 # self.config = deepcopy(config)
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 else:
if self.parent is not None:
Brian Granger
Changed Component.__init__ so that config is not deepcopy'd....
r2274 self.config = self.parent.config
# We used to deepcopy, but for now we are trying to just save
# by reference. This *could* have side effects as all components
bgranger
Work on examples and fixed a super subtle bug in Component....
r2337 # will share config. In fact, I did find such a side effect in
# _config_changed below. If a config attribute value was a mutable type
# all instances of a component were getting the same copy, effectively
# making that a class attribute.
Brian Granger
Changed Component.__init__ so that config is not deepcopy'd....
r2274 # self.config = deepcopy(self.parent.config)
Brian Granger
First prototype of component, traitlets and a config loader.
r2157
Brian Granger
Continuing a massive refactor of everything.
r2205 self.created = datetime.datetime.now()
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 #-------------------------------------------------------------------------
Brian Granger
Improvements to component.py....
r2180 # Static traitlet notifiations
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 #-------------------------------------------------------------------------
Brian Granger
Improvements to component.py....
r2180 def _parent_changed(self, name, old, new):
if old is not None:
old._remove_child(self)
if new is not None:
new._add_child(self)
Brian Granger
First prototype of component, traitlets and a config loader.
r2157
Brian Granger
Improvements to component.py....
r2180 if new is None:
self.root = self
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 else:
Brian Granger
Improvements to component.py....
r2180 self.root = new.root
Brian Granger
First prototype of component, traitlets and a config loader.
r2157
Brian Granger
Fixing subtle bug in the traitlets with This....
r2183 def _root_changed(self, name, old, new):
if self.parent is None:
if not (new is self):
raise ComponentError("Root not self, but parent is None.")
else:
if not self.parent.root is new:
raise ComponentError("Error in setting the root attribute: "
"root != parent.root")
Brian Granger
Improvement to how config is handled in Components....
r2184
def _config_changed(self, name, old, new):
Brian Granger
Components now auto-load the configs of parent classes as well.
r2259 """Update all the class traits having ``config=True`` as metadata.
Brian Granger
Minor doc updates.
r2222
Brian Granger
Components now auto-load the configs of parent classes as well.
r2259 For any class traitlet with a ``config`` metadata attribute that is
``True``, we update the traitlet with the value of the corresponding
config entry.
Brian Granger
Minor doc updates.
r2222 """
Brian Granger
Massive refactoring of of the core....
r2245 # Get all traitlets with a config metadata entry that is True
traitlets = self.traitlets(config=True)
Brian Granger
Components now auto-load the configs of parent classes as well.
r2259 # We auto-load config section for this class as well as any parent
# classes that are Component subclasses. This starts with Component
# and works down the mro loading the config for each section.
section_names = [cls.__name__ for cls in \
reversed(self.__class__.__mro__) if
issubclass(cls, Component) and issubclass(self.__class__, cls)]
for sname in section_names:
# Don't do a blind getattr as that would cause the config to
# dynamically create the section with name self.__class__.__name__.
if new._has_section(sname):
my_config = new[sname]
for k, v in traitlets.items():
Brian Granger
Finished refactoring ipcontroller to be a proper application....
r2297 # Don't allow traitlets with config=True to start with
# uppercase. Otherwise, they are confused with Config
# subsections. But, developers shouldn't have uppercase
# attributes anyways! (PEP 6)
if k[0].upper()==k[0] and not k.startswith('_'):
raise ComponentError('Component traitlets with '
'config=True must start with a lowercase so they are '
'not confused with Config subsections: %s.%s' % \
(self.__class__.__name__, k))
Brian Granger
Components now auto-load the configs of parent classes as well.
r2259 try:
Brian Granger
Finished refactoring ipcontroller to be a proper application....
r2297 # Here we grab the value from the config
# If k has the naming convention of a config
# section, it will be auto created.
Brian Granger
Components now auto-load the configs of parent classes as well.
r2259 config_value = my_config[k]
except KeyError:
pass
else:
# print "Setting %s.%s from %s.%s=%r" % \
# (self.__class__.__name__,k,sname,k,config_value)
bgranger
Work on examples and fixed a super subtle bug in Component....
r2337 # We have to do a deepcopy here if we don't deepcopy the entire
# config object. If we don't, a mutable config_value will be
# shared by all instances, effectively making it a class attribute.
setattr(self, k, deepcopy(config_value))
Brian Granger
Improvement to how config is handled in Components....
r2184
Brian Granger
First prototype of component, traitlets and a config loader.
r2157 @property
Brian Granger
Improvements to component.py....
r2180 def children(self):
"""A list of all my child components."""
return self._children
def _remove_child(self, child):
Brian Granger
Fixing minor typo.
r2219 """A private method for removing children components."""
Brian Granger
Improvements to component.py....
r2180 if child in self._children:
index = self._children.index(child)
del self._children[index]
def _add_child(self, child):
Brian Granger
Fixing minor typo.
r2219 """A private method for adding children components."""
Brian Granger
Improvements to component.py....
r2180 if child not in self._children:
self._children.append(child)
def __repr__(self):
Brian Granger
More work on refactoring things into components....
r2244 return "<%s('%s')>" % (self.__class__.__name__, self.name)