component.py
229 lines
| 7.6 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
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
|
r2184 | from copy import deepcopy | ||
Brian Granger
|
r2205 | import datetime | ||
Brian Granger
|
r2157 | from weakref import WeakValueDictionary | ||
Brian Granger
|
r2180 | from IPython.utils.ipstruct import Struct | ||
Brian Granger
|
r2157 | from IPython.utils.traitlets import ( | ||
Brian Granger
|
r2180 | HasTraitlets, TraitletError, MetaHasTraitlets, Instance, This | ||
Brian Granger
|
r2157 | ) | ||
#----------------------------------------------------------------------------- | ||||
# Helper classes for Components | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r2183 | class ComponentError(Exception): | ||
pass | ||||
Brian Granger
|
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
|
r2180 | """Called when *class* is called (instantiated)!!! | ||
Brian Granger
|
r2157 | |||
Brian Granger
|
r2180 | When a Component or subclass is instantiated, this is called and | ||
Brian Granger
|
r2157 | 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 | ||||
Brian Granger
|
r2180 | def get_instances(cls, name=None, klass=None, root=None): | ||
"""Get all instances of cls and its subclasses. | ||||
Brian Granger
|
r2157 | |||
Brian Granger
|
r2180 | Parameters | ||
---------- | ||||
name : str | ||||
Limit to components with this name. | ||||
klass : class | ||||
Limit to components having isinstance(component, klass) | ||||
root : Component or subclass | ||||
Limit to components having this root. | ||||
Brian Granger
|
r2157 | """ | ||
Brian Granger
|
r2180 | instances = cls.__instance_refs.values() | ||
if name is not None: | ||||
instances = [i for i in instances if i.name == name] | ||||
if klass is not None: | ||||
instances = [i for i in instances if isinstance(i, klass)] | ||||
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, klass=None, root=None): | ||||
"""Get all instances of cls, i such that call(i)==True. | ||||
This also takes the ``name``, ``klass`` and ``root`` arguments of | ||||
:meth:`get_instance` | ||||
Brian Granger
|
r2157 | """ | ||
Brian Granger
|
r2180 | return [i for i in cls.get_instances(name,klass,root) if call(i)] | ||
Brian Granger
|
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
|
r2180 | # Traitlets are fun! | ||
Brian Granger
|
r2183 | config = Instance(Struct,(),{}) | ||
parent = This() | ||||
root = This() | ||||
Brian Granger
|
r2205 | created = None | ||
Brian Granger
|
r2157 | |||
def __init__(self, parent, name=None, config=None): | ||||
Brian Granger
|
r2183 | """Create a component given a parent and possibly and name and config. | ||
Brian Granger
|
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
|
r2184 | config : Struct | ||
Brian Granger
|
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
|
r2157 | """ | ||
super(Component, self).__init__() | ||||
Brian Granger
|
r2180 | self._children = [] | ||
Brian Granger
|
r2157 | if name is None: | ||
Brian Granger
|
r2180 | self.name = ComponentNameGenerator() | ||
Brian Granger
|
r2157 | else: | ||
Brian Granger
|
r2180 | self.name = name | ||
self.root = self # This is the default, it is set when parent is set | ||||
self.parent = parent | ||||
Brian Granger
|
r2157 | if config is not None: | ||
Brian Granger
|
r2184 | self.config = deepcopy(config) | ||
Brian Granger
|
r2157 | else: | ||
if self.parent is not None: | ||||
Brian Granger
|
r2184 | self.config = deepcopy(self.parent.config) | ||
Brian Granger
|
r2157 | |||
Brian Granger
|
r2205 | self.created = datetime.datetime.now() | ||
Brian Granger
|
r2157 | #------------------------------------------------------------------------- | ||
Brian Granger
|
r2180 | # Static traitlet notifiations | ||
Brian Granger
|
r2157 | #------------------------------------------------------------------------- | ||
Brian Granger
|
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
|
r2157 | |||
Brian Granger
|
r2180 | if new is None: | ||
self.root = self | ||||
Brian Granger
|
r2157 | else: | ||
Brian Granger
|
r2180 | self.root = new.root | ||
Brian Granger
|
r2157 | |||
Brian Granger
|
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
|
r2184 | |||
def _config_changed(self, name, old, new): | ||||
# Get all traitlets with a config_key metadata entry | ||||
Brian Granger
|
r2200 | traitlets = self.traitlets('config_key') | ||
Brian Granger
|
r2184 | for k, v in traitlets.items(): | ||
try: | ||||
config_value = new[v.get_metadata('config_key')] | ||||
except KeyError: | ||||
pass | ||||
else: | ||||
setattr(self, k, config_value) | ||||
Brian Granger
|
r2157 | @property | ||
Brian Granger
|
r2180 | def children(self): | ||
"""A list of all my child components.""" | ||||
return self._children | ||||
def _remove_child(self, child): | ||||
"""A private method for removing children componenets.""" | ||||
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 componenets.""" | ||||
if child not in self._children: | ||||
self._children.append(child) | ||||
def __repr__(self): | ||||
Brian Granger
|
r2181 | return "<Component('%s')>" % self.name | ||