Merge lp:~flacoste/lazr.lifecycle/devel into lp:~launchpad-pqm/lazr.lifecycle/trunk
- devel
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~flacoste/lazr.lifecycle/devel |
Merge into: | lp:~launchpad-pqm/lazr.lifecycle/trunk |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~flacoste/lazr.lifecycle/devel |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gary Poster | Approve | ||
Gary Arel | Pending | ||
Review via email: mp+4207@code.launchpad.net |
Commit message
Description of the change
Francis J. Lacoste (flacoste) wrote : | # |
Gary Poster (gary) wrote : | # |
merge-conditional
Looks good. I have a few changes for readability, below. Other than that, ship it. :-)
Thanks
Gary
...
> === modified file 'src/lazr/
> --- src/lazr/
> +++ src/lazr/
> @@ -15,8 +15,14 @@
> along with lazr.lifecycle. If not, see <http://
>
> LAZR lifecycle
> -***********
> -
> -This is a pure template for new lazr namespace packages. Whenever you want to
> -make a new lazr subpackage, just branch this code and change all occurrences
> -of 'lifecycle' with whatever your subpackage's name is.
> +**************
> +
> +This package defines three "lifecycle" events that notifies about object
...that notify...
> +creation, modification and deletion. The events include information about the
> +user responsible for the changes.
> +
> +The modification event also includes information about the state of the object
> +before the changes.
> +
> +The module also contains Snapshot support (to save the state of an object for
> +notification) and to compute deltas between version of objects.
Remove parens, add a comma:
The module also contains Snapshot support to save the state of an object for
notification, and to compute deltas between version of objects.
>
> === modified file 'src/lazr/
> --- src/lazr/
> +++ src/lazr/
> @@ -117,50 +117,53 @@
> >>> IBar.providedBy
> True
>
> -Snapshots of Iterables
> -------
> -
> -If your some fields is actually an iterable (but not a string), they will be
> -automatically converted to lists when attaching them to
> -the Snapshot object. The example below uses a property that returns
> -a generator for example.
> -
> - >>> class IterableFoo(Foo):
> - ... implements(IFoo)
> +
> +ISnapshotValue
> +------
> +
> +For some fields, assigning the existing value to the snapshot object
> +isn't appropriate. For these case, one can provide a factory registered
> +as an adapter for the value to ISnapshotValueF
> +adaptation lookup will be stored in the snapshot attribute.
> +
> + >>> from zope.interface import implementer, Interface
> + >>> from zope.component import adapter, getSiteManager
> +
> + >>> class IIterable(
> + ... """Marker for a value that needs special snapshot need."""
"...that needs a special snapshot"?
> +
> + >>> class EvenOrOddIterable:
> + ... """An object that will be snapshotted specially."""
> + ... implements(
> ...
> ... even = True
> ... max = 10
> ...
> - ... @property
> - ... def remotes(self):
> + ... def __iter__(self):
> ... for i in range(self.max):
> ... if i % 2 == 0 and self.even:
> - ... yield 1
> + ... yield i
> ... elif i % 2 == 1 and not self.even:
> - ... yield 1
> + ... ...
- 34. By Francis J. Lacoste
-
Make example clearer.
- 35. By Francis J. Lacoste
-
Updates based on Gary's review.
Preview Diff
1 | === modified file 'README.txt' |
2 | --- README.txt 2009-03-02 21:29:43 +0000 |
3 | +++ README.txt 2009-03-05 16:35:08 +0000 |
4 | @@ -1,8 +1,6 @@ |
5 | -This is a template for your lazr package. To start your own lazr package, |
6 | -branch this code and change 'lifecycle' to your package's actual name in all |
7 | -files. |
8 | - |
9 | - |
10 | +Richer lifecycle events API. |
11 | + |
12 | +.. |
13 | This file is part of lazr.lifecycle. |
14 | |
15 | lazr.lifecycle is free software: you can redistribute it and/or modify it |
16 | |
17 | === modified file 'src/lazr/lifecycle/README.txt' |
18 | --- src/lazr/lifecycle/README.txt 2009-03-02 21:29:43 +0000 |
19 | +++ src/lazr/lifecycle/README.txt 2009-03-05 16:35:08 +0000 |
20 | @@ -15,8 +15,14 @@ |
21 | along with lazr.lifecycle. If not, see <http://www.gnu.org/licenses/>. |
22 | |
23 | LAZR lifecycle |
24 | -*********** |
25 | - |
26 | -This is a pure template for new lazr namespace packages. Whenever you want to |
27 | -make a new lazr subpackage, just branch this code and change all occurrences |
28 | -of 'lifecycle' with whatever your subpackage's name is. |
29 | +************** |
30 | + |
31 | +This package defines three "lifecycle" events that notifies about object |
32 | +creation, modification and deletion. The events include information about the |
33 | +user responsible for the changes. |
34 | + |
35 | +The modification event also includes information about the state of the object |
36 | +before the changes. |
37 | + |
38 | +The module also contains Snapshot support (to save the state of an object for |
39 | +notification) and to compute deltas between version of objects. |
40 | |
41 | === modified file 'src/lazr/lifecycle/docs/snapshot.txt' |
42 | --- src/lazr/lifecycle/docs/snapshot.txt 2009-03-03 19:34:49 +0000 |
43 | +++ src/lazr/lifecycle/docs/snapshot.txt 2009-03-05 15:19:18 +0000 |
44 | @@ -117,50 +117,53 @@ |
45 | >>> IBar.providedBy(snapshot) |
46 | True |
47 | |
48 | -Snapshots of Iterables |
49 | ----------------------- |
50 | - |
51 | -If your some fields is actually an iterable (but not a string), they will be |
52 | -automatically converted to lists when attaching them to |
53 | -the Snapshot object. The example below uses a property that returns |
54 | -a generator for example. |
55 | - |
56 | - >>> class IterableFoo(Foo): |
57 | - ... implements(IFoo) |
58 | + |
59 | +ISnapshotValueFactory |
60 | +--------------------- |
61 | + |
62 | +For some fields, assigning the existing value to the snapshot object |
63 | +isn't appropriate. For these case, one can provide a factory registered |
64 | +as an adapter for the value to ISnapshotValueFactory. The result of the |
65 | +adaptation lookup will be stored in the snapshot attribute. |
66 | + |
67 | + >>> from zope.interface import implementer, Interface |
68 | + >>> from zope.component import adapter, getSiteManager |
69 | + |
70 | + >>> class IIterable(Interface): |
71 | + ... """Marker for a value that needs special snapshot need.""" |
72 | + |
73 | + >>> class EvenOrOddIterable: |
74 | + ... """An object that will be snapshotted specially.""" |
75 | + ... implements(IIterable) |
76 | ... |
77 | ... even = True |
78 | ... max = 10 |
79 | ... |
80 | - ... @property |
81 | - ... def remotes(self): |
82 | + ... def __iter__(self): |
83 | ... for i in range(self.max): |
84 | ... if i % 2 == 0 and self.even: |
85 | - ... yield 1 |
86 | + ... yield i |
87 | ... elif i % 2 == 1 and not self.even: |
88 | - ... yield 1 |
89 | + ... yield i |
90 | ... else: |
91 | ... continue |
92 | - >>> even_generator = IterableFoo() |
93 | + |
94 | + >>> from lazr.lifecycle.interfaces import ISnapshotValueFactory |
95 | + >>> @implementer(ISnapshotValueFactory) |
96 | + ... @adapter(IIterable) |
97 | + ... def snapshot_iterable(value): |
98 | + ... return list(value) |
99 | + >>> getSiteManager().registerAdapter(snapshot_iterable) |
100 | + |
101 | + >>> even_generator = Foo() |
102 | >>> even_generator.title = 'Even' |
103 | >>> even_generator.description = 'Generates even number below 10.' |
104 | + >>> even_generator.remotes = EvenOrOddIterable() |
105 | |
106 | >>> snapshot = Snapshot(even_generator, providing=IFoo) |
107 | >>> snapshot.remotes == list(even_generator.remotes) |
108 | True |
109 | |
110 | -shortlist is used under the hood to convert this, so a warning will be issued |
111 | -if the iterables has more than 100 items and an error will be raised if there |
112 | -are more than 1000. |
113 | - |
114 | - >>> even_generator.max = 202 |
115 | - >>> snapshot = Snapshot(even_generator, providing=IFoo) |
116 | - UserWarning: ... |
117 | - |
118 | - |
119 | - >>> even_generator.max = 2002 |
120 | - >>> snapshot = Snapshot(even_generator, providing=IFoo) |
121 | - Traceback (most recent call last): |
122 | - ... |
123 | - ShortListTooBigError: ... |
124 | - |
125 | + >>> getSiteManager().unregisterAdapter(snapshot_iterable) |
126 | + True |
127 | |
128 | |
129 | === modified file 'src/lazr/lifecycle/interfaces.py' |
130 | --- src/lazr/lifecycle/interfaces.py 2009-03-03 20:38:02 +0000 |
131 | +++ src/lazr/lifecycle/interfaces.py 2009-03-05 15:19:18 +0000 |
132 | @@ -21,7 +21,8 @@ |
133 | __all__ = [ |
134 | 'IObjectCreatedEvent', |
135 | 'IObjectDeletedEvent', |
136 | - 'IObjectModifiedEvent' |
137 | + 'IObjectModifiedEvent', |
138 | + 'ISnapshotValueFactory', |
139 | ] |
140 | |
141 | import zope.lifecycleevent.interfaces as z3lifecycle |
142 | @@ -49,3 +50,13 @@ |
143 | "changed.") |
144 | user = Attribute("The user who modified the object.") |
145 | |
146 | + |
147 | +class ISnapshotValueFactory(Interface): |
148 | + """This is a marker interface used to obtain snapshot of values. |
149 | + |
150 | + The interface isn't meant to be provided, but is only used as a factory |
151 | + lookup. The snapshot value is what should be returned from the adapter |
152 | + lookup. |
153 | + """ |
154 | + |
155 | + |
156 | |
157 | === removed file 'src/lazr/lifecycle/shortlist.py' |
158 | --- src/lazr/lifecycle/shortlist.py 2009-03-03 23:12:10 +0000 |
159 | +++ src/lazr/lifecycle/shortlist.py 1970-01-01 00:00:00 +0000 |
160 | @@ -1,89 +0,0 @@ |
161 | -# Copyright 2009 Canonical Ltd. All rights reserved. |
162 | -# |
163 | -# This file is part of lazr.lifecycle |
164 | -# |
165 | -# lazr.lifecycle is free software: you can redistribute it and/or modify it |
166 | -# under the terms of the GNU Lesser General Public License as published by |
167 | -# the Free Software Foundation, either version 3 of the License, or (at your |
168 | -# option) any later version. |
169 | -# |
170 | -# lazr.lifecycle is distributed in the hope that it will be useful, but WITHOUT |
171 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
172 | -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
173 | -# License for more details. |
174 | -# |
175 | -# You should have received a copy of the GNU Lesser General Public License |
176 | -# along with lazr.lifecycle. If not, see <http://www.gnu.org/licenses/>. |
177 | - |
178 | -"""A function to listify iterables but with a maximum size.""" |
179 | - |
180 | -# This should probably live in its own package as it is generally useful. |
181 | -# But since none of the other lazr libraries requires it, it's fine here for |
182 | -# the moment. |
183 | - |
184 | -__metaclass__ = type |
185 | - |
186 | -__all__ = [ |
187 | - 'shortlist', |
188 | - 'ShortListTooBigError' |
189 | - ] |
190 | - |
191 | -import warnings |
192 | - |
193 | - |
194 | -class ShortListTooBigError(Exception): |
195 | - """This error is raised when the shortlist hardlimit is reached""" |
196 | - |
197 | - |
198 | -def shortlist(sequence, longest_expected=15, hardlimit=None): |
199 | - """Return a listified version of sequence. |
200 | - |
201 | - If <sequence> has more than <longest_expected> items, a warning is issued. |
202 | - |
203 | - >>> shortlist([1, 2]) |
204 | - [1, 2] |
205 | - |
206 | - >>> shortlist([1, 2, 3], 2) |
207 | - UserWarning: shortlist() should not be used here. It's meant to listify sequences with no more than 2 items. There were 3 items. |
208 | - [1, 2, 3] |
209 | - |
210 | - >>> shortlist([1, 2, 3, 4], hardlimit=2) |
211 | - Traceback (most recent call last): |
212 | - ... |
213 | - ShortListTooBigError: Hard limit of 2 exceeded. |
214 | - |
215 | - >>> shortlist([1, 2, 3, 4], 2, hardlimit=4) |
216 | - UserWarning: shortlist() should not be used here. It's meant to listify sequences with no more than 2 items. There were 4 items. |
217 | - [1, 2, 3, 4] |
218 | - |
219 | - It works on iterable also. |
220 | - |
221 | - >>> shortlist(range(10), 5, hardlimit=8) #doctest: +ELLIPSIS |
222 | - Traceback (most recent call last): |
223 | - ... |
224 | - ShortListTooBigError: ... |
225 | - |
226 | - """ |
227 | - if hardlimit is not None: |
228 | - last = hardlimit + 1 |
229 | - else: |
230 | - last = longest_expected + 1 |
231 | - if getattr(sequence, '__getitem__', False): |
232 | - results = list(sequence[:last]) |
233 | - else: |
234 | - results = [] |
235 | - for idx, item in enumerate(sequence): |
236 | - if hardlimit and idx > hardlimit: |
237 | - break |
238 | - results.append(item) |
239 | - |
240 | - size = len(results) |
241 | - if hardlimit and size > hardlimit: |
242 | - raise ShortListTooBigError( |
243 | - 'Hard limit of %d exceeded.' % hardlimit) |
244 | - elif size > longest_expected: |
245 | - warnings.warn( |
246 | - "shortlist() should not be used here. It's meant to listify" |
247 | - " sequences with no more than %d items. There were %s items." |
248 | - % (longest_expected, size), stacklevel=2) |
249 | - return results |
250 | |
251 | === modified file 'src/lazr/lifecycle/snapshot.py' |
252 | --- src/lazr/lifecycle/snapshot.py 2009-03-03 20:38:02 +0000 |
253 | +++ src/lazr/lifecycle/snapshot.py 2009-03-05 15:19:18 +0000 |
254 | @@ -26,13 +26,13 @@ |
255 | 'Snapshot', |
256 | ] |
257 | |
258 | +from zope.component import queryAdapter |
259 | from zope.interface.interfaces import IInterface |
260 | from zope.interface import directlyProvides |
261 | from zope.schema.interfaces import IField |
262 | from zope.security.proxy import removeSecurityProxy |
263 | |
264 | -from lazr.lifecycle.shortlist import shortlist |
265 | - |
266 | +from lazr.lifecycle.interfaces import ISnapshotValueFactory |
267 | |
268 | _marker = object() |
269 | |
270 | @@ -48,6 +48,9 @@ |
271 | will also provide the interfaces listed in providing. If no names |
272 | are supplied but an interface is provided, all Fields of that |
273 | interface will be included in the snapshot. |
274 | + |
275 | + The attributes are copied by passing them through a ISnapshotValueFactory. |
276 | + The default implementation of that adapter just returns the value itself. |
277 | """ |
278 | def __init__(self, ob, names=None, providing=None): |
279 | ob = removeSecurityProxy(ob) |
280 | @@ -72,15 +75,11 @@ |
281 | if value is _marker: |
282 | raise AssertionError("Attribute %s not in object %r" |
283 | % (name, ob)) |
284 | - def should_listify(value): |
285 | - # We listify using shortlist any iterable that isn't one of |
286 | - # the basic types. |
287 | - return ( |
288 | - not isinstance(value, (str, unicode, tuple, list)) |
289 | - and getattr(value, '__iter__', False)) |
290 | - if should_listify(value): |
291 | - value = shortlist(value, longest_expected=100, hardlimit=1000) |
292 | - setattr(self, name, value) |
293 | + snapshot_value = queryAdapter( |
294 | + value, ISnapshotValueFactory, default=_marker) |
295 | + if snapshot_value is _marker: |
296 | + snapshot_value = value |
297 | + setattr(self, name, snapshot_value) |
298 | |
299 | if providing is not None: |
300 | directlyProvides(self, providing) |
301 | |
302 | === modified file 'src/lazr/lifecycle/tests/test_documentation.py' |
303 | --- src/lazr/lifecycle/tests/test_documentation.py 2009-03-03 19:29:52 +0000 |
304 | +++ src/lazr/lifecycle/tests/test_documentation.py 2009-03-05 16:05:12 +0000 |
305 | @@ -12,8 +12,6 @@ |
306 | import unittest |
307 | import warnings |
308 | |
309 | -import lazr.lifecycle.shortlist |
310 | - |
311 | DOCTEST_FLAGS = ( |
312 | doctest.ELLIPSIS | |
313 | doctest.NORMALIZE_WHITESPACE | |
314 | @@ -50,6 +48,4 @@ |
315 | optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS |
316 | ) |
317 | return unittest.TestSuite(( |
318 | - doctest.DocFileSuite(*doctest_files, **options), |
319 | - doctest.DocTestSuite( |
320 | - lazr.lifecycle.shortlist, setUp=setUp, tearDown=tearDown))) |
321 | + doctest.DocFileSuite(*doctest_files, **options))) |
Hi Gary,
Here is the initial version of lazr.lifecycle. It brings ObjectDelta, Snapshot and the SQLObject events which have been renamed to the more generic names.