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 |
|
|
|
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() | |
|
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. | |
|
59 | def get_instances(cls, name=None, klass=None, root=None): | |
|
60 | """Get all instances of cls and its subclasses. | |
|
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)] | |
|
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. |
|
|
146 | self.name = ComponentNameGenerator() | |
|
145 | 147 | else: |
|
146 |
self. |
|
|
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 _ |
|
|
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 |
|
|
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,18 +167,23 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() |
|
185 | # Call __set__ with first=True so we don't get a recursion | |
|
186 | if not skipset: | |
|
182 | 187 | self.__set__(obj, dv, first=True) |
|
183 | 188 | return dv |
|
184 | 189 | else: |
@@ -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