Merge lp:~leonardr/lazr.restful/benji-updated-size-link into lp:lazr.restful
- benji-updated-size-link
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Graham Binns |
Approved revision: | 160 |
Merge reported by: | Leonard Richardson |
Merged at revision: | not available |
Proposed branch: | lp:~leonardr/lazr.restful/benji-updated-size-link |
Merge into: | lp:lazr.restful |
Diff against target: |
1233 lines (+451/-244) 21 files modified
setup.py (+1/-1) src/lazr/restful/NEWS.txt (+9/-0) src/lazr/restful/_operation.py (+28/-2) src/lazr/restful/_resource.py (+48/-18) src/lazr/restful/declarations.py (+19/-12) src/lazr/restful/docs/webservice-declarations.txt (+62/-15) src/lazr/restful/docs/webservice.txt (+1/-0) src/lazr/restful/example/base/root.py (+1/-0) src/lazr/restful/example/base/tests/collection.txt (+17/-8) src/lazr/restful/example/base/tests/wadl.txt (+6/-5) src/lazr/restful/example/multiversion/tests/wadl.txt (+29/-0) src/lazr/restful/interfaces/_rest.py (+7/-0) src/lazr/restful/simple.py (+2/-1) src/lazr/restful/tales.py (+7/-1) src/lazr/restful/templates/wadl-root.pt (+31/-8) src/lazr/restful/testing/webservice.py (+3/-0) src/lazr/restful/tests/test_utils.py (+23/-1) src/lazr/restful/tests/test_webservice.py (+78/-2) src/lazr/restful/utils.py (+12/-0) src/lazr/restful/version.txt (+1/-1) versions.cfg (+66/-169) |
To merge this branch: | bzr merge lp:~leonardr/lazr.restful/benji-updated-size-link |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Graham Binns (community) | code | Approve | |
Review via email: mp+32187@code.launchpad.net |
Commit message
Description of the change
This branch makes lazr.restful send a 'total_size_link' in lieu of a 'total_size', to spare the underlying data model the pain of calculating the total size of a collection all the time.
The branch has already been reviewed (https:/
Summary of changes:
1. Rather than passing total_size_only as an argument into batch(), create a new method get_total_size and call it when the total size is desired, instead of calling batch().
2. When generating the WADL, use a new TALES method to determine whether or not total_size_link is active, rather than putting that boolean in the ZPT namespace. I had to rename namespace[
3. Removed a no-longer-necessary named operation and field from webservice-
Preview Diff
1 | === modified file 'setup.py' | |||
2 | --- setup.py 2010-03-04 14:27:58 +0000 | |||
3 | +++ setup.py 2010-08-10 12:24:46 +0000 | |||
4 | @@ -57,7 +57,7 @@ | |||
5 | 57 | 'docutils', | 57 | 'docutils', |
6 | 58 | 'epydoc', # used by wadl generation | 58 | 'epydoc', # used by wadl generation |
7 | 59 | 'grokcore.component==1.6', | 59 | 'grokcore.component==1.6', |
9 | 60 | 'lazr.batchnavigator', | 60 | 'lazr.batchnavigator>=1.2.0-dev', |
10 | 61 | 'lazr.delegates', | 61 | 'lazr.delegates', |
11 | 62 | 'lazr.enum', | 62 | 'lazr.enum', |
12 | 63 | 'lazr.lifecycle', | 63 | 'lazr.lifecycle', |
13 | 64 | 64 | ||
14 | === modified file 'src/lazr/restful/NEWS.txt' | |||
15 | --- src/lazr/restful/NEWS.txt 2010-08-05 14:01:44 +0000 | |||
16 | +++ src/lazr/restful/NEWS.txt 2010-08-10 12:24:46 +0000 | |||
17 | @@ -2,6 +2,15 @@ | |||
18 | 2 | NEWS for lazr.restful | 2 | NEWS for lazr.restful |
19 | 3 | ===================== | 3 | ===================== |
20 | 4 | 4 | ||
21 | 5 | 0.11.0 (unreleased) | ||
22 | 6 | =================== | ||
23 | 7 | |||
24 | 8 | Added an optimization to total_size so that it is fetched via a link when | ||
25 | 9 | possible. The new configuration option first_version_with_total_size_link | ||
26 | 10 | specifies what version should be the first to expose the behavior. The default | ||
27 | 11 | is for it to be enabled for all versions so set this option to preserve the | ||
28 | 12 | earlier behavior for previously released web services. | ||
29 | 13 | |||
30 | 5 | 0.10.0 (2010-08-05) | 14 | 0.10.0 (2010-08-05) |
31 | 6 | =================== | 15 | =================== |
32 | 7 | 16 | ||
33 | 8 | 17 | ||
34 | === modified file 'src/lazr/restful/_operation.py' | |||
35 | --- src/lazr/restful/_operation.py 2010-04-20 18:26:49 +0000 | |||
36 | +++ src/lazr/restful/_operation.py 2010-08-10 12:24:46 +0000 | |||
37 | @@ -44,6 +44,23 @@ | |||
38 | 44 | def __init__(self, context, request): | 44 | def __init__(self, context, request): |
39 | 45 | self.context = context | 45 | self.context = context |
40 | 46 | self.request = request | 46 | self.request = request |
41 | 47 | self.total_size_only = False | ||
42 | 48 | |||
43 | 49 | def total_size_link(self, navigator): | ||
44 | 50 | """Return a link to the total size of a collection.""" | ||
45 | 51 | if getattr(self, 'include_total_size', True): | ||
46 | 52 | # This is a named operation that includes the total size | ||
47 | 53 | # inline rather than with a link. | ||
48 | 54 | return None | ||
49 | 55 | if not IResourceGETOperation.providedBy(self): | ||
50 | 56 | # Only GET operations can have their total size split out into | ||
51 | 57 | # a link, because only GET operations are safe. | ||
52 | 58 | return None | ||
53 | 59 | base = str(self.request.URL) | ||
54 | 60 | query = navigator.getCleanQueryString() | ||
55 | 61 | if query != '': | ||
56 | 62 | query += '&' | ||
57 | 63 | return base + '?' + query + "ws.show=total_size" | ||
58 | 47 | 64 | ||
59 | 48 | def __call__(self): | 65 | def __call__(self): |
60 | 49 | values, errors = self.validate() | 66 | values, errors = self.validate() |
61 | @@ -80,13 +97,22 @@ | |||
62 | 80 | # this object served to the client. | 97 | # this object served to the client. |
63 | 81 | return result | 98 | return result |
64 | 82 | 99 | ||
65 | 100 | # The similar patterns in the two branches below suggest some deeper | ||
66 | 101 | # symmetry that should be extracted. | ||
67 | 83 | if queryMultiAdapter((result, self.request), ICollection): | 102 | if queryMultiAdapter((result, self.request), ICollection): |
68 | 84 | # If the result is a web service collection, serve only one | 103 | # If the result is a web service collection, serve only one |
69 | 85 | # batch of the collection. | 104 | # batch of the collection. |
70 | 86 | collection = getMultiAdapter((result, self.request), ICollection) | 105 | collection = getMultiAdapter((result, self.request), ICollection) |
72 | 87 | result = CollectionResource(collection, self.request).batch() + '}' | 106 | resource = CollectionResource(collection, self.request) |
73 | 107 | if self.total_size_only: | ||
74 | 108 | result = resource.get_total_size(collection) | ||
75 | 109 | else: | ||
76 | 110 | result = resource.batch() + '}' | ||
77 | 88 | elif self.should_batch(result): | 111 | elif self.should_batch(result): |
79 | 89 | result = self.batch(result, self.request) + '}' | 112 | if self.total_size_only: |
80 | 113 | result = self.get_total_size(result) | ||
81 | 114 | else: | ||
82 | 115 | result = self.batch(result, self.request) + '}' | ||
83 | 90 | else: | 116 | else: |
84 | 91 | # Serialize the result to JSON. Any embedded entries will be | 117 | # Serialize the result to JSON. Any embedded entries will be |
85 | 92 | # automatically serialized. | 118 | # automatically serialized. |
86 | 93 | 119 | ||
87 | === modified file 'src/lazr/restful/_resource.py' | |||
88 | --- src/lazr/restful/_resource.py 2010-08-04 18:25:21 +0000 | |||
89 | +++ src/lazr/restful/_resource.py 2010-08-10 12:24:46 +0000 | |||
90 | @@ -110,6 +110,7 @@ | |||
91 | 110 | status_reasons[code] = reason | 110 | status_reasons[code] = reason |
92 | 111 | init_status_codes() | 111 | init_status_codes() |
93 | 112 | 112 | ||
94 | 113 | |||
95 | 113 | def decode_value(value): | 114 | def decode_value(value): |
96 | 114 | """Return a unicode value curresponding to `value`.""" | 115 | """Return a unicode value curresponding to `value`.""" |
97 | 115 | if isinstance(value, unicode): | 116 | if isinstance(value, unicode): |
98 | @@ -589,27 +590,45 @@ | |||
99 | 589 | 590 | ||
100 | 590 | """A mixin for resources that need to batch lists of entries.""" | 591 | """A mixin for resources that need to batch lists of entries.""" |
101 | 591 | 592 | ||
109 | 592 | def __init__(self, context, request): | 593 | # TODO: determine real need for __init__ and super() call |
110 | 593 | """A basic constructor.""" | 594 | |
111 | 594 | # Like all mixin classes, this class is designed to be used | 595 | def total_size_link(self, navigator): |
112 | 595 | # with multiple inheritance. That requires defining __init__ | 596 | """Return the URL to fetch to find out the collection's total size. |
113 | 596 | # to call the next constructor in the chain, which means using | 597 | |
114 | 597 | # super() even though this class itself has no superclass. | 598 | If this is None, the total size will be included inline. |
115 | 598 | super(BatchingResourceMixin, self).__init__(context, request) | 599 | |
116 | 600 | :param navigator: A BatchNavigator object for the current batch. | ||
117 | 601 | """ | ||
118 | 602 | return None | ||
119 | 603 | |||
120 | 604 | def get_total_size(self, entries): | ||
121 | 605 | """Get the number of items in entries. | ||
122 | 606 | |||
123 | 607 | :return: a JSON string representing the number of objects in the list | ||
124 | 608 | """ | ||
125 | 609 | if not hasattr(entries, '__len__'): | ||
126 | 610 | entries = IFiniteSequence(entries) | ||
127 | 611 | |||
128 | 612 | return simplejson.dumps(len(entries)) | ||
129 | 613 | |||
130 | 599 | 614 | ||
131 | 600 | def batch(self, entries, request): | 615 | def batch(self, entries, request): |
132 | 601 | """Prepare a batch from a (possibly huge) list of entries. | 616 | """Prepare a batch from a (possibly huge) list of entries. |
133 | 602 | 617 | ||
135 | 603 | :return: A JSON string representing a hash: | 618 | :return: a JSON string representing a hash: |
136 | 619 | |||
137 | 604 | 'entries' contains a list of EntryResource objects for the | 620 | 'entries' contains a list of EntryResource objects for the |
138 | 605 | entries that actually made it into this batch | 621 | entries that actually made it into this batch |
139 | 606 | 'total_size' contains the total size of the list. | 622 | 'total_size' contains the total size of the list. |
140 | 623 | 'total_size_link' contains a link to the total size of the list. | ||
141 | 607 | 'next_url', if present, contains a URL to get the next batch | 624 | 'next_url', if present, contains a URL to get the next batch |
142 | 608 | in the list. | 625 | in the list. |
143 | 609 | 'prev_url', if present, contains a URL to get the previous batch | 626 | 'prev_url', if present, contains a URL to get the previous batch |
144 | 610 | in the list. | 627 | in the list. |
145 | 611 | 'start' contains the starting index of this batch | 628 | 'start' contains the starting index of this batch |
146 | 612 | 629 | ||
147 | 630 | Only one of 'total_size' or 'total_size_link' will be present. | ||
148 | 631 | |||
149 | 613 | Note that the JSON string will be missing its final curly | 632 | Note that the JSON string will be missing its final curly |
150 | 614 | brace. This is in case the caller wants to add some additional | 633 | brace. This is in case the caller wants to add some additional |
151 | 615 | keys to the JSON hash. It's the caller's responsibility to add | 634 | keys to the JSON hash. It's the caller's responsibility to add |
152 | @@ -620,11 +639,12 @@ | |||
153 | 620 | navigator = WebServiceBatchNavigator(entries, request) | 639 | navigator = WebServiceBatchNavigator(entries, request) |
154 | 621 | 640 | ||
155 | 622 | view_permission = getUtility(IWebServiceConfiguration).view_permission | 641 | view_permission = getUtility(IWebServiceConfiguration).view_permission |
161 | 623 | resources = [EntryResource(entry, request) | 642 | batch = { 'start' : navigator.batch.start } |
162 | 624 | for entry in navigator.batch | 643 | total_size_link = self.total_size_link(navigator) |
163 | 625 | if checkPermission(view_permission, entry)] | 644 | if total_size_link is None: |
164 | 626 | batch = { 'total_size' : navigator.batch.listlength, | 645 | batch['total_size'] = navigator.batch.listlength |
165 | 627 | 'start' : navigator.batch.start } | 646 | else: |
166 | 647 | batch['total_size_link'] = total_size_link | ||
167 | 628 | if navigator.batch.start < 0: | 648 | if navigator.batch.start < 0: |
168 | 629 | batch['start'] = None | 649 | batch['start'] = None |
169 | 630 | next_url = navigator.nextBatchURL() | 650 | next_url = navigator.nextBatchURL() |
170 | @@ -637,6 +657,9 @@ | |||
171 | 637 | 657 | ||
172 | 638 | # String together a bunch of entry representations, possibly | 658 | # String together a bunch of entry representations, possibly |
173 | 639 | # obtained from a representation cache. | 659 | # obtained from a representation cache. |
174 | 660 | resources = [EntryResource(entry, request) | ||
175 | 661 | for entry in navigator.batch | ||
176 | 662 | if checkPermission(view_permission, entry)] | ||
177 | 640 | entry_strings = [ | 663 | entry_strings = [ |
178 | 641 | resource._representation(HTTPResource.JSON_TYPE) | 664 | resource._representation(HTTPResource.JSON_TYPE) |
179 | 642 | for resource in resources] | 665 | for resource in resources] |
180 | @@ -674,6 +697,10 @@ | |||
181 | 674 | except ComponentLookupError: | 697 | except ComponentLookupError: |
182 | 675 | self.request.response.setStatus(400) | 698 | self.request.response.setStatus(400) |
183 | 676 | return "No such operation: " + operation_name | 699 | return "No such operation: " + operation_name |
184 | 700 | |||
185 | 701 | show = self.request.form.get('ws.show') | ||
186 | 702 | if show == 'total_size': | ||
187 | 703 | operation.total_size_only = True | ||
188 | 677 | return operation() | 704 | return operation() |
189 | 678 | 705 | ||
190 | 679 | def handleCustomPOST(self, operation_name): | 706 | def handleCustomPOST(self, operation_name): |
191 | @@ -1664,14 +1691,17 @@ | |||
192 | 1664 | self.request.response.setHeader('Content-type', self.JSON_TYPE) | 1691 | self.request.response.setHeader('Content-type', self.JSON_TYPE) |
193 | 1665 | return result | 1692 | return result |
194 | 1666 | 1693 | ||
196 | 1667 | def batch(self, entries=None): | 1694 | def batch(self, entries=None, request=None): |
197 | 1668 | """Return a JSON representation of a batch of entries. | 1695 | """Return a JSON representation of a batch of entries. |
198 | 1669 | 1696 | ||
199 | 1670 | :param entries: (Optional) A precomputed list of entries to batch. | 1697 | :param entries: (Optional) A precomputed list of entries to batch. |
200 | 1698 | :param request: (Optional) The current request. | ||
201 | 1671 | """ | 1699 | """ |
202 | 1672 | if entries is None: | 1700 | if entries is None: |
203 | 1673 | entries = self.collection.find() | 1701 | entries = self.collection.find() |
205 | 1674 | result = super(CollectionResource, self).batch(entries, self.request) | 1702 | if request is None: |
206 | 1703 | request = self.request | ||
207 | 1704 | result = super(CollectionResource, self).batch(entries, request) | ||
208 | 1675 | result += ( | 1705 | result += ( |
209 | 1676 | ', "resource_type_link" : ' + simplejson.dumps(self.type_url) | 1706 | ', "resource_type_link" : ' + simplejson.dumps(self.type_url) |
210 | 1677 | + '}') | 1707 | + '}') |
211 | @@ -1862,7 +1892,7 @@ | |||
212 | 1862 | # by the entry classes. | 1892 | # by the entry classes. |
213 | 1863 | collection_classes.append(registration.factory) | 1893 | collection_classes.append(registration.factory) |
214 | 1864 | namespace = self.WADL_TEMPLATE.pt_getContext() | 1894 | namespace = self.WADL_TEMPLATE.pt_getContext() |
216 | 1865 | namespace['context'] = self | 1895 | namespace['service'] = self |
217 | 1866 | namespace['request'] = self.request | 1896 | namespace['request'] = self.request |
218 | 1867 | namespace['entries'] = entry_classes | 1897 | namespace['entries'] = entry_classes |
219 | 1868 | namespace['collections'] = collection_classes | 1898 | namespace['collections'] = collection_classes |
220 | @@ -2061,13 +2091,13 @@ | |||
221 | 2061 | def singular_type(self): | 2091 | def singular_type(self): |
222 | 2062 | """Return the singular name for this object type.""" | 2092 | """Return the singular name for this object type.""" |
223 | 2063 | interface = self.entry_interface | 2093 | interface = self.entry_interface |
225 | 2064 | return interface.queryTaggedValue(LAZR_WEBSERVICE_NAME)['singular'] | 2094 | return interface.getTaggedValue(LAZR_WEBSERVICE_NAME)['singular'] |
226 | 2065 | 2095 | ||
227 | 2066 | @property | 2096 | @property |
228 | 2067 | def plural_type(self): | 2097 | def plural_type(self): |
229 | 2068 | """Return the plural name for this object type.""" | 2098 | """Return the plural name for this object type.""" |
230 | 2069 | interface = self.entry_interface | 2099 | interface = self.entry_interface |
232 | 2070 | return interface.queryTaggedValue(LAZR_WEBSERVICE_NAME)['plural'] | 2100 | return interface.getTaggedValue(LAZR_WEBSERVICE_NAME)['plural'] |
233 | 2071 | 2101 | ||
234 | 2072 | @property | 2102 | @property |
235 | 2073 | def type_link(self): | 2103 | def type_link(self): |
236 | 2074 | 2104 | ||
237 | === modified file 'src/lazr/restful/declarations.py' | |||
238 | --- src/lazr/restful/declarations.py 2010-08-04 18:25:21 +0000 | |||
239 | +++ src/lazr/restful/declarations.py 2010-08-10 12:24:46 +0000 | |||
240 | @@ -63,7 +63,7 @@ | |||
241 | 63 | from lazr.restful.security import protect_schema | 63 | from lazr.restful.security import protect_schema |
242 | 64 | from lazr.restful.utils import ( | 64 | from lazr.restful.utils import ( |
243 | 65 | camelcase_to_underscore_separated, get_current_web_service_request, | 65 | camelcase_to_underscore_separated, get_current_web_service_request, |
245 | 66 | make_identifier_safe, VersionedDict) | 66 | make_identifier_safe, VersionedDict, is_total_size_link_active) |
246 | 67 | 67 | ||
247 | 68 | LAZR_WEBSERVICE_EXPORTED = '%s.exported' % LAZR_WEBSERVICE_NS | 68 | LAZR_WEBSERVICE_EXPORTED = '%s.exported' % LAZR_WEBSERVICE_NS |
248 | 69 | LAZR_WEBSERVICE_MUTATORS = '%s.exported.mutators' % LAZR_WEBSERVICE_NS | 69 | LAZR_WEBSERVICE_MUTATORS = '%s.exported.mutators' % LAZR_WEBSERVICE_NS |
249 | @@ -712,10 +712,9 @@ | |||
250 | 712 | class operation_returns_collection_of(_method_annotator): | 712 | class operation_returns_collection_of(_method_annotator): |
251 | 713 | """Specify that the exported operation returns a collection. | 713 | """Specify that the exported operation returns a collection. |
252 | 714 | 714 | ||
255 | 715 | The decorator takes a single argument: an interface that's been | 715 | The decorator takes one required argument, "schema", an interface that's |
256 | 716 | exported as an entry. | 716 | been exported as an entry. |
257 | 717 | """ | 717 | """ |
258 | 718 | |||
259 | 719 | def __init__(self, schema): | 718 | def __init__(self, schema): |
260 | 720 | _check_called_from_interface_def('%s()' % self.__class__.__name__) | 719 | _check_called_from_interface_def('%s()' % self.__class__.__name__) |
261 | 721 | if not IInterface.providedBy(schema): | 720 | if not IInterface.providedBy(schema): |
262 | @@ -1209,23 +1208,22 @@ | |||
263 | 1209 | version = getUtility(IWebServiceConfiguration).active_versions[0] | 1208 | version = getUtility(IWebServiceConfiguration).active_versions[0] |
264 | 1210 | 1209 | ||
265 | 1211 | bases = (BaseResourceOperationAdapter, ) | 1210 | bases = (BaseResourceOperationAdapter, ) |
267 | 1212 | if tag['type'] == 'read_operation': | 1211 | operation_type = tag['type'] |
268 | 1212 | if operation_type == 'read_operation': | ||
269 | 1213 | prefix = 'GET' | 1213 | prefix = 'GET' |
270 | 1214 | provides = IResourceGETOperation | 1214 | provides = IResourceGETOperation |
272 | 1215 | elif tag['type'] in ('factory', 'write_operation'): | 1215 | elif operation_type in ('factory', 'write_operation'): |
273 | 1216 | provides = IResourcePOSTOperation | 1216 | provides = IResourcePOSTOperation |
274 | 1217 | prefix = 'POST' | 1217 | prefix = 'POST' |
276 | 1218 | if tag['type'] == 'factory': | 1218 | if operation_type == 'factory': |
277 | 1219 | bases = (BaseFactoryResourceOperationAdapter,) | 1219 | bases = (BaseFactoryResourceOperationAdapter,) |
279 | 1220 | elif tag['type'] == 'destructor': | 1220 | elif operation_type == 'destructor': |
280 | 1221 | provides = IResourceDELETEOperation | 1221 | provides = IResourceDELETEOperation |
281 | 1222 | prefix = 'DELETE' | 1222 | prefix = 'DELETE' |
282 | 1223 | else: | 1223 | else: |
284 | 1224 | raise AssertionError('Unknown method export type: %s' % tag['type']) | 1224 | raise AssertionError('Unknown method export type: %s' % operation_type) |
285 | 1225 | 1225 | ||
286 | 1226 | return_type = tag['return_type'] | 1226 | return_type = tag['return_type'] |
287 | 1227 | if return_type is None: | ||
288 | 1228 | return_type = None | ||
289 | 1229 | 1227 | ||
290 | 1230 | name = _versioned_class_name( | 1228 | name = _versioned_class_name( |
291 | 1231 | '%s_%s_%s' % (prefix, method.interface.__name__, tag['as']), | 1229 | '%s_%s_%s' % (prefix, method.interface.__name__, tag['as']), |
292 | @@ -1238,7 +1236,16 @@ | |||
293 | 1238 | '_method_name': method.__name__, | 1236 | '_method_name': method.__name__, |
294 | 1239 | '__doc__': method.__doc__} | 1237 | '__doc__': method.__doc__} |
295 | 1240 | 1238 | ||
297 | 1241 | if tag['type'] == 'write_operation': | 1239 | if isinstance(return_type, CollectionField): |
298 | 1240 | # If the version we're being asked for is equal to or later than the | ||
299 | 1241 | # version in which we started exposing total_size_link and this is a | ||
300 | 1242 | # read operation, then include it, otherwise include total_size. | ||
301 | 1243 | config = getUtility(IWebServiceConfiguration) | ||
302 | 1244 | class_dict['include_total_size'] = not ( | ||
303 | 1245 | is_total_size_link_active(version, config) and | ||
304 | 1246 | operation_type == 'read_operation') | ||
305 | 1247 | |||
306 | 1248 | if operation_type == 'write_operation': | ||
307 | 1242 | class_dict['send_modification_event'] = True | 1249 | class_dict['send_modification_event'] = True |
308 | 1243 | factory = type(name, bases, class_dict) | 1250 | factory = type(name, bases, class_dict) |
309 | 1244 | classImplements(factory, provides) | 1251 | classImplements(factory, provides) |
310 | 1245 | 1252 | ||
311 | === modified file 'src/lazr/restful/docs/webservice-declarations.txt' | |||
312 | --- src/lazr/restful/docs/webservice-declarations.txt 2010-08-02 20:10:33 +0000 | |||
313 | +++ src/lazr/restful/docs/webservice-declarations.txt 2010-08-10 12:24:46 +0000 | |||
314 | @@ -32,7 +32,7 @@ | |||
315 | 32 | field, but not the inventory_number field. | 32 | field, but not the inventory_number field. |
316 | 33 | 33 | ||
317 | 34 | >>> from zope.interface import Interface | 34 | >>> from zope.interface import Interface |
319 | 35 | >>> from zope.schema import TextLine, Float, List | 35 | >>> from zope.schema import Text, TextLine, Float, List |
320 | 36 | >>> from lazr.restful.declarations import ( | 36 | >>> from lazr.restful.declarations import ( |
321 | 37 | ... export_as_webservice_entry, exported) | 37 | ... export_as_webservice_entry, exported) |
322 | 38 | >>> class IBook(Interface): | 38 | >>> class IBook(Interface): |
323 | @@ -213,7 +213,7 @@ | |||
324 | 213 | TypeError: export_as_webservice_collection() is missing a method | 213 | TypeError: export_as_webservice_collection() is missing a method |
325 | 214 | tagged with @collection_default_content. | 214 | tagged with @collection_default_content. |
326 | 215 | 215 | ||
328 | 216 | As it is an error, to mark more than one methods: | 216 | As it is an error, to mark more than one method: |
329 | 217 | 217 | ||
330 | 218 | >>> class TwoDefaultContent(Interface): | 218 | >>> class TwoDefaultContent(Interface): |
331 | 219 | ... export_as_webservice_collection(IDummyInterface) | 219 | ... export_as_webservice_collection(IDummyInterface) |
332 | @@ -330,8 +330,8 @@ | |||
333 | 330 | ... text=copy_field(IBook['title'], title=u'Text to search for.')) | 330 | ... text=copy_field(IBook['title'], title=u'Text to search for.')) |
334 | 331 | ... @operation_returns_collection_of(IBook) | 331 | ... @operation_returns_collection_of(IBook) |
335 | 332 | ... @export_read_operation() | 332 | ... @export_read_operation() |
338 | 333 | ... def searchBooks(text): | 333 | ... def searchBookTitles(text): |
339 | 334 | ... """Return list of books containing 'text'.""" | 334 | ... """Return list of books whose titles contain 'text'.""" |
340 | 335 | ... | 335 | ... |
341 | 336 | ... @operation_parameters( | 336 | ... @operation_parameters( |
342 | 337 | ... text=copy_field(IBook['title'], title=u'Text to search for.')) | 337 | ... text=copy_field(IBook['title'], title=u'Text to search for.')) |
343 | @@ -393,11 +393,11 @@ | |||
344 | 393 | return_type: <lazr.restful._operation.ObjectLink object...> | 393 | return_type: <lazr.restful._operation.ObjectLink object...> |
345 | 394 | type: 'factory' | 394 | type: 'factory' |
346 | 395 | 395 | ||
348 | 396 | We did specify the return type for the 'searchBooks' method: it | 396 | We did specify the return type for the 'searchBookTitles' method: it |
349 | 397 | returns a collection. | 397 | returns a collection. |
350 | 398 | 398 | ||
353 | 399 | >>> print_export_tag(IBookSetOnSteroids['searchBooks']) | 399 | >>> print_export_tag(IBookSetOnSteroids['searchBookTitles']) |
354 | 400 | as: 'searchBooks' | 400 | as: 'searchBookTitles' |
355 | 401 | call_with: {} | 401 | call_with: {} |
356 | 402 | params: {'text': <...TextLine...>} | 402 | params: {'text': <...TextLine...>} |
357 | 403 | return_type: <lazr.restful.fields.CollectionField object...> | 403 | return_type: <lazr.restful.fields.CollectionField object...> |
358 | @@ -920,7 +920,8 @@ | |||
359 | 920 | >>> class Book(object): | 920 | >>> class Book(object): |
360 | 921 | ... """Simple IBook implementation.""" | 921 | ... """Simple IBook implementation.""" |
361 | 922 | ... implements(IBook) | 922 | ... implements(IBook) |
363 | 923 | ... def __init__(self, author, title, base_price, inventory_number): | 923 | ... def __init__(self, author, title, base_price, |
364 | 924 | ... inventory_number): | ||
365 | 924 | ... self.author = author | 925 | ... self.author = author |
366 | 925 | ... self.title = title | 926 | ... self.title = title |
367 | 926 | ... self.base_price = base_price | 927 | ... self.base_price = base_price |
368 | @@ -937,6 +938,7 @@ | |||
369 | 937 | >>> class MyWebServiceConfiguration(TestWebServiceConfiguration): | 938 | >>> class MyWebServiceConfiguration(TestWebServiceConfiguration): |
370 | 938 | ... active_versions = ["beta", "1.0", "2.0", "3.0"] | 939 | ... active_versions = ["beta", "1.0", "2.0", "3.0"] |
371 | 939 | ... last_version_with_mutator_named_operations = "1.0" | 940 | ... last_version_with_mutator_named_operations = "1.0" |
372 | 941 | ... first_version_with_total_size_link = "2.0" | ||
373 | 940 | ... code_revision = "1.0b" | 942 | ... code_revision = "1.0b" |
374 | 941 | ... default_batch_size = 50 | 943 | ... default_batch_size = 50 |
375 | 942 | >>> provideUtility(MyWebServiceConfiguration(), IWebServiceConfiguration) | 944 | >>> provideUtility(MyWebServiceConfiguration(), IWebServiceConfiguration) |
376 | @@ -1087,7 +1089,7 @@ | |||
377 | 1087 | ... generate_operation_adapter) | 1089 | ... generate_operation_adapter) |
378 | 1088 | 1090 | ||
379 | 1089 | >>> read_method_adapter_factory = generate_operation_adapter( | 1091 | >>> read_method_adapter_factory = generate_operation_adapter( |
381 | 1090 | ... IBookSetOnSteroids['searchBooks']) | 1092 | ... IBookSetOnSteroids['searchBookTitles']) |
382 | 1091 | >>> IResourceGETOperation.implementedBy(read_method_adapter_factory) | 1093 | >>> IResourceGETOperation.implementedBy(read_method_adapter_factory) |
383 | 1092 | True | 1094 | True |
384 | 1093 | 1095 | ||
385 | @@ -1099,14 +1101,14 @@ | |||
386 | 1099 | 1101 | ||
387 | 1100 | >>> from lazr.restful import ResourceOperation | 1102 | >>> from lazr.restful import ResourceOperation |
388 | 1101 | >>> read_method_adapter_factory.__name__ | 1103 | >>> read_method_adapter_factory.__name__ |
390 | 1102 | 'GET_IBookSetOnSteroids_searchBooks_beta' | 1104 | 'GET_IBookSetOnSteroids_searchBookTitles_beta' |
391 | 1103 | >>> issubclass(read_method_adapter_factory, ResourceOperation) | 1105 | >>> issubclass(read_method_adapter_factory, ResourceOperation) |
392 | 1104 | True | 1106 | True |
393 | 1105 | 1107 | ||
394 | 1106 | The adapter's docstring is taken from the decorated method docstring. | 1108 | The adapter's docstring is taken from the decorated method docstring. |
395 | 1107 | 1109 | ||
396 | 1108 | >>> read_method_adapter_factory.__doc__ | 1110 | >>> read_method_adapter_factory.__doc__ |
398 | 1109 | "Return list of books containing 'text'." | 1111 | "Return list of books whose titles contain 'text'." |
399 | 1110 | 1112 | ||
400 | 1111 | The adapter's params attribute contains the specification of the | 1113 | The adapter's params attribute contains the specification of the |
401 | 1112 | parameters accepted by the operation. | 1114 | parameters accepted by the operation. |
402 | @@ -1126,7 +1128,7 @@ | |||
403 | 1126 | ... | 1128 | ... |
404 | 1127 | ... result = None | 1129 | ... result = None |
405 | 1128 | ... | 1130 | ... |
407 | 1129 | ... def searchBooks(self, text): | 1131 | ... def searchBookTitles(self, text): |
408 | 1130 | ... return self.result | 1132 | ... return self.result |
409 | 1131 | ... | 1133 | ... |
410 | 1132 | ... def new(self, author, base_price, title): | 1134 | ... def new(self, author, base_price, title): |
411 | @@ -1148,7 +1150,7 @@ | |||
412 | 1148 | return value is a dictionary containing a batched list. | 1150 | return value is a dictionary containing a batched list. |
413 | 1149 | 1151 | ||
414 | 1150 | >>> print read_method_adapter.call(text='') | 1152 | >>> print read_method_adapter.call(text='') |
416 | 1151 | {"total_size": 0, "start": null, "entries": []} | 1153 | {"total_size": 0, "start": 0, "entries": []} |
417 | 1152 | 1154 | ||
418 | 1153 | Methods exported as a write operations generates an adapter providing | 1155 | Methods exported as a write operations generates an adapter providing |
419 | 1154 | IResourcePOSTOperation. | 1156 | IResourcePOSTOperation. |
420 | @@ -1649,6 +1651,51 @@ | |||
421 | 1649 | AssertionError: 'IMultiVersionCollection' isn't tagged for export | 1651 | AssertionError: 'IMultiVersionCollection' isn't tagged for export |
422 | 1650 | to web service version 'NoSuchVersion'. | 1652 | to web service version 'NoSuchVersion'. |
423 | 1651 | 1653 | ||
424 | 1654 | total_size_link | ||
425 | 1655 | ~~~~~~~~~~~~~~~ | ||
426 | 1656 | |||
427 | 1657 | Collections previously exposed their total size via a `total_size` attribute. | ||
428 | 1658 | However, newer versions of lazr.restful expose a `total_size_link` intead. To | ||
429 | 1659 | facilitate transitioning from one approach to the other the configuration | ||
430 | 1660 | option `first_version_with_total_size_link` has been added to | ||
431 | 1661 | IWebServiceConfiguration. | ||
432 | 1662 | |||
433 | 1663 | By default the first_version_with_total_size_link is set to the earliest | ||
434 | 1664 | available web service version, but if you have stable versions of your web | ||
435 | 1665 | service you wish to maintain compatability with you can specify the version in | ||
436 | 1666 | which you want the new behavior to take effect in the web service | ||
437 | 1667 | configuration. | ||
438 | 1668 | |||
439 | 1669 | >>> from zope.component import getUtility | ||
440 | 1670 | >>> config = getUtility(IWebServiceConfiguration) | ||
441 | 1671 | >>> config.last_version_with_mutator_named_operations = '1.0' | ||
442 | 1672 | |||
443 | 1673 | >>> from lazr.restful.declarations import ( | ||
444 | 1674 | ... export_read_operation, operation_returns_collection_of, | ||
445 | 1675 | ... operation_for_version) | ||
446 | 1676 | >>> class IWithMultiVersionCollection(Interface): | ||
447 | 1677 | ... export_as_webservice_entry() | ||
448 | 1678 | ... | ||
449 | 1679 | ... @operation_for_version('2.0') | ||
450 | 1680 | ... @operation_for_version('1.0') | ||
451 | 1681 | ... @operation_returns_collection_of(Interface) | ||
452 | 1682 | ... @export_read_operation() | ||
453 | 1683 | ... def method(): | ||
454 | 1684 | ... """A method that returns a collection.""" | ||
455 | 1685 | |||
456 | 1686 | >>> method = IWithMultiVersionCollection['method'] | ||
457 | 1687 | >>> dummy_data = None # this would be an intance that has the method | ||
458 | 1688 | >>> v10 = generate_operation_adapter(method, '1.0')(dummy_data, request) | ||
459 | 1689 | >>> v20 = generate_operation_adapter(method, '2.0')(dummy_data, request) | ||
460 | 1690 | |||
461 | 1691 | We can see that version 1.0 includes the total size for backward compatability | ||
462 | 1692 | while version 2.0 includes a link to fetch the total size. | ||
463 | 1693 | |||
464 | 1694 | >>> v10.include_total_size | ||
465 | 1695 | True | ||
466 | 1696 | >>> v20.include_total_size | ||
467 | 1697 | False | ||
468 | 1698 | |||
469 | 1652 | Entries | 1699 | Entries |
470 | 1653 | ------- | 1700 | ------- |
471 | 1654 | 1701 | ||
472 | @@ -2541,8 +2588,8 @@ | |||
473 | 2541 | >>> request_interface = IWebServiceClientRequest | 2588 | >>> request_interface = IWebServiceClientRequest |
474 | 2542 | >>> adapter_registry.lookup( | 2589 | >>> adapter_registry.lookup( |
475 | 2543 | ... (IBookSetOnSteroids, request_interface), | 2590 | ... (IBookSetOnSteroids, request_interface), |
478 | 2544 | ... IResourceGETOperation, 'searchBooks') | 2591 | ... IResourceGETOperation, 'searchBookTitles') |
479 | 2545 | <class '...GET_IBookSetOnSteroids_searchBooks_beta'> | 2592 | <class '...GET_IBookSetOnSteroids_searchBookTitles_beta'> |
480 | 2546 | >>> adapter_registry.lookup( | 2593 | >>> adapter_registry.lookup( |
481 | 2547 | ... (IBookSetOnSteroids, request_interface), | 2594 | ... (IBookSetOnSteroids, request_interface), |
482 | 2548 | ... IResourcePOSTOperation, 'create_book') | 2595 | ... IResourcePOSTOperation, 'create_book') |
483 | 2549 | 2596 | ||
484 | === modified file 'src/lazr/restful/docs/webservice.txt' | |||
485 | --- src/lazr/restful/docs/webservice.txt 2010-08-04 18:25:21 +0000 | |||
486 | +++ src/lazr/restful/docs/webservice.txt 2010-08-10 12:24:46 +0000 | |||
487 | @@ -503,6 +503,7 @@ | |||
488 | 503 | ... code_revision = 'test' | 503 | ... code_revision = 'test' |
489 | 504 | ... max_batch_size = 100 | 504 | ... max_batch_size = 100 |
490 | 505 | ... directives.publication_class(WebServiceTestPublication) | 505 | ... directives.publication_class(WebServiceTestPublication) |
491 | 506 | ... first_version_with_total_size_link = 'devel' | ||
492 | 506 | 507 | ||
493 | 507 | >>> from grokcore.component.testing import grok_component | 508 | >>> from grokcore.component.testing import grok_component |
494 | 508 | >>> ignore = grok_component( | 509 | >>> ignore = grok_component( |
495 | 509 | 510 | ||
496 | === modified file 'src/lazr/restful/example/base/root.py' | |||
497 | --- src/lazr/restful/example/base/root.py 2010-06-14 14:09:31 +0000 | |||
498 | +++ src/lazr/restful/example/base/root.py 2010-08-10 12:24:46 +0000 | |||
499 | @@ -398,6 +398,7 @@ | |||
500 | 398 | <p>Don't use this unless you like changing things.</p>""" | 398 | <p>Don't use this unless you like changing things.</p>""" |
501 | 399 | } | 399 | } |
502 | 400 | last_version_with_mutator_named_operations = None | 400 | last_version_with_mutator_named_operations = None |
503 | 401 | first_version_with_total_size_link = None | ||
504 | 401 | use_https = False | 402 | use_https = False |
505 | 402 | view_permission = 'lazr.restful.example.base.View' | 403 | view_permission = 'lazr.restful.example.base.View' |
506 | 403 | 404 | ||
507 | 404 | 405 | ||
508 | === modified file 'src/lazr/restful/example/base/tests/collection.txt' | |||
509 | --- src/lazr/restful/example/base/tests/collection.txt 2009-04-24 15:14:07 +0000 | |||
510 | +++ src/lazr/restful/example/base/tests/collection.txt 2010-08-10 12:24:46 +0000 | |||
511 | @@ -173,14 +173,12 @@ | |||
512 | 173 | ... "/cookbooks?ws.op=find_recipes&%s" % args).jsonBody() | 173 | ... "/cookbooks?ws.op=find_recipes&%s" % args).jsonBody() |
513 | 174 | 174 | ||
514 | 175 | >>> s_recipes = search_recipes("chicken") | 175 | >>> s_recipes = search_recipes("chicken") |
515 | 176 | >>> s_recipes['total_size'] | ||
516 | 177 | 3 | ||
517 | 178 | >>> sorted(r['instructions'] for r in s_recipes['entries']) | 176 | >>> sorted(r['instructions'] for r in s_recipes['entries']) |
518 | 179 | [u'Draw, singe, stuff, and truss...', u'You can always judge...'] | 177 | [u'Draw, singe, stuff, and truss...', u'You can always judge...'] |
519 | 180 | 178 | ||
520 | 181 | >>> veg_recipes = search_recipes("chicken", True) | 179 | >>> veg_recipes = search_recipes("chicken", True) |
523 | 182 | >>> veg_recipes['total_size'] | 180 | >>> veg_recipes['entries'] |
524 | 183 | 0 | 181 | [] |
525 | 184 | 182 | ||
526 | 185 | A custom operation that returns a list of objects is paginated, just | 183 | A custom operation that returns a list of objects is paginated, just |
527 | 186 | like a collection. | 184 | like a collection. |
528 | @@ -196,11 +194,21 @@ | |||
529 | 196 | empty list of results: | 194 | empty list of results: |
530 | 197 | 195 | ||
531 | 198 | >>> empty_collection = search_recipes("nosuchrecipe") | 196 | >>> empty_collection = search_recipes("nosuchrecipe") |
532 | 199 | >>> empty_collection['total_size'] | ||
533 | 200 | 0 | ||
534 | 201 | >>> [r['instructions'] for r in empty_collection['entries']] | 197 | >>> [r['instructions'] for r in empty_collection['entries']] |
535 | 202 | [] | 198 | [] |
536 | 203 | 199 | ||
537 | 200 | When an operation yields a collection of objects, the representation | ||
538 | 201 | includes a link that yields the total size of the collection. | ||
539 | 202 | |||
540 | 203 | >>> print s_recipes['total_size_link'] | ||
541 | 204 | http://.../cookbooks?search=chicken&vegetarian=false&ws.op=find_recipes&ws.show=total_size | ||
542 | 205 | |||
543 | 206 | Sending a GET request to that link yields a JSON representation of the | ||
544 | 207 | total size. | ||
545 | 208 | |||
546 | 209 | >>> print webservice.get(s_recipes['total_size_link']).jsonBody() | ||
547 | 210 | 3 | ||
548 | 211 | |||
549 | 204 | Custom operations may have error handling. In this case, the error | 212 | Custom operations may have error handling. In this case, the error |
550 | 205 | handling is in the validate() method of the 'search' field. | 213 | handling is in the validate() method of the 'search' field. |
551 | 206 | 214 | ||
552 | @@ -216,8 +224,9 @@ | |||
553 | 216 | 224 | ||
554 | 217 | >>> general_cookbooks = webservice.get( | 225 | >>> general_cookbooks = webservice.get( |
555 | 218 | ... "/cookbooks?ws.op=find_for_cuisine&cuisine=General") | 226 | ... "/cookbooks?ws.op=find_for_cuisine&cuisine=General") |
558 | 219 | >>> general_cookbooks.jsonBody()['total_size'] | 227 | >>> print general_cookbooks.jsonBody()['total_size_link'] |
559 | 220 | 3 | 228 | http://cookbooks.dev/devel/cookbooks?cuisine=General&ws.op=find_for_cuisine&ws.show=total_size |
560 | 229 | |||
561 | 221 | 230 | ||
562 | 222 | POST operations | 231 | POST operations |
563 | 223 | =============== | 232 | =============== |
564 | 224 | 233 | ||
565 | === modified file 'src/lazr/restful/example/base/tests/wadl.txt' | |||
566 | --- src/lazr/restful/example/base/tests/wadl.txt 2010-03-10 18:45:04 +0000 | |||
567 | +++ src/lazr/restful/example/base/tests/wadl.txt 2010-08-10 12:24:46 +0000 | |||
568 | @@ -537,8 +537,9 @@ | |||
569 | 537 | 'entry_resource_descriptions'. | 537 | 'entry_resource_descriptions'. |
570 | 538 | 538 | ||
571 | 539 | >>> entry_resource_descriptions = [] | 539 | >>> entry_resource_descriptions = [] |
574 | 540 | >>> entry_resource_types = other_children[first_entry_type_index:-2] | 540 | >>> entry_resource_types = other_children[first_entry_type_index:-3] |
575 | 541 | >>> hosted_binary_resource_type, simple_binary_type = other_children[-2:] | 541 | >>> (hosted_binary_resource_type, scalar_type, simple_binary_type |
576 | 542 | ... ) = other_children[-3:] | ||
577 | 542 | >>> for index in range(0, len(entry_resource_types), 5): | 543 | >>> for index in range(0, len(entry_resource_types), 5): |
578 | 543 | ... entry_resource_descriptions.append( | 544 | ... entry_resource_descriptions.append( |
579 | 544 | ... (tuple(entry_resource_types[index:index + 5]))) | 545 | ... (tuple(entry_resource_types[index:index + 5]))) |
580 | @@ -1155,9 +1156,9 @@ | |||
581 | 1155 | All collection representations have the same five <param> tags. | 1156 | All collection representations have the same five <param> tags. |
582 | 1156 | 1157 | ||
583 | 1157 | >>> [param.attrib['name'] for param in collection_rep] | 1158 | >>> [param.attrib['name'] for param in collection_rep] |
587 | 1158 | ['resource_type_link', 'total_size', 'start', 'next_collection_link', | 1159 | ['resource_type_link', 'total_size', 'total_size_link', 'start', |
588 | 1159 | 'prev_collection_link', 'entries', 'entry_links'] | 1160 | 'next_collection_link', 'prev_collection_link', 'entries', 'entry_links'] |
589 | 1160 | >>> (type_link, size, start, next, prev, entries, | 1161 | >>> (type_link, size, size_link, start, next, prev, entries, |
590 | 1161 | ... entry_links) = collection_rep | 1162 | ... entry_links) = collection_rep |
591 | 1162 | 1163 | ||
592 | 1163 | So what's the difference between a collection of people and a | 1164 | So what's the difference between a collection of people and a |
593 | 1164 | 1165 | ||
594 | === modified file 'src/lazr/restful/example/multiversion/tests/wadl.txt' | |||
595 | --- src/lazr/restful/example/multiversion/tests/wadl.txt 2010-03-10 20:44:03 +0000 | |||
596 | +++ src/lazr/restful/example/multiversion/tests/wadl.txt 2010-08-10 12:24:46 +0000 | |||
597 | @@ -92,3 +92,32 @@ | |||
598 | 92 | >>> pair_collection = contents['pair_collection'] | 92 | >>> pair_collection = contents['pair_collection'] |
599 | 93 | >>> sorted([method.attrib['id'] for method in pair_collection]) | 93 | >>> sorted([method.attrib['id'] for method in pair_collection]) |
600 | 94 | ['key_value_pairs-get'] | 94 | ['key_value_pairs-get'] |
601 | 95 | |||
602 | 96 | total_size_link | ||
603 | 97 | =============== | ||
604 | 98 | |||
605 | 99 | The version in which total_size_link is introduced is controlled by the | ||
606 | 100 | first_version_with_total_size_link attribute of the web service configuration | ||
607 | 101 | (IWebServiceConfiguration) utility. | ||
608 | 102 | |||
609 | 103 | We'll configure the web service to begin including `total_size_link` values | ||
610 | 104 | in version 3.0: | ||
611 | 105 | |||
612 | 106 | >>> from zope.component import getUtility | ||
613 | 107 | >>> from lazr.restful.interfaces import IWebServiceConfiguration | ||
614 | 108 | >>> config = getUtility(IWebServiceConfiguration) | ||
615 | 109 | >>> config.first_version_with_total_size_link = '3.0' | ||
616 | 110 | |||
617 | 111 | Now if we request the WADL for 3.0 it will include a description of | ||
618 | 112 | total_size_link. | ||
619 | 113 | |||
620 | 114 | >>> webservice.get('/', media_type='application/vnd.sun.wadl+xml', | ||
621 | 115 | ... api_version='3.0').body | ||
622 | 116 | '...<wadl:param style="plain" name="total_size_link"...' | ||
623 | 117 | |||
624 | 118 | If we request an earlier version, total_size_link is not described. | ||
625 | 119 | |||
626 | 120 | >>> wadl = webservice.get('/', media_type='application/vnd.sun.wadl+xml', | ||
627 | 121 | ... api_version='2.0').body | ||
628 | 122 | >>> 'total_size_link' in wadl | ||
629 | 123 | False | ||
630 | 95 | 124 | ||
631 | === modified file 'src/lazr/restful/interfaces/_rest.py' | |||
632 | --- src/lazr/restful/interfaces/_rest.py 2010-06-03 14:42:44 +0000 | |||
633 | +++ src/lazr/restful/interfaces/_rest.py 2010-08-10 12:24:46 +0000 | |||
634 | @@ -507,6 +507,13 @@ | |||
635 | 507 | all subsequent versions, they will not be published as named | 507 | all subsequent versions, they will not be published as named |
636 | 508 | operations.""") | 508 | operations.""") |
637 | 509 | 509 | ||
638 | 510 | first_version_with_total_size_link = TextLine( | ||
639 | 511 | default=None, | ||
640 | 512 | description=u"""In earlier versions of lazr.restful collections | ||
641 | 513 | included a total_size field, now they include a total_size_link | ||
642 | 514 | instead. Setting this value determines in which version the new | ||
643 | 515 | behavior takes effect.""") | ||
644 | 516 | |||
645 | 510 | code_revision = TextLine( | 517 | code_revision = TextLine( |
646 | 511 | default=u"", | 518 | default=u"", |
647 | 512 | description=u"""A string designating the current revision | 519 | description=u"""A string designating the current revision |
648 | 513 | 520 | ||
649 | === modified file 'src/lazr/restful/simple.py' | |||
650 | --- src/lazr/restful/simple.py 2010-06-14 18:47:39 +0000 | |||
651 | +++ src/lazr/restful/simple.py 2010-08-10 12:24:46 +0000 | |||
652 | @@ -477,5 +477,6 @@ | |||
653 | 477 | 477 | ||
654 | 478 | 478 | ||
655 | 479 | BaseWebServiceConfiguration = implement_from_dict( | 479 | BaseWebServiceConfiguration = implement_from_dict( |
657 | 480 | "BaseWebServiceConfiguration", IWebServiceConfiguration, {}, object) | 480 | "BaseWebServiceConfiguration", IWebServiceConfiguration, |
658 | 481 | {'first_version_with_total_size_link': None}, object) | ||
659 | 481 | 482 | ||
660 | 482 | 483 | ||
661 | === modified file 'src/lazr/restful/tales.py' | |||
662 | --- src/lazr/restful/tales.py 2010-03-10 20:44:03 +0000 | |||
663 | +++ src/lazr/restful/tales.py 2010-08-10 12:24:46 +0000 | |||
664 | @@ -34,7 +34,8 @@ | |||
665 | 34 | IResourceOperation, IResourcePOSTOperation, IScopedCollection, | 34 | IResourceOperation, IResourcePOSTOperation, IScopedCollection, |
666 | 35 | ITopLevelEntryLink, IWebServiceClientRequest, IWebServiceConfiguration, | 35 | ITopLevelEntryLink, IWebServiceClientRequest, IWebServiceConfiguration, |
667 | 36 | IWebServiceVersion, LAZR_WEBSERVICE_NAME) | 36 | IWebServiceVersion, LAZR_WEBSERVICE_NAME) |
669 | 37 | from lazr.restful.utils import get_current_web_service_request | 37 | from lazr.restful.utils import (get_current_web_service_request, |
670 | 38 | is_total_size_link_active) | ||
671 | 38 | 39 | ||
672 | 39 | 40 | ||
673 | 40 | class WadlDocstringLinker(DocstringLinker): | 41 | class WadlDocstringLinker(DocstringLinker): |
674 | @@ -234,6 +235,11 @@ | |||
675 | 234 | return config.version_descriptions.get(self.service_version, None) | 235 | return config.version_descriptions.get(self.service_version, None) |
676 | 235 | 236 | ||
677 | 236 | @property | 237 | @property |
678 | 238 | def is_total_size_link_active(self): | ||
679 | 239 | config = getUtility(IWebServiceConfiguration) | ||
680 | 240 | return is_total_size_link_active(self.resource.request.version, config) | ||
681 | 241 | |||
682 | 242 | @property | ||
683 | 237 | def top_level_resources(self): | 243 | def top_level_resources(self): |
684 | 238 | """Return a list of dicts describing the top-level resources.""" | 244 | """Return a list of dicts describing the top-level resources.""" |
685 | 239 | resource_dicts = [] | 245 | resource_dicts = [] |
686 | 240 | 246 | ||
687 | === modified file 'src/lazr/restful/templates/wadl-root.pt' | |||
688 | --- src/lazr/restful/templates/wadl-root.pt 2010-03-10 18:50:27 +0000 | |||
689 | +++ src/lazr/restful/templates/wadl-root.pt 2010-08-10 12:24:46 +0000 | |||
690 | @@ -13,21 +13,21 @@ | |||
691 | 13 | 13 | ||
692 | 14 | <wadl:doc xmlns="http://www.w3.org/1999/xhtml" | 14 | <wadl:doc xmlns="http://www.w3.org/1999/xhtml" |
693 | 15 | title="About this service" | 15 | title="About this service" |
695 | 16 | tal:content="structure context/wadl:description"> | 16 | tal:content="structure service/wadl:description"> |
696 | 17 | Version-independent description of the web service. | 17 | Version-independent description of the web service. |
697 | 18 | </wadl:doc> | 18 | </wadl:doc> |
698 | 19 | 19 | ||
699 | 20 | <wadl:doc xmlns="http://www.w3.org/1999/xhtml" | 20 | <wadl:doc xmlns="http://www.w3.org/1999/xhtml" |
701 | 21 | tal:define="version context/wadl:service_version" | 21 | tal:define="version service/wadl:service_version" |
702 | 22 | tal:attributes="title string:About version ${version}" | 22 | tal:attributes="title string:About version ${version}" |
704 | 23 | tal:content="structure context/wadl:version_description"> | 23 | tal:content="structure service/wadl:version_description"> |
705 | 24 | Description of this version of the web service. | 24 | Description of this version of the web service. |
706 | 25 | </wadl:doc> | 25 | </wadl:doc> |
707 | 26 | 26 | ||
708 | 27 | <!--There is one "service root" resource, located (as you'd expect) | 27 | <!--There is one "service root" resource, located (as you'd expect) |
709 | 28 | at the service root. This very document is the WADL | 28 | at the service root. This very document is the WADL |
710 | 29 | representation of the "service root" resource.--> | 29 | representation of the "service root" resource.--> |
712 | 30 | <resources tal:attributes="base context/wadl:url"> | 30 | <resources tal:attributes="base service/wadl:url"> |
713 | 31 | <resource path="" type="#service-root" /> | 31 | <resource path="" type="#service-root" /> |
714 | 32 | </resources> | 32 | </resources> |
715 | 33 | 33 | ||
716 | @@ -46,7 +46,7 @@ | |||
717 | 46 | <!--The JSON representation of a "service root" resource contains a | 46 | <!--The JSON representation of a "service root" resource contains a |
718 | 47 | number of links to collection-type resources.--> | 47 | number of links to collection-type resources.--> |
719 | 48 | <representation mediaType="application/json" id="service-root-json"> | 48 | <representation mediaType="application/json" id="service-root-json"> |
721 | 49 | <tal:root_params tal:repeat="param context/wadl:top_level_resources"> | 49 | <tal:root_params tal:repeat="param service/wadl:top_level_resources"> |
722 | 50 | <param style="plain" tal:attributes="name param/name; | 50 | <param style="plain" tal:attributes="name param/name; |
723 | 51 | path param/path"> | 51 | path param/path"> |
724 | 52 | <link tal:attributes="resource_type param/resource/wadl:type_link" /> | 52 | <link tal:attributes="resource_type param/resource/wadl:type_link" /> |
725 | @@ -284,15 +284,29 @@ | |||
726 | 284 | 284 | ||
727 | 285 | <representation mediaType="application/json" | 285 | <representation mediaType="application/json" |
728 | 286 | tal:attributes="id | 286 | tal:attributes="id |
730 | 287 | string:${context/wadl_entry:entry_page_representation_id}"> | 287 | string:${context/wadl_entry:entry_page_representation_id}" |
731 | 288 | tal:define="is_total_size_link_active | ||
732 | 289 | service/wadl:is_total_size_link_active"> | ||
733 | 288 | 290 | ||
734 | 289 | <param style="plain" name="resource_type_link" | 291 | <param style="plain" name="resource_type_link" |
735 | 290 | path="$['resource_type_link']"> | 292 | path="$['resource_type_link']"> |
736 | 291 | <link /> | 293 | <link /> |
737 | 292 | </param> | 294 | </param> |
738 | 293 | 295 | ||
739 | 296 | <tal:comment condition="nothing"> | ||
740 | 297 | If we are not using total_size_link, continue to signal that | ||
741 | 298 | total_size is required as it has been in the past. | ||
742 | 299 | </tal:comment> | ||
743 | 300 | |||
744 | 294 | <param style="plain" name="total_size" path="$['total_size']" | 301 | <param style="plain" name="total_size" path="$['total_size']" |
746 | 295 | required="true" /> | 302 | tal:attributes=" |
747 | 303 | required python:is_total_size_link_active and 'false' or 'true'"/> | ||
748 | 304 | |||
749 | 305 | <param tal:condition="is_total_size_link_active" | ||
750 | 306 | style="plain" name="total_size_link" path="$['total_size_link']" | ||
751 | 307 | required="false"> | ||
752 | 308 | <link resource_type="#ScalarValue" /> | ||
753 | 309 | </param> | ||
754 | 296 | 310 | ||
755 | 297 | <param style="plain" name="start" path="$['start']" required="true" /> | 311 | <param style="plain" name="start" path="$['start']" required="true" /> |
756 | 298 | 312 | ||
757 | @@ -321,7 +335,7 @@ | |||
758 | 321 | <!--End representation and resource_type definitions for entry | 335 | <!--End representation and resource_type definitions for entry |
759 | 322 | resources. --> | 336 | resources. --> |
760 | 323 | 337 | ||
762 | 324 | <!--Finally, describe the 'hosted binary file' type.--> | 338 | <!--Finally, describe the 'hosted binary file' type...--> |
763 | 325 | <resource_type id="HostedFile"> | 339 | <resource_type id="HostedFile"> |
764 | 326 | <method name="GET" id="HostedFile-get"> | 340 | <method name="GET" id="HostedFile-get"> |
765 | 327 | <response> | 341 | <response> |
766 | @@ -334,6 +348,15 @@ | |||
767 | 334 | <method name="DELETE" id="HostedFile-delete" /> | 348 | <method name="DELETE" id="HostedFile-delete" /> |
768 | 335 | </resource_type> | 349 | </resource_type> |
769 | 336 | 350 | ||
770 | 351 | <!--...and the simple 'scalar value' type.--> | ||
771 | 352 | <resource_type id="ScalarValue"> | ||
772 | 353 | <method name="GET" id="ScalarValue-get"> | ||
773 | 354 | <response> | ||
774 | 355 | <representation mediaType="application/json" /> | ||
775 | 356 | </response> | ||
776 | 357 | </method> | ||
777 | 358 | </resource_type> | ||
778 | 359 | |||
779 | 337 | <!--Define a data type for binary data.--> | 360 | <!--Define a data type for binary data.--> |
780 | 338 | <xsd:simpleType name="binary"> | 361 | <xsd:simpleType name="binary"> |
781 | 339 | <xsd:list itemType="byte" /> | 362 | <xsd:list itemType="byte" /> |
782 | 340 | 363 | ||
783 | === modified file 'src/lazr/restful/testing/webservice.py' | |||
784 | --- src/lazr/restful/testing/webservice.py 2010-03-03 16:40:14 +0000 | |||
785 | +++ src/lazr/restful/testing/webservice.py 2010-08-10 12:24:46 +0000 | |||
786 | @@ -135,6 +135,7 @@ | |||
787 | 135 | self.traversed_objects = [] | 135 | self.traversed_objects = [] |
788 | 136 | self.query_string_params = {} | 136 | self.query_string_params = {} |
789 | 137 | self.method = 'GET' | 137 | self.method = 'GET' |
790 | 138 | self.URL = 'http://api.example.org/' | ||
791 | 138 | 139 | ||
792 | 139 | 140 | ||
793 | 140 | def getTraversalStack(self): | 141 | def getTraversalStack(self): |
794 | @@ -173,6 +174,8 @@ | |||
795 | 173 | def pprint_collection(json_body): | 174 | def pprint_collection(json_body): |
796 | 174 | """Pretty-print a webservice collection JSON representation.""" | 175 | """Pretty-print a webservice collection JSON representation.""" |
797 | 175 | for key, value in sorted(json_body.items()): | 176 | for key, value in sorted(json_body.items()): |
798 | 177 | if key == 'total_size_link': | ||
799 | 178 | continue | ||
800 | 176 | if key != 'entries': | 179 | if key != 'entries': |
801 | 177 | print '%s: %r' % (key, value) | 180 | print '%s: %r' % (key, value) |
802 | 178 | print '---' | 181 | print '---' |
803 | 179 | 182 | ||
804 | === modified file 'src/lazr/restful/tests/test_utils.py' | |||
805 | --- src/lazr/restful/tests/test_utils.py 2010-03-03 13:08:12 +0000 | |||
806 | +++ src/lazr/restful/tests/test_utils.py 2010-08-10 12:24:46 +0000 | |||
807 | @@ -10,7 +10,8 @@ | |||
808 | 10 | from zope.security.management import ( | 10 | from zope.security.management import ( |
809 | 11 | endInteraction, newInteraction, queryInteraction) | 11 | endInteraction, newInteraction, queryInteraction) |
810 | 12 | 12 | ||
812 | 13 | from lazr.restful.utils import get_current_browser_request | 13 | from lazr.restful.utils import (get_current_browser_request, |
813 | 14 | is_total_size_link_active) | ||
814 | 14 | 15 | ||
815 | 15 | 16 | ||
816 | 16 | class TestUtils(unittest.TestCase): | 17 | class TestUtils(unittest.TestCase): |
817 | @@ -28,6 +29,27 @@ | |||
818 | 28 | self.assertEquals(request, get_current_browser_request()) | 29 | self.assertEquals(request, get_current_browser_request()) |
819 | 29 | endInteraction() | 30 | endInteraction() |
820 | 30 | 31 | ||
821 | 32 | def test_is_total_size_link_active(self): | ||
822 | 33 | # Parts of the code want to know if the sizes of collections should be | ||
823 | 34 | # reported in an attribute or via a link back to the service. The | ||
824 | 35 | # is_total_size_link_active function takes the version of the API in | ||
825 | 36 | # question and a web service configuration object and returns a | ||
826 | 37 | # boolean that is true if a link should be used, false otherwise. | ||
827 | 38 | |||
828 | 39 | # Here's the fake web service config we'll be using. | ||
829 | 40 | class FakeConfig: | ||
830 | 41 | active_versions = ['1.0', '2.0', '3.0'] | ||
831 | 42 | first_version_with_total_size_link = '2.0' | ||
832 | 43 | |||
833 | 44 | # First, if the version is lower than the threshold for using links, | ||
834 | 45 | # the result is false (i.e., links should not be used). | ||
835 | 46 | self.assertEqual(is_total_size_link_active('1.0', FakeConfig), False) | ||
836 | 47 | |||
837 | 48 | # However, if the requested version is equal to, or higher than the | ||
838 | 49 | # threshold, the result is true (i.e., links should be used). | ||
839 | 50 | self.assertEqual(is_total_size_link_active('2.0', FakeConfig), True) | ||
840 | 51 | self.assertEqual(is_total_size_link_active('3.0', FakeConfig), True) | ||
841 | 52 | |||
842 | 31 | # For the sake of convenience, test_get_current_web_service_request() | 53 | # For the sake of convenience, test_get_current_web_service_request() |
843 | 32 | # and tag_request_with_version_name() are tested in test_webservice.py. | 54 | # and tag_request_with_version_name() are tested in test_webservice.py. |
844 | 33 | 55 | ||
845 | 34 | 56 | ||
846 | === modified file 'src/lazr/restful/tests/test_webservice.py' | |||
847 | --- src/lazr/restful/tests/test_webservice.py 2010-03-03 16:33:21 +0000 | |||
848 | +++ src/lazr/restful/tests/test_webservice.py 2010-08-10 12:24:46 +0000 | |||
849 | @@ -9,20 +9,22 @@ | |||
850 | 9 | import unittest | 9 | import unittest |
851 | 10 | 10 | ||
852 | 11 | from zope.component import getGlobalSiteManager, getUtility | 11 | from zope.component import getGlobalSiteManager, getUtility |
854 | 12 | from zope.interface import implements, Interface | 12 | from zope.interface import implements, Interface, directlyProvides |
855 | 13 | from zope.publisher.browser import TestRequest | 13 | from zope.publisher.browser import TestRequest |
856 | 14 | from zope.schema import Date, Datetime, TextLine | 14 | from zope.schema import Date, Datetime, TextLine |
857 | 15 | from zope.security.management import ( | 15 | from zope.security.management import ( |
858 | 16 | endInteraction, newInteraction, queryInteraction) | 16 | endInteraction, newInteraction, queryInteraction) |
859 | 17 | from zope.traversing.browser.interfaces import IAbsoluteURL | 17 | from zope.traversing.browser.interfaces import IAbsoluteURL |
860 | 18 | 18 | ||
861 | 19 | from lazr.restful import ResourceOperation | ||
862 | 19 | from lazr.restful.fields import Reference | 20 | from lazr.restful.fields import Reference |
863 | 20 | from lazr.restful.interfaces import ( | 21 | from lazr.restful.interfaces import ( |
864 | 21 | ICollection, IEntry, IEntryResource, IResourceGETOperation, | 22 | ICollection, IEntry, IEntryResource, IResourceGETOperation, |
865 | 22 | IServiceRootResource, IWebServiceConfiguration, | 23 | IServiceRootResource, IWebServiceConfiguration, |
866 | 23 | IWebServiceClientRequest, IWebServiceVersion) | 24 | IWebServiceClientRequest, IWebServiceVersion) |
867 | 24 | from lazr.restful import EntryResource, ResourceGETOperation | 25 | from lazr.restful import EntryResource, ResourceGETOperation |
869 | 25 | from lazr.restful.declarations import exported, export_as_webservice_entry | 26 | from lazr.restful.declarations import (exported, export_as_webservice_entry, |
870 | 27 | LAZR_WEBSERVICE_NAME) | ||
871 | 26 | from lazr.restful.testing.webservice import ( | 28 | from lazr.restful.testing.webservice import ( |
872 | 27 | create_web_service_request, IGenericCollection, IGenericEntry, | 29 | create_web_service_request, IGenericCollection, IGenericEntry, |
873 | 28 | WebServiceTestCase, WebServiceTestPublication) | 30 | WebServiceTestCase, WebServiceTestPublication) |
874 | @@ -30,6 +32,7 @@ | |||
875 | 30 | from lazr.restful.utils import ( | 32 | from lazr.restful.utils import ( |
876 | 31 | get_current_browser_request, get_current_web_service_request, | 33 | get_current_browser_request, get_current_web_service_request, |
877 | 32 | tag_request_with_version_name) | 34 | tag_request_with_version_name) |
878 | 35 | from lazr.restful._resource import CollectionResource, BatchingResourceMixin | ||
879 | 33 | 36 | ||
880 | 34 | 37 | ||
881 | 35 | def get_resource_factory(model_interface, resource_interface): | 38 | def get_resource_factory(model_interface, resource_interface): |
882 | @@ -350,5 +353,78 @@ | |||
883 | 350 | self.assertEquals("2.0", webservice_request.version) | 353 | self.assertEquals("2.0", webservice_request.version) |
884 | 351 | self.assertTrue(marker_20.providedBy(webservice_request)) | 354 | self.assertTrue(marker_20.providedBy(webservice_request)) |
885 | 352 | 355 | ||
886 | 356 | |||
887 | 353 | def additional_tests(): | 357 | def additional_tests(): |
888 | 354 | return unittest.TestLoader().loadTestsFromName(__name__) | 358 | return unittest.TestLoader().loadTestsFromName(__name__) |
889 | 359 | |||
890 | 360 | |||
891 | 361 | class ITestEntry(IEntry): | ||
892 | 362 | """Interface for a test entry.""" | ||
893 | 363 | export_as_webservice_entry() | ||
894 | 364 | |||
895 | 365 | |||
896 | 366 | class TestEntry: | ||
897 | 367 | implements(ITestEntry) | ||
898 | 368 | def __init__(self, context, request): | ||
899 | 369 | pass | ||
900 | 370 | |||
901 | 371 | |||
902 | 372 | class BaseBatchingTest: | ||
903 | 373 | """A base class which tests BatchingResourceMixin and subclasses.""" | ||
904 | 374 | |||
905 | 375 | testmodule_objects = [HasRestrictedField, IHasRestrictedField] | ||
906 | 376 | |||
907 | 377 | def setUp(self): | ||
908 | 378 | super(BaseBatchingTest, self).setUp() | ||
909 | 379 | # Register TestEntry as the IEntry implementation for ITestEntry. | ||
910 | 380 | getGlobalSiteManager().registerAdapter( | ||
911 | 381 | TestEntry, [ITestEntry, IWebServiceClientRequest], provided=IEntry) | ||
912 | 382 | # Is doing this by hand the right way? | ||
913 | 383 | ITestEntry.setTaggedValue( | ||
914 | 384 | LAZR_WEBSERVICE_NAME, | ||
915 | 385 | dict(singular='test_entity', plural='test_entities')) | ||
916 | 386 | |||
917 | 387 | |||
918 | 388 | def make_instance(self, entries, request): | ||
919 | 389 | raise NotImplementedError('You have to make your own instances.') | ||
920 | 390 | |||
921 | 391 | def test_getting_a_batch(self): | ||
922 | 392 | entries = [1, 2, 3] | ||
923 | 393 | request = create_web_service_request('/devel') | ||
924 | 394 | instance = self.make_instance(entries, request) | ||
925 | 395 | total_size = instance.get_total_size(entries) | ||
926 | 396 | self.assertEquals(total_size, '3') | ||
927 | 397 | |||
928 | 398 | |||
929 | 399 | class TestBatchingResourceMixin(BaseBatchingTest, WebServiceTestCase): | ||
930 | 400 | """Test that BatchingResourceMixin does batching correctly.""" | ||
931 | 401 | |||
932 | 402 | def make_instance(self, entries, request): | ||
933 | 403 | return BatchingResourceMixin() | ||
934 | 404 | |||
935 | 405 | |||
936 | 406 | class TestCollectionResourceBatching(BaseBatchingTest, WebServiceTestCase): | ||
937 | 407 | """Test that CollectionResource does batching correctly.""" | ||
938 | 408 | |||
939 | 409 | def make_instance(self, entries, request): | ||
940 | 410 | class Collection: | ||
941 | 411 | implements(ICollection) | ||
942 | 412 | entry_schema = ITestEntry | ||
943 | 413 | |||
944 | 414 | def __init__(self, entries): | ||
945 | 415 | self.entries = entries | ||
946 | 416 | |||
947 | 417 | def find(self): | ||
948 | 418 | return self.entries | ||
949 | 419 | |||
950 | 420 | return CollectionResource(Collection(entries), request) | ||
951 | 421 | |||
952 | 422 | |||
953 | 423 | class TestResourceOperationBatching(BaseBatchingTest, WebServiceTestCase): | ||
954 | 424 | """Test that ResourceOperation does batching correctly.""" | ||
955 | 425 | |||
956 | 426 | def make_instance(self, entries, request): | ||
957 | 427 | # constructor parameters are ignored | ||
958 | 428 | return ResourceOperation(None, request) | ||
959 | 429 | |||
960 | 430 | |||
961 | 355 | 431 | ||
962 | === modified file 'src/lazr/restful/utils.py' | |||
963 | --- src/lazr/restful/utils.py 2010-03-10 20:44:03 +0000 | |||
964 | +++ src/lazr/restful/utils.py 2010-08-10 12:24:46 +0000 | |||
965 | @@ -37,6 +37,18 @@ | |||
966 | 37 | missing = object() | 37 | missing = object() |
967 | 38 | 38 | ||
968 | 39 | 39 | ||
969 | 40 | def is_total_size_link_active(version, config): | ||
970 | 41 | versions = config.active_versions | ||
971 | 42 | total_size_link_version = config.first_version_with_total_size_link | ||
972 | 43 | # The version "None" is a special marker for the earliest version. | ||
973 | 44 | if total_size_link_version is None: | ||
974 | 45 | total_size_link_version = versions[0] | ||
975 | 46 | # If the version we're being asked about is equal to or later than the | ||
976 | 47 | # version in which we started exposing total_size_link, then we should | ||
977 | 48 | # return True, False otherwise. | ||
978 | 49 | return versions.index(total_size_link_version) <= versions.index(version) | ||
979 | 50 | |||
980 | 51 | |||
981 | 40 | class VersionedDict(object): | 52 | class VersionedDict(object): |
982 | 41 | """A stack of named dictionaries. | 53 | """A stack of named dictionaries. |
983 | 42 | 54 | ||
984 | 43 | 55 | ||
985 | === modified file 'src/lazr/restful/version.txt' | |||
986 | --- src/lazr/restful/version.txt 2010-08-05 14:01:44 +0000 | |||
987 | +++ src/lazr/restful/version.txt 2010-08-10 12:24:46 +0000 | |||
988 | @@ -1,1 +1,1 @@ | |||
990 | 1 | 0.10.0 | 1 | 0.11.0-dev |
991 | 2 | 2 | ||
992 | === modified file 'versions.cfg' | |||
993 | --- versions.cfg 2009-04-15 14:43:40 +0000 | |||
994 | +++ versions.cfg 2010-08-10 12:24:46 +0000 | |||
995 | @@ -1,172 +1,69 @@ | |||
996 | 1 | [buildout] | ||
997 | 2 | versions = versions | ||
998 | 3 | allow-picked-versions = false | ||
999 | 4 | use-dependency-links = false | ||
1000 | 5 | |||
1001 | 1 | [versions] | 6 | [versions] |
1124 | 2 | ClientForm = 0.2.9 | 7 | # Alphabetical, case-SENSITIVE, blank line after this comment |
1125 | 3 | RestrictedPython = 3.4.2 | 8 | |
1126 | 4 | ZConfig = 2.5.1 | 9 | Jinja2 = 2.5 |
1127 | 5 | ZODB3 = 3.8.1 | 10 | Pygments = 1.3.1 |
1128 | 6 | docutils = 0.4 | 11 | RestrictedPython = 3.5.1 |
1129 | 7 | jquery.javascript = 1.0.0 | 12 | Sphinx = 1.0.1 |
1130 | 8 | jquery.layer = 1.0.0 | 13 | ZConfig = 2.7.1 |
1131 | 9 | lxml = 1.3.6 | 14 | ZODB3 = 3.9.2 |
1132 | 10 | mechanize = 0.1.7b | 15 | docutils = 0.5 |
1133 | 11 | pytz = 2007k | 16 | epydoc = 3.0.1 |
1134 | 12 | setuptools = 0.6c9 | 17 | grokcore.component = 1.6 |
1135 | 13 | z3c.coverage = 1.1.2 | 18 | lazr.batchnavigator = 1.2.0 |
1136 | 14 | z3c.csvvocabulary = 1.0.0 | 19 | lazr.delegates = 1.2.0 |
1137 | 15 | z3c.etestbrowser = 1.0.4 | 20 | lazr.enum = 1.1.2 |
1138 | 16 | z3c.form = 1.9.0 | 21 | lazr.lifecycle = 1.0 |
1139 | 17 | z3c.formdemo = 1.5.3 | 22 | lazr.uri = 1.0.2 |
1140 | 18 | z3c.formjs = 0.4.0 | 23 | lxml = 2.2.7 |
1141 | 19 | z3c.formjsdemo = 0.3.1 | 24 | martian = 0.11 |
1142 | 20 | z3c.formui = 1.4.2 | 25 | pytz = 2010h |
1143 | 21 | z3c.i18n = 0.1.1 | 26 | setuptools = 0.6c11 |
1144 | 22 | z3c.layer = 0.2.3 | 27 | simplejson = 2.0.9 |
1145 | 23 | z3c.macro = 1.1.0 | 28 | transaction = 1.0.0 |
1146 | 24 | z3c.macroviewlet = 1.0.0 | 29 | van.testing = 2.0.1 |
1147 | 25 | z3c.menu = 0.2.0 | 30 | wsgi-intercept = 0.4 |
1148 | 26 | z3c.optionstorage = 1.0.4 | 31 | wsgiref = 0.1.2 |
1149 | 27 | z3c.pagelet = 1.0.2 | 32 | z3c.recipe.sphinxdoc = 0.0.8 |
1150 | 28 | z3c.rml = 0.7.3 | 33 | z3c.recipe.staticlxml = 0.7.1 |
1151 | 29 | z3c.skin.pagelet = 1.0.2 | 34 | z3c.recipe.tag = 0.2.0 |
1152 | 30 | z3c.template = 1.1.0 | 35 | zc.buildout = 1.4.3 |
1153 | 31 | z3c.testing = 0.2.0 | 36 | zc.lockfile = 1.0.0 |
1154 | 32 | z3c.traverser = 0.2.3 | 37 | zc.recipe.cmmi = 1.3.1 |
1155 | 33 | z3c.viewlet = 1.0.0 | 38 | zc.recipe.egg = 1.2.3b2 |
1156 | 34 | z3c.viewtemplate = 0.3.2 | 39 | zc.recipe.testrunner = 1.3.0 |
1157 | 35 | z3c.zrtresource = 1.0.1 | 40 | zdaemon = 2.0.4 |
1158 | 36 | zc.buildout = 1.1.1 | 41 | zope.annotation = 3.5.0 |
1159 | 37 | zc.catalog = 1.2.0 | 42 | zope.app.pagetemplate = 3.7.1 |
1160 | 38 | zc.datetimewidget = 0.5.2 | 43 | zope.browser = 1.2 |
1161 | 39 | zc.i18n = 0.5.2 | 44 | zope.cachedescriptors = 3.5.0 |
1162 | 40 | zc.recipe.egg = 1.0.0 | 45 | zope.component = 3.9.3 |
1163 | 41 | zc.recipe.filestorage = 1.0.0 | 46 | zope.configuration = 3.6.0 |
1164 | 42 | zc.recipe.testrunner = 1.0.0 | 47 | zope.contenttype = 3.5.0 |
1165 | 43 | zc.resourcelibrary = 1.0.1 | 48 | zope.copy = 3.5.0 |
1044 | 44 | zc.table = 0.6 | ||
1045 | 45 | zc.zope3recipes = 0.6.2 | ||
1046 | 46 | zdaemon = 2.0.2 | ||
1047 | 47 | zodbcode = 3.4.0 | ||
1048 | 48 | zope.annotation = 3.4.1 | ||
1049 | 49 | zope.app.annotation = 3.4.0 | ||
1050 | 50 | zope.app.apidoc = 3.4.3 | ||
1051 | 51 | zope.app.applicationcontrol = 3.4.3 | ||
1052 | 52 | zope.app.appsetup = 3.4.1 | ||
1053 | 53 | zope.app.authentication = 3.4.4 | ||
1054 | 54 | zope.app.basicskin = 3.4.0 | ||
1055 | 55 | zope.app.boston = 3.4.0 | ||
1056 | 56 | zope.app.broken = 3.4.0 | ||
1057 | 57 | zope.app.cache = 3.4.1 | ||
1058 | 58 | zope.app.catalog = 3.5.1 | ||
1059 | 59 | zope.app.component = 3.4.1 | ||
1060 | 60 | zope.app.container = 3.5.6 | ||
1061 | 61 | zope.app.content = 3.4.0 | ||
1062 | 62 | zope.app.dav = 3.4.1 | ||
1063 | 63 | zope.app.debug = 3.4.1 | ||
1064 | 64 | zope.app.debugskin = 3.4.0 | ||
1065 | 65 | zope.app.dependable = 3.4.0 | ||
1066 | 66 | zope.app.dtmlpage = 3.4.1 | ||
1067 | 67 | zope.app.error = 3.5.1 | ||
1068 | 68 | zope.app.exception = 3.4.1 | ||
1069 | 69 | zope.app.externaleditor = 3.4.0 | ||
1070 | 70 | zope.app.file = 3.4.4 | ||
1071 | 71 | zope.app.folder = 3.4.0 | ||
1072 | 72 | zope.app.form = 3.4.1 | ||
1073 | 73 | zope.app.ftp = 3.4.0 | ||
1074 | 74 | zope.app.generations = 3.4.1 | ||
1075 | 75 | zope.app.homefolder = 3.4.0 | ||
1076 | 76 | zope.app.http = 3.4.1 | ||
1077 | 77 | zope.app.i18n = 3.4.4 | ||
1078 | 78 | zope.app.i18nfile = 3.4.1 | ||
1079 | 79 | zope.app.interface = 3.4.0 | ||
1080 | 80 | zope.app.interpreter = 3.4.0 | ||
1081 | 81 | zope.app.intid = 3.4.1 | ||
1082 | 82 | zope.app.keyreference = 3.4.1 | ||
1083 | 83 | zope.app.layers = 3.4.0 | ||
1084 | 84 | zope.app.locales = 3.4.5 | ||
1085 | 85 | zope.app.locking = 3.4.0 | ||
1086 | 86 | zope.app.module = 3.4.0 | ||
1087 | 87 | zope.app.onlinehelp = 3.4.1 | ||
1088 | 88 | zope.app.pagetemplate = 3.4.1 | ||
1089 | 89 | zope.app.pluggableauth = 3.4.0 | ||
1090 | 90 | zope.app.preference = 3.4.1 | ||
1091 | 91 | zope.app.preview = 3.4.0 | ||
1092 | 92 | zope.app.principalannotation = 3.4.0 | ||
1093 | 93 | zope.app.publication = 3.4.3 | ||
1094 | 94 | zope.app.publisher = 3.4.1 | ||
1095 | 95 | zope.app.pythonpage = 3.4.1 | ||
1096 | 96 | zope.app.renderer = 3.4.0 | ||
1097 | 97 | zope.app.rotterdam = 3.4.1 | ||
1098 | 98 | zope.app.schema = 3.4.0 | ||
1099 | 99 | zope.app.security = 3.5.2 | ||
1100 | 100 | zope.app.securitypolicy = 3.4.6 | ||
1101 | 101 | zope.app.server = 3.4.2 | ||
1102 | 102 | zope.app.session = 3.5.1 | ||
1103 | 103 | zope.app.skins = 3.4.0 | ||
1104 | 104 | zope.app.sqlscript = 3.4.1 | ||
1105 | 105 | zope.app.testing = 3.4.3 | ||
1106 | 106 | zope.app.traversing = 3.4.0 | ||
1107 | 107 | zope.app.tree = 3.4.0 | ||
1108 | 108 | zope.app.twisted = 3.4.1 | ||
1109 | 109 | zope.app.undo = 3.4.0 | ||
1110 | 110 | zope.app.wfmc = 0.1.2 | ||
1111 | 111 | zope.app.workflow = 3.4.1 | ||
1112 | 112 | zope.app.wsgi = 3.4.1 | ||
1113 | 113 | zope.app.xmlrpcintrospection = 3.4.0 | ||
1114 | 114 | zope.app.zapi = 3.4.0 | ||
1115 | 115 | zope.app.zcmlfiles = 3.4.3 | ||
1116 | 116 | zope.app.zopeappgenerations = 3.4.0 | ||
1117 | 117 | zope.app.zptpage = 3.4.1 | ||
1118 | 118 | zope.cachedescriptors = 3.4.1 | ||
1119 | 119 | zope.component = 3.4.0 | ||
1120 | 120 | zope.configuration = 3.4.0 | ||
1121 | 121 | zope.contentprovider = 3.4.0 | ||
1122 | 122 | zope.contenttype = 3.4.0 | ||
1123 | 123 | zope.copypastemove = 3.4.0 | ||
1166 | 124 | zope.datetime = 3.4.0 | 49 | zope.datetime = 3.4.0 |
1204 | 125 | zope.decorator = 3.4.0 | 50 | zope.dublincore = 3.5.0 |
1205 | 126 | zope.deferredimport = 3.4.0 | 51 | zope.event = 3.4.1 |
1206 | 127 | zope.deprecation = 3.4.0 | 52 | zope.exceptions = 3.5.2 |
1207 | 128 | zope.documenttemplate = 3.4.0 | 53 | zope.hookable = 3.4.1 |
1208 | 129 | zope.dottedname = 3.4.2 | 54 | zope.i18n = 3.7.1 |
1209 | 130 | zope.dublincore = 3.4.0 | 55 | zope.i18nmessageid = 3.5.0 |
1210 | 131 | zope.error = 3.5.1 | 56 | zope.interface = 3.5.2 |
1211 | 132 | zope.event = 3.4.0 | 57 | zope.lifecycleevent = 3.5.2 |
1212 | 133 | zope.exceptions = 3.4.0 | 58 | zope.location = 3.7.0 |
1213 | 134 | zope.file = 0.3.0 | 59 | zope.pagetemplate = 3.5.0 |
1214 | 135 | zope.filerepresentation = 3.4.0 | 60 | zope.proxy = 3.5.0 |
1215 | 136 | zope.formlib = 3.4.0 | 61 | zope.publisher = 3.12.0 |
1216 | 137 | zope.hookable = 3.4.0 | 62 | zope.schema = 3.5.4 |
1217 | 138 | zope.html = 1.0.1 | 63 | zope.security = 3.7.1 |
1218 | 139 | zope.i18n = 3.4.0 | 64 | zope.size = 3.4.1 |
1219 | 140 | zope.i18nmessageid = 3.4.3 | 65 | zope.tal = 3.5.1 |
1183 | 141 | zope.index = 3.4.1 | ||
1184 | 142 | zope.interface = 3.4.1 | ||
1185 | 143 | zope.lifecycleevent = 3.4.0 | ||
1186 | 144 | zope.location = 3.4.0 | ||
1187 | 145 | zope.mimetype = 0.3.0 | ||
1188 | 146 | zope.minmax = 1.1.0 | ||
1189 | 147 | zope.modulealias = 3.4.0 | ||
1190 | 148 | zope.pagetemplate = 3.4.0 | ||
1191 | 149 | zope.proxy = 3.4.2 | ||
1192 | 150 | zope.publisher = 3.4.6 | ||
1193 | 151 | zope.rdb = 3.4.0 | ||
1194 | 152 | zope.schema = 3.4.0 | ||
1195 | 153 | zope.security = 3.4.1 | ||
1196 | 154 | zope.securitypolicy = 3.4.1 | ||
1197 | 155 | zope.sendmail = 3.4.0 | ||
1198 | 156 | zope.sequencesort = 3.4.0 | ||
1199 | 157 | zope.server = 3.4.3 | ||
1200 | 158 | zope.session = 3.4.1 | ||
1201 | 159 | zope.size = 3.4.0 | ||
1202 | 160 | zope.structuredtext = 3.4.0 | ||
1203 | 161 | zope.tal = 3.4.1 | ||
1220 | 162 | zope.tales = 3.4.0 | 66 | zope.tales = 3.4.0 |
1231 | 163 | zope.testbrowser = 3.4.2 | 67 | zope.testing = 3.9.4 |
1232 | 164 | zope.testing = 3.5.6 | 68 | zope.testrunner = 4.0.0b5 |
1233 | 165 | zope.testrecorder = 0.3.0 | 69 | zope.traversing = 3.8.0 |
1224 | 166 | zope.thread = 3.4 | ||
1225 | 167 | zope.traversing = 3.6.0 | ||
1226 | 168 | zope.ucol = 1.0.2 | ||
1227 | 169 | zope.viewlet = 3.4.2 | ||
1228 | 170 | zope.wfmc = 3.4.0 | ||
1229 | 171 | zope.xmlpickle = 3.4.0 | ||
1230 | 172 | van.testing = 2.0.1 |
The changes in http:// paste.ubuntu. com/475892/ look good; r=me.