13 """PyAMS_utils.inherit module |
13 """PyAMS_utils.inherit module |
14 |
14 |
15 This module is used to manage a generic inheritance between a content and |
15 This module is used to manage a generic inheritance between a content and |
16 it's parent container. It also defines a custom InheritedFieldProperty which |
16 it's parent container. It also defines a custom InheritedFieldProperty which |
17 allows to automatically manage inherited properties. |
17 allows to automatically manage inherited properties. |
|
18 |
|
19 This PyAMS module is used to handle inheritance between a parent object and a child which can |
|
20 "inherit" from some of it's properties, as long as they share the same "target" interface. |
|
21 |
|
22 >>> from zope.interface import implementer, Interface, Attribute |
|
23 >>> from zope.schema import TextLine |
|
24 >>> from zope.schema.fieldproperty import FieldProperty |
|
25 |
|
26 >>> from pyams_utils.adapter import adapter_config |
|
27 >>> from pyams_utils.interfaces.inherit import IInheritInfo |
|
28 >>> from pyams_utils.inherit import BaseInheritInfo, InheritedFieldProperty |
|
29 >>> from pyams_utils.registry import get_global_registry |
|
30 |
|
31 Let's start by creating a "content" interface, and a marker interface for objects for which we |
|
32 want to provide this interface: |
|
33 |
|
34 >>> class IMyInfoInterface(IInheritInfo): |
|
35 ... '''Custom interface''' |
|
36 ... value = TextLine(title="Custom attribute") |
|
37 |
|
38 >>> class IMyTargetInterface(Interface): |
|
39 ... '''Target interface''' |
|
40 |
|
41 >>> @implementer(IMyInfoInterface) |
|
42 ... class MyInfo(BaseInheritInfo): |
|
43 ... target_interface = IMyTargetInterface |
|
44 ... adapted_interface = IMyInfoInterface |
|
45 ... |
|
46 ... _value = FieldProperty(IMyInfoInterface['value']) |
|
47 ... value = InheritedFieldProperty(IMyInfoInterface['value']) |
|
48 |
|
49 Please note that for each field of the interface which can be inherited, you must define to |
|
50 properties: one using "InheritedFieldProperty" with the name of the field, and one using a classic |
|
51 "FieldProperty" with the same name prefixed by "_"; this property is used to store the "local" |
|
52 property value, when inheritance is unset. |
|
53 |
|
54 The adapter is created to adapt an object providing IMyTargetInterface to IMyInfoInterface; |
|
55 please note that the adapter *must* attach the created object to it's parent by setting |
|
56 __parent__ attribute: |
|
57 |
|
58 >>> @adapter_config(context=IMyTargetInterface, provides=IMyInfoInterface) |
|
59 ... def my_info_factory(context): |
|
60 ... info = getattr(context, '__info__', None) |
|
61 ... if info is None: |
|
62 ... info = context.__info__ = MyInfo() |
|
63 ... info.__parent__ = context |
|
64 ... return info |
|
65 |
|
66 Adapter registration is here only for testing; the "adapter_config" decorator may do the job in |
|
67 a normal application context: |
|
68 |
|
69 >>> registry = get_global_registry() |
|
70 >>> registry.registerAdapter(my_info_factory, (IMyTargetInterface, ), IMyInfoInterface) |
|
71 |
|
72 We can then create classes which will be adapted to support inheritance: |
|
73 |
|
74 >>> @implementer(IMyTargetInterface) |
|
75 ... class MyTarget: |
|
76 ... '''Target class''' |
|
77 ... __parent__ = None |
|
78 ... __info__ = None |
|
79 |
|
80 >>> parent = MyTarget() |
|
81 >>> parent_info = IMyInfoInterface(parent) |
|
82 >>> parent.__info__ |
|
83 <pyams_utils.tests.test_utils...MyInfo object at ...> |
|
84 >>> parent_info.value = 'parent' |
|
85 >>> parent_info.value |
|
86 'parent' |
|
87 >>> parent_info.can_inherit |
|
88 False |
|
89 |
|
90 As soon as a parent is defined, the child object can inherit from it's parent: |
|
91 |
|
92 >>> child = MyTarget() |
|
93 >>> child.__parent__ = parent |
|
94 >>> child_info = IMyInfoInterface(child) |
|
95 >>> child.__info__ |
|
96 <pyams_utils.tests.test_utils...MyInfo object at ...> |
|
97 |
|
98 >>> child_info.can_inherit |
|
99 True |
|
100 >>> child_info.inherit |
|
101 True |
|
102 >>> child_info.value |
|
103 'parent' |
|
104 |
|
105 Setting child value while inheritance is enabled donesn't have any effect: |
|
106 |
|
107 >>> child_info.value = 'child' |
|
108 >>> child_info.value |
|
109 'parent' |
|
110 >>> child_info.inherit_from == parent |
|
111 True |
|
112 |
|
113 You can disable inheritance and define your own value: |
|
114 |
|
115 >>> child_info.inherit = False |
|
116 >>> child_info.value = 'child' |
|
117 >>> child_info.value |
|
118 'child' |
|
119 >>> child_info.inherit_from == child |
|
120 True |
|
121 |
|
122 Please note that parent and child in this example share the same class, but this is not a |
|
123 requirement; they just have to implement the same marker interface, to be adapted to the same |
|
124 content interface. |
18 """ |
125 """ |
19 |
|
20 __docformat__ = 'restructuredtext' |
|
21 |
126 |
22 from zope.interface import Interface, implementer |
127 from zope.interface import Interface, implementer |
23 from zope.location import Location |
128 from zope.location import Location |
24 from zope.schema.fieldproperty import FieldProperty |
129 from zope.schema.fieldproperty import FieldProperty |
25 |
130 |
26 from pyams_utils.interfaces.inherit import IInheritInfo |
131 from pyams_utils.interfaces.inherit import IInheritInfo |
27 from pyams_utils.traversing import get_parent |
132 from pyams_utils.traversing import get_parent |
28 from pyams_utils.zodb import volatile_property |
133 from pyams_utils.zodb import volatile_property |
29 |
134 |
30 |
135 |
|
136 __docformat__ = 'restructuredtext' |
|
137 |
|
138 |
31 @implementer(IInheritInfo) |
139 @implementer(IInheritInfo) |
32 class BaseInheritInfo(Location): |
140 class BaseInheritInfo(Location): |
33 """Base inherit class""" |
141 """Base inherit class |
|
142 |
|
143 Subclasses may generaly override target_interface and adapted_interface to |
|
144 correctly handle inheritance (see example in doctests). |
|
145 Please note also that adapters to this interface must correctly 'locate' |
|
146 """ |
34 |
147 |
35 target_interface = Interface |
148 target_interface = Interface |
36 adapted_interface = Interface |
149 adapted_interface = Interface |
37 |
150 |
38 _inherit = FieldProperty(IInheritInfo['inherit']) |
151 _inherit = FieldProperty(IInheritInfo['inherit']) |
92 |
205 |
93 def __get__(self, inst, klass): |
206 def __get__(self, inst, klass): |
94 if inst is None: |
207 if inst is None: |
95 return self |
208 return self |
96 inherit_info = IInheritInfo(inst) |
209 inherit_info = IInheritInfo(inst) |
97 if inherit_info.inherit: |
210 if inherit_info.inherit and (inherit_info.parent is not None): |
|
211 # pylint: disable=not-callable |
98 return getattr(inherit_info.adapted_interface(inherit_info.parent), self.__name) |
212 return getattr(inherit_info.adapted_interface(inherit_info.parent), self.__name) |
99 else: |
213 return getattr(inst, '_{0}'.format(self.__name)) |
100 return getattr(inst, '_{0}'.format(self.__name)) |
|
101 |
214 |
102 def __set__(self, inst, value): |
215 def __set__(self, inst, value): |
103 inherit_info = IInheritInfo(inst) |
216 inherit_info = IInheritInfo(inst) |
104 if not (inherit_info.can_inherit and inherit_info.inherit): |
217 if not (inherit_info.can_inherit and inherit_info.inherit): |
105 setattr(inst, '_{0}'.format(self.__name), value) |
218 setattr(inst, '_{0}'.format(self.__name), value) |