##// END OF EJS Templates
Improvements to component.py....
Brian Granger -
Show More
@@ -23,9 +23,9 b' Authors:'
23
23
24 from weakref import WeakValueDictionary
24 from weakref import WeakValueDictionary
25
25
26 from IPython.utils.ipstruct import Struct
26 from IPython.utils.traitlets import (
27 from IPython.utils.traitlets import (
27 HasTraitlets, TraitletError, MetaHasTraitlets,
28 HasTraitlets, TraitletError, MetaHasTraitlets, Instance, This
28 Int, Float, Str, Bool, Unicode
29 )
29 )
30
30
31
31
@@ -34,10 +34,6 b' from IPython.utils.traitlets import ('
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36
36
37 class Config(object):
38 pass
39
40
41 class MetaComponentTracker(type):
37 class MetaComponentTracker(type):
42 """A metaclass that tracks instances of Components and its subclasses."""
38 """A metaclass that tracks instances of Components and its subclasses."""
43
39
@@ -47,9 +43,9 b' class MetaComponentTracker(type):'
47 cls.__numcreated = 0
43 cls.__numcreated = 0
48
44
49 def __call__(cls, *args, **kw):
45 def __call__(cls, *args, **kw):
50 """Called when class is called (instantiated)!!!
46 """Called when *class* is called (instantiated)!!!
51
47
52 Then a Component or subclass is instantiated, this is called and
48 When a Component or subclass is instantiated, this is called and
53 the instance is saved in a WeakValueDictionary for tracking.
49 the instance is saved in a WeakValueDictionary for tracking.
54 """
50 """
55
51
@@ -60,32 +56,34 b' class MetaComponentTracker(type):'
60 c.__instance_refs[c.__numcreated] = instance
56 c.__instance_refs[c.__numcreated] = instance
61 return instance
57 return instance
62
58
63 def get_instances(cls):
59 def get_instances(cls, name=None, klass=None, root=None):
64 """Get all instances of cls and its subclasses."""
60 """Get all instances of cls and its subclasses.
65 return cls.__instance_refs.values()
66
67 def get_instances_by_name(cls, name):
68 """Get all instances of cls and its subclasses by name."""
69 return [i for i in cls.get_instances() if i.name == name]
70
71 def get_instances_by_subclass(cls, thisclass):
72 """Get all instances of cls that are instances of thisclass.
73
61
74 This includes all instances of subclasses of thisclass.
62 Parameters
63 ----------
64 name : str
65 Limit to components with this name.
66 klass : class
67 Limit to components having isinstance(component, klass)
68 root : Component or subclass
69 Limit to components having this root.
75 """
70 """
76 return [i for i in cls.get_instances() if isinstance(i, thisclass)]
71 instances = cls.__instance_refs.values()
77
72 if name is not None:
78 def get_instances_by_class(cls, thisclass):
73 instances = [i for i in instances if i.name == name]
79 """Get all instances of cls that are instances of thisclass.
74 if klass is not None:
80
75 instances = [i for i in instances if isinstance(i, klass)]
81 This exclused instances of thisclass subclasses.
76 if root is not None:
77 instances = [i for i in instances if i.root == root]
78 return instances
79
80 def get_instances_by_condition(cls, call, name=None, klass=None, root=None):
81 """Get all instances of cls, i such that call(i)==True.
82
83 This also takes the ``name``, ``klass`` and ``root`` arguments of
84 :meth:`get_instance`
82 """
85 """
83
86 return [i for i in cls.get_instances(name,klass,root) if call(i)]
84 return [i for i in cls.get_instances() if type(i) is thisclass]
85
86 def get_instances_by_condition(cls, call):
87 """Get all instances of cls, i such that call(i)==True."""
88 return [i for i in cls.get_instances() if call(i)]
89
87
90
88
91 class ComponentNameGenerator(object):
89 class ComponentNameGenerator(object):
@@ -117,7 +115,10 b' class Component(HasTraitlets):'
117
115
118 __metaclass__ = MetaComponent
116 __metaclass__ = MetaComponent
119
117
120 config = Config()
118 # Traitlets are fun!
119 config = Instance(Struct)
120 parent = This(allow_none=True)
121 root = This(allow_none=True)
121
122
122 def __init__(self, parent, name=None, config=None):
123 def __init__(self, parent, name=None, config=None):
123 """Create a component given a parent.
124 """Create a component given a parent.
@@ -140,11 +141,13 b' class Component(HasTraitlets):'
140 to this argument. We might think about changing this behavior.
141 to this argument. We might think about changing this behavior.
141 """
142 """
142 super(Component, self).__init__()
143 super(Component, self).__init__()
144 self._children = []
143 if name is None:
145 if name is None:
144 self._name = ComponentNameGenerator()
146 self.name = ComponentNameGenerator()
145 else:
147 else:
146 self._name = name
148 self.name = name
147 self.parent = parent # this uses the property and handles None
149 self.root = self # This is the default, it is set when parent is set
150 self.parent = parent
148 if config is not None:
151 if config is not None:
149 self.config = config
152 self.config = config
150 else:
153 else:
@@ -152,33 +155,35 b' class Component(HasTraitlets):'
152 self.config = self.parent.config
155 self.config = self.parent.config
153
156
154 #-------------------------------------------------------------------------
157 #-------------------------------------------------------------------------
155 # Properties
158 # Static traitlet notifiations
156 #-------------------------------------------------------------------------
159 #-------------------------------------------------------------------------
157
160
158 def _set_name(self, name):
161 def _parent_changed(self, name, old, new):
159 # This should use the ComponentNameGenerator to test for uniqueness
162 if old is not None:
160 self._name = name
163 old._remove_child(self)
161
164 if new is not None:
162 def _get_name(self):
165 new._add_child(self)
163 return self._name
164
166
165 name = property(_get_name, _set_name)
167 if new is None:
166
168 self.root = self
167 def _set_parent(self, parent):
168 if parent is None:
169 self._parent = None
170 self._root = self
171 else:
169 else:
172 assert isinstance(parent, Component), 'parent must be a component'
170 self.root = new.root
173 self._parent = parent
174 self._root = parent.root
175
176 def _get_parent(self):
177 return self._parent
178
179 parent = property(_get_parent, _set_parent)
180
171
181 @property
172 @property
182 def root(self):
173 def children(self):
183 return self._root
174 """A list of all my child components."""
184
175 return self._children
176
177 def _remove_child(self, child):
178 """A private method for removing children componenets."""
179 if child in self._children:
180 index = self._children.index(child)
181 del self._children[index]
182
183 def _add_child(self, child):
184 """A private method for adding children componenets."""
185 if child not in self._children:
186 self._children.append(child)
187
188 def __repr__(self):
189 return "<Component('%s')>" % self.name No newline at end of file
@@ -31,7 +31,7 b' from unittest import TestCase'
31 from IPython.utils.traitlets import (
31 from IPython.utils.traitlets import (
32 HasTraitlets, MetaHasTraitlets, TraitletType, Any,
32 HasTraitlets, MetaHasTraitlets, TraitletType, Any,
33 Int, Long, Float, Complex, Str, Unicode, Bool, TraitletError,
33 Int, Long, Float, Complex, Str, Unicode, Bool, TraitletError,
34 Undefined, Type, Instance
34 Undefined, Type, Instance, This
35 )
35 )
36
36
37
37
@@ -440,6 +440,44 b' class TestInstance(TestCase):'
440 b = B()
440 b = B()
441 self.assertRaises(TraitletError, getattr, b, 'inst')
441 self.assertRaises(TraitletError, getattr, b, 'inst')
442
442
443
444 class TestThis(TestCase):
445
446 def test_this_class(self):
447 class Foo(HasTraitlets):
448 this = This
449
450 f = Foo()
451 self.assertEquals(f.this, None)
452 g = Foo()
453 f.this = g
454 self.assertEquals(f.this, g)
455 self.assertRaises(TraitletError, setattr, f, 'this', 10)
456
457 def test_this_inst(self):
458 class Foo(HasTraitlets):
459 this = This()
460
461 f = Foo()
462 f.this = Foo()
463 self.assert_(isinstance(f.this, Foo))
464
465 def test_allow_none(self):
466 class Foo(HasTraitlets):
467 this = This(allow_none=False)
468
469 f = Foo()
470 g = Foo()
471 f.this = g
472 self.assertEquals(f.this, g)
473
474 f = Foo()
475 self.assertRaises(TraitletError, getattr, f, 'this')
476
477 f = Foo()
478 self.assertRaises(TraitletError, setattr, f, 'this', None)
479
480
443 class TraitletTestBase(TestCase):
481 class TraitletTestBase(TestCase):
444 """A best testing class for basic traitlet types."""
482 """A best testing class for basic traitlet types."""
445
483
@@ -167,18 +167,23 b' class TraitletType(object):'
167 dv = self.default_value
167 dv = self.default_value
168 return dv
168 return dv
169
169
170 def __get__(self, obj, cls=None):
170 def __get__(self, obj, cls=None, skipset=False):
171 """Get the value of the traitlet by self.name for the instance.
171 """Get the value of the traitlet by self.name for the instance.
172
172
173 The creation of default values is deferred until this is called the
173 The creation of default values is deferred until this is called the
174 first time. This is done so instances of the parent HasTraitlets
174 first time. This is done so instances of the parent HasTraitlets
175 will have their own default value instances.
175 will have their own default value instances.
176
177 A default value is not validated until it is requested. Thus, if
178 you use an invalid default value, but never request it, you are fine.
176 """
179 """
177 if obj is None:
180 if obj is None:
178 return self
181 return self
179 else:
182 else:
180 if not obj._traitlet_values.has_key(self.name):
183 if not obj._traitlet_values.has_key(self.name):
181 dv = self.get_default_value()
184 dv = self.get_default_value()
185 # Call __set__ with first=True so we don't get a recursion
186 if not skipset:
182 self.__set__(obj, dv, first=True)
187 self.__set__(obj, dv, first=True)
183 return dv
188 return dv
184 else:
189 else:
@@ -187,7 +192,8 b' class TraitletType(object):'
187 def __set__(self, obj, value, first=False):
192 def __set__(self, obj, value, first=False):
188 new_value = self._validate(obj, value)
193 new_value = self._validate(obj, value)
189 if not first:
194 if not first:
190 old_value = self.__get__(obj)
195 # Call __get__ with skipset=True so we don't get a recursion
196 old_value = self.__get__(obj, skipset=True)
191 if old_value != new_value:
197 if old_value != new_value:
192 obj._traitlet_values[self.name] = new_value
198 obj._traitlet_values[self.name] = new_value
193 obj._notify_traitlet(self.name, old_value, new_value)
199 obj._notify_traitlet(self.name, old_value, new_value)
@@ -659,6 +665,40 b' class Instance(BaseClassResolver):'
659 return dv
665 return dv
660
666
661
667
668 class This(TraitletType):
669 """A traitlet for instances of the class containing this trait."""
670
671 info_text = 'an instance of the same type as the receiver'
672
673 def __init__(self, default_value=None, allow_none=True, **metadata):
674 if default_value is not None:
675 raise TraitletError("The default value of 'This' can only be None.")
676 super(This, self).__init__(default_value, **metadata)
677 self._allow_none = allow_none
678 if allow_none:
679 self.info_text = self.info_text + ' or None'
680
681 def validate(self, obj, value):
682 if value is None:
683 if self._allow_none:
684 return value
685 self.validate_failed(obj, value)
686
687 if isinstance(value, obj.__class__):
688 return value
689 else:
690 self.validate_failed(obj, value)
691
692 def validate_failed (self, obj, value):
693 kind = type(value)
694 if kind is InstanceType:
695 msg = 'class %s' % value.__class__.__name__
696 else:
697 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
698
699 self.error(obj, msg)
700
701
662 #-----------------------------------------------------------------------------
702 #-----------------------------------------------------------------------------
663 # Basic TraitletTypes implementations/subclasses
703 # Basic TraitletTypes implementations/subclasses
664 #-----------------------------------------------------------------------------
704 #-----------------------------------------------------------------------------
General Comments 0
You need to be logged in to leave comments. Login now