##// END OF EJS Templates
Minor doc updates.
Minor doc updates.

File last commit:

r2222:582b3278
r2222:582b3278
Show More
component.py
229 lines | 7.6 KiB | text/x-python | PythonLexer
#!/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
#-----------------------------------------------------------------------------
from copy import deepcopy
from weakref import WeakValueDictionary
from IPython.utils.ipstruct import Struct
from IPython.utils.traitlets import (
HasTraitlets, TraitletError, MetaHasTraitlets, Instance, This
)
#-----------------------------------------------------------------------------
# Helper classes for Components
#-----------------------------------------------------------------------------
class ComponentError(Exception):
pass
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):
"""Called when *class* is called (instantiated)!!!
When a Component or subclass is instantiated, this is called and
the instance is saved in a WeakValueDictionary for tracking.
"""
instance = super(MetaComponentTracker, cls).__call__(*args, **kw)
for c in cls.__mro__:
if issubclass(cls, c) and issubclass(c, Component):
c.__numcreated += 1
c.__instance_refs[c.__numcreated] = instance
return instance
def get_instances(cls, name=None, root=None):
"""Get all instances of cls and its subclasses.
Parameters
----------
name : str
Limit to components with this name.
root : Component or subclass
Limit to components having this root.
"""
instances = cls.__instance_refs.values()
if name is not None:
instances = [i for i in instances if i.name == name]
if root is not None:
instances = [i for i in instances if i.root == root]
return instances
def get_instances_by_condition(cls, call, name=None, root=None):
"""Get all instances of cls, i such that call(i)==True.
This also takes the ``name`` and ``root`` arguments of
:meth:`get_instance`
"""
return [i for i in cls.get_instances(name, root) if call(i)]
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
# Traitlets are fun!
config = Instance(Struct,(),{})
parent = This()
root = This()
def __init__(self, parent, name=None, config=None):
"""Create a component given a parent and possibly and name and config.
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.
config : Struct
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.
"""
super(Component, self).__init__()
self._children = []
if name is None:
self.name = ComponentNameGenerator()
else:
self.name = name
self.root = self # This is the default, it is set when parent is set
self.parent = parent
if config is not None:
self.config = deepcopy(config)
else:
if self.parent is not None:
self.config = deepcopy(self.parent.config)
#-------------------------------------------------------------------------
# Static traitlet notifiations
#-------------------------------------------------------------------------
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)
if new is None:
self.root = self
else:
self.root = new.root
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")
def _config_changed(self, name, old, new):
"""Update all the class traits having a config_key with the config.
For any class traitlet with a ``config_key`` metadata attribute, we
update the traitlet with the value of the corresponding config entry.
In the future, we might want to do a pop here so stale config info
is not passed onto children.
"""
# Get all traitlets with a config_key metadata entry
traitlets = self.traitlets('config_key')
for k, v in traitlets.items():
try:
config_value = new[v.get_metadata('config_key')]
except KeyError:
pass
else:
setattr(self, k, config_value)
@property
def children(self):
"""A list of all my child components."""
return self._children
def _remove_child(self, child):
"""A private method for removing children components."""
if child in self._children:
index = self._children.index(child)
del self._children[index]
def _add_child(self, child):
"""A private method for adding children components."""
if child not in self._children:
self._children.append(child)
def __repr__(self):
return "<Component('%s')>" % self.name