##// END OF EJS Templates
Improvements to component.py....
Brian Granger -
Show More
@@ -23,9 +23,9 b' Authors:'
23 23
24 24 from weakref import WeakValueDictionary
25 25
26 from IPython.utils.ipstruct import Struct
26 27 from IPython.utils.traitlets import (
27 HasTraitlets, TraitletError, MetaHasTraitlets,
28 Int, Float, Str, Bool, Unicode
28 HasTraitlets, TraitletError, MetaHasTraitlets, Instance, This
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 37 class MetaComponentTracker(type):
42 38 """A metaclass that tracks instances of Components and its subclasses."""
43 39
@@ -47,9 +43,9 b' class MetaComponentTracker(type):'
47 43 cls.__numcreated = 0
48 44
49 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 49 the instance is saved in a WeakValueDictionary for tracking.
54 50 """
55 51
@@ -60,32 +56,34 b' class MetaComponentTracker(type):'
60 56 c.__instance_refs[c.__numcreated] = instance
61 57 return instance
62 58
63 def get_instances(cls):
64 """Get all instances of cls and its subclasses."""
65 return cls.__instance_refs.values()
59 def get_instances(cls, name=None, klass=None, root=None):
60 """Get all instances of cls and its subclasses.
66 61
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
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)]
77
78 def get_instances_by_class(cls, thisclass):
79 """Get all instances of cls that are instances of thisclass.
80
81 This exclused instances of thisclass subclasses.
71 instances = cls.__instance_refs.values()
72 if name is not None:
73 instances = [i for i in instances if i.name == name]
74 if klass is not None:
75 instances = [i for i in instances if isinstance(i, klass)]
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
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)]
86 return [i for i in cls.get_instances(name,klass,root) if call(i)]
89 87
90 88
91 89 class ComponentNameGenerator(object):
@@ -117,7 +115,10 b' class Component(HasTraitlets):'
117 115
118 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 123 def __init__(self, parent, name=None, config=None):
123 124 """Create a component given a parent.
@@ -140,11 +141,13 b' class Component(HasTraitlets):'
140 141 to this argument. We might think about changing this behavior.
141 142 """
142 143 super(Component, self).__init__()
144 self._children = []
143 145 if name is None:
144 self._name = ComponentNameGenerator()
146 self.name = ComponentNameGenerator()
145 147 else:
146 self._name = name
147 self.parent = parent # this uses the property and handles None
148 self.name = name
149 self.root = self # This is the default, it is set when parent is set
150 self.parent = parent
148 151 if config is not None:
149 152 self.config = config
150 153 else:
@@ -152,33 +155,35 b' class Component(HasTraitlets):'
152 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):
159 # This should use the ComponentNameGenerator to test for uniqueness
160 self._name = name
161
162 def _get_name(self):
163 return self._name
161 def _parent_changed(self, name, old, new):
162 if old is not None:
163 old._remove_child(self)
164 if new is not None:
165 new._add_child(self)
164 166
165 name = property(_get_name, _set_name)
166
167 def _set_parent(self, parent):
168 if parent is None:
169 self._parent = None
170 self._root = self
167 if new is None:
168 self.root = self
171 169 else:
172 assert isinstance(parent, Component), 'parent must be a component'
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)
170 self.root = new.root
180 171
181 172 @property
182 def root(self):
183 return self._root
184
173 def children(self):
174 """A list of all my child components."""
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 31 from IPython.utils.traitlets import (
32 32 HasTraitlets, MetaHasTraitlets, TraitletType, Any,
33 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 440 b = B()
441 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 481 class TraitletTestBase(TestCase):
444 482 """A best testing class for basic traitlet types."""
445 483
@@ -167,19 +167,24 b' class TraitletType(object):'
167 167 dv = self.default_value
168 168 return dv
169 169
170 def __get__(self, obj, cls=None):
170 def __get__(self, obj, cls=None, skipset=False):
171 171 """Get the value of the traitlet by self.name for the instance.
172
172
173 173 The creation of default values is deferred until this is called the
174 174 first time. This is done so instances of the parent HasTraitlets
175 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 180 if obj is None:
178 181 return self
179 182 else:
180 183 if not obj._traitlet_values.has_key(self.name):
181 184 dv = self.get_default_value()
182 self.__set__(obj, dv, first=True)
185 # Call __set__ with first=True so we don't get a recursion
186 if not skipset:
187 self.__set__(obj, dv, first=True)
183 188 return dv
184 189 else:
185 190 return obj._traitlet_values[self.name]
@@ -187,7 +192,8 b' class TraitletType(object):'
187 192 def __set__(self, obj, value, first=False):
188 193 new_value = self._validate(obj, value)
189 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 197 if old_value != new_value:
192 198 obj._traitlet_values[self.name] = new_value
193 199 obj._notify_traitlet(self.name, old_value, new_value)
@@ -659,6 +665,40 b' class Instance(BaseClassResolver):'
659 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 703 # Basic TraitletTypes implementations/subclasses
664 704 #-----------------------------------------------------------------------------
General Comments 0
You need to be logged in to leave comments. Login now