This repository has been archived by the owner on Feb 17, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
README.txt
249 lines (182 loc) · 7.88 KB
/
README.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
================
The Configurator
================
The configurator is designed to extend a component after its
creation. Traditionally this is done by listening to ``ObjectCreatedEvent``
events. However, this low-level method does not suffice, since configuration
often depends on other configuration steps and additional data is often needed
to complete the configuration. And this is where the configurator comes
in. It uses a separate plugin mechanism to implement the mentioned high-level
functionality.
Before we can demonstrate the configuration mechanism, we'll have to create an
interface and a component on which the configuration can act upon:
>>> import zope.interface
>>> class ISomething(zope.interface.Interface):
... """Some interesting interface."""
>>> class Something(object):
... """Implementation of something."""
... zope.interface.implements(ISomething)
>>> something = Something()
Now we can have the configuration act on the component:
>>> from z3c import configurator
>>> configurator.configure(something, {})
The second argument is the data dictionary, which can be used to pass in
additional information that might be needed during the configuration. It is up
to each plugin to interpret the data.
Of course nothing happens, since no configuration plugins are
registered. Let's now create a new configuration plugin, which sets a new
attribute on the component:
>>> import zope.component
>>> from z3c.configurator import interfaces
>>> class AddFooAttribute(configurator.ConfigurationPluginBase):
... zope.component.adapts(ISomething)
...
... def __call__(self, data):
... setattr(self.context, 'foo', data.get('foo'))
>>> zope.component.provideAdapter(AddFooAttribute, name='add foo')
If we execute the configuration again, the attribute will be added:
>>> configurator.configure(something, {'foo': u'my value'})
>>> something.foo
u'my value'
Dependencies
------------
Now that we have simple configuration plugins, we can also develop plugins
that depend on another one. Let's create a configuration plugin that adds some
additional data to the foo attribute. Clearly, the foo attribute has to exist
before this step can be taken. The ``dependencies`` attribute can be used to
specify all plugin dependencies by name:
>>> class ExtendFooAttribute(configurator.ConfigurationPluginBase):
... zope.component.adapts(ISomething)
... dependencies = ('add foo',)
...
... def __call__(self, data):
... self.context.foo = u'Text: ' + self.context.foo
>>> zope.component.provideAdapter(ExtendFooAttribute, name='extend foo')
If we now execute the configuration again, the extended result should be seen:
>>> something = Something()
>>> configurator.configure(something, {'foo': u'my value'})
>>> something.foo
u'Text: my value'
Data Schemas
------------
For purely informational purposes, a ``schema`` attribute is used on the
plugin to describe the fields that the plugin expects from the data
dictionary. For adding another simple attribute, this could look as follows:
>>> import zope.schema
>>> class IAddBar(zope.interface.Interface):
... bar = zope.schema.Text(title=u'Bar')
>>> class AddBarAttribute(configurator.SchemaConfigurationPluginBase):
... zope.component.adapts(ISomething)
... schema = IAddBar
...
... def __call__(self, data):
... self.verify(data)
... setattr(self.context, 'bar', data.get('bar'))
>>> zope.component.provideAdapter(AddBarAttribute, name='add bar')
The advantage of using the base class for this case is that it provides a
``verify()`` method that allows you to verify the data against the shema. We
can now run the configuration again:
>>> something = Something()
>>> configurator.configure(something, {'foo': u'my value', 'bar': u'value'})
>>> something.bar
u'value'
The value must exist and be valid:
>>> something = Something()
>>> configurator.configure(something, {'foo': u'my value'})
Traceback (most recent call last):
...
RequiredMissing
>>> something = Something()
>>> configurator.configure(something, {'foo': u'my value', 'bar': 1})
Traceback (most recent call last):
...
WrongType: (1, <type 'unicode'>)
Data Namespaces
---------------
In order to not confuse attribute names if two plugins share a common
name it is possible to pass data as a dictionary of dictionaries. The
keys of the dictionary is the name under which the plugins are
registered.
>>> something = Something()
>>> data = {u'add foo': {'foo': u'foo value'},
... u'add bar': {'bar': u'bar value'}}
>>> configurator.configure(something, data, useNameSpaces=True)
>>> something.foo, something.bar
(u'Text: foo value', u'bar value')
Named Configuration
-------------------
Sometimes we do not want all registered configuration plugins to be
executed. This can be achieved by providing the names argument to the
configure function.
Let us create a new something:
>>> something = Something()
If we now configure it without names we get both attributes set.
>>> configurator.configure(something, {'foo': u'my value', 'bar': u'asdf'})
>>> something.__dict__
{'foo': u'Text: my value', 'bar': u'asdf'}
Now let us just configure the plugin 'add bar'.
>>> something = Something()
>>> configurator.configure(something, {'foo': u'my value', 'bar': u'asdf'},
... names=['add bar'])
>>> something.__dict__
{'bar': u'asdf'}
Dependencies of plugins are always executed - they don't have to be
added to the ```names``` argument.
>>> something = Something()
>>> configurator.configure(something, {'foo': u'my value'},
... names=['extend foo'])
>>> something.foo
u'Text: my value'
Named configurations are usefull when called manually through the web
(see browser/README.txt). The configurator package does not look if a
configuration is already applied if called twice. It is the
responsibility of the plugin to be aware that it doesn't do things
twice or delete things.
Wrong Implementations
---------------------
A configurator must provide a __call__ method.
>>> class CallNotImplemented(configurator.ConfigurationPluginBase):
... zope.component.adapts(ISomething)
>>> zope.component.provideAdapter(CallNotImplemented, name='no call')
>>> configurator.configure(something, None, names=['no call'])
Traceback (most recent call last):
...
NotImplementedError
The same must happen for a schema base configurator.
>>> class SchemaCallNotImplemented(configurator.SchemaConfigurationPluginBase):
... zope.component.adapts(ISomething)
>>> zope.component.provideAdapter(SchemaCallNotImplemented, name='schema no call')
>>> configurator.configure(something, None, names=['schema no call'])
Traceback (most recent call last):
...
NotImplementedError
No Recursion
------------
It's possible to define recursive dependencies without to run into recursion
errors. Let's define a new plugin free object:
>>> class IFoo(zope.interface.Interface):
... """Just a foo interface."""
>>> class Foo(object):
... """Implementation of foo."""
... zope.interface.implements(IFoo)
Let's define another plugin named `first` which depends on a plugin named
`second`.
>>> class FirstPlugin(configurator.ConfigurationPluginBase):
... zope.component.adapts(IFoo)
... dependencies = ('second',)
...
... def __call__(self, data):
... print 'FirstPlugin called'
>>> zope.component.provideAdapter(FirstPlugin, name='first')
And define a plugin named `second` which depends on `first`:
>>> class SecondPlugin(configurator.ConfigurationPluginBase):
... zope.component.adapts(IFoo)
... dependencies = ('first',)
...
... def __call__(self, data):
... print 'SecondPlugin called'
>>> zope.component.provideAdapter(SecondPlugin, name='second')
>>> foo = Foo()
>>> configurator.configure(foo, {})
FirstPlugin called
SecondPlugin called