Merge lp:~cjwatson/lazr.restful/py3-declarations into lp:lazr.restful
- py3-declarations
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 243 |
Proposed branch: | lp:~cjwatson/lazr.restful/py3-declarations |
Merge into: | lp:lazr.restful |
Diff against target: |
2275 lines (+681/-421) 11 files modified
NEWS.rst (+10/-0) src/lazr/restful/declarations.py (+205/-79) src/lazr/restful/docs/webservice-declarations.rst (+162/-214) src/lazr/restful/example/base/interfaces.py (+20/-12) src/lazr/restful/example/base_extended/comments.py (+6/-4) src/lazr/restful/example/multiversion/resources.py (+16/-8) src/lazr/restful/example/wsgi/README.txt (+4/-4) src/lazr/restful/example/wsgi/resources.py (+7/-5) src/lazr/restful/testing/webservice.py (+3/-3) src/lazr/restful/tests/test_declarations.py (+232/-79) src/lazr/restful/tests/test_webservice.py (+16/-13) |
To merge this branch: | bzr merge lp:~cjwatson/lazr.restful/py3-declarations |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+378548@code.launchpad.net |
Commit message
Deprecate class advice APIs and provide replacements.
Description of the change
lazr.restful.
I've run the Launchpad test suite on a branch converted to use the new decorators, and everything looks fine.
- 237. By Colin Watson
-
Add exported_
as_webservice_ {collection, entry} to __all__. - 238. By Colin Watson
-
Fix skipping of test classes with testtools.
testtools doesn't currently support skipping test classes the normal
unittest way; see https://github. com/testing- cabal/testtools /issues/ 205.
However, we can avoid having to apply @unittest.skipIf to every test case in
the class by calling skipTest in setUp instead. - 239. By Colin Watson
-
Merge trunk.
William Grant (wgrant) : | # |
Preview Diff
1 | === modified file 'NEWS.rst' |
2 | --- NEWS.rst 2020-02-19 15:03:01 +0000 |
3 | +++ NEWS.rst 2020-02-19 16:09:23 +0000 |
4 | @@ -2,6 +2,16 @@ |
5 | NEWS for lazr.restful |
6 | ===================== |
7 | |
8 | +0.22.0 |
9 | +====== |
10 | + |
11 | +Deprecate the "class advice" APIs from ``lazr.restful.declarations``: |
12 | +``export_as_webservice_entry`` and ``export_as_webservice_collection``. In |
13 | +their place, prefer the equivalent class decorators: |
14 | +``@exported_as_webservice_entry`` and |
15 | +``@exported_as_webservice_collection``. The functions based on class advice |
16 | +will not work on Python 3. |
17 | + |
18 | 0.21.1 (2020-02-19) |
19 | =================== |
20 | |
21 | |
22 | === modified file 'src/lazr/restful/declarations.py' |
23 | --- src/lazr/restful/declarations.py 2020-02-04 11:52:59 +0000 |
24 | +++ src/lazr/restful/declarations.py 2020-02-19 16:09:23 +0000 |
25 | @@ -19,7 +19,6 @@ |
26 | 'call_with', |
27 | 'collection_default_content', |
28 | 'error_status', |
29 | - 'exported', |
30 | 'export_as_webservice_collection', |
31 | 'export_as_webservice_entry', |
32 | 'export_destructor_operation', |
33 | @@ -27,6 +26,9 @@ |
34 | 'export_operation_as', |
35 | 'export_read_operation', |
36 | 'export_write_operation', |
37 | + 'exported', |
38 | + 'exported_as_webservice_collection', |
39 | + 'exported_as_webservice_entry', |
40 | 'generate_collection_adapter', |
41 | 'generate_entry_adapters', |
42 | 'generate_entry_interfaces', |
43 | @@ -159,6 +161,84 @@ |
44 | return f_locals.setdefault(TAGGED_DATA, {}) |
45 | |
46 | |
47 | +def _export_as_webservice_entry(interface, |
48 | + singular_name=None, plural_name=None, |
49 | + contributes_to=None, publish_web_link=True, |
50 | + as_of=None, versioned_annotations=None): |
51 | + """Tag an interface as exported on the web service as an entry. |
52 | + |
53 | + This is the core of export_as_webservice_entry and |
54 | + exported_as_webservice_entry. |
55 | + """ |
56 | + annotation_stack = VersionedDict() |
57 | + |
58 | + if singular_name is None: |
59 | + # By convention, interfaces are called IWord1[Word2...]. The default |
60 | + # behavior assumes this convention and yields a singular name of |
61 | + # "word1_word2". |
62 | + my_singular_name = camelcase_to_underscore_separated( |
63 | + interface.__name__[1:]) |
64 | + else: |
65 | + my_singular_name = singular_name |
66 | + |
67 | + # Turn the named arguments into a dictionary for the first exported |
68 | + # version. |
69 | + initial_version = dict( |
70 | + type=ENTRY_TYPE, singular_name=my_singular_name, |
71 | + plural_name=plural_name, contributes_to=contributes_to, |
72 | + publish_web_link=publish_web_link, exported=True, |
73 | + _as_of_was_used=(as_of is not None)) |
74 | + |
75 | + afterwards = versioned_annotations or [] |
76 | + for version, annotations in itertools.chain( |
77 | + [(as_of, initial_version)], afterwards): |
78 | + annotation_stack.push(version) |
79 | + |
80 | + for key, value in annotations.items(): |
81 | + if annotations != initial_version: |
82 | + # Make sure that the 'annotations' dict contains only |
83 | + # recognized annotations. |
84 | + if key not in ENTRY_ANNOTATION_KEYS: |
85 | + raise ValueError( |
86 | + 'Unrecognized annotation for version "%s": ' |
87 | + '"%s"' % (version, key)) |
88 | + annotation_stack[key] = value |
89 | + # If this version provides a singular name but not a plural name, |
90 | + # apply the default pluralization rule. |
91 | + if (annotations.get('singular_name') is not None |
92 | + and annotations.get('plural_name') is None): |
93 | + annotation_stack['plural_name'] = ( |
94 | + annotations['singular_name'] + 's') |
95 | + |
96 | + # When called from the @exported_as_webservice_entry decorator, this |
97 | + # would overwrite any interface annotations made by method decorators; |
98 | + # but there are currently no such annotations that are valid for methods |
99 | + # of entry interfaces anyway. |
100 | + interface.setTaggedValue(LAZR_WEBSERVICE_EXPORTED, annotation_stack) |
101 | + |
102 | + # Set the name of the fields that didn't specify it using the |
103 | + # 'export_as' parameter in exported(). This must be done here, because |
104 | + # the field's __name__ attribute is only set when the interface is |
105 | + # created. |
106 | + for name, field in getFields(interface).items(): |
107 | + tag_stack = field.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED) |
108 | + if tag_stack is None or tag_stack.is_empty: |
109 | + continue |
110 | + if tag_stack['type'] != FIELD_TYPE: |
111 | + continue |
112 | + for version, tags in tag_stack.stack: |
113 | + # Set 'as' for every version in which the field is published but |
114 | + # no 'as' is specified. Also set 'original_name' for every |
115 | + # version in which the field is published--this will help with |
116 | + # performance optimizations around permission checks. |
117 | + if tags.get('exported') != False: |
118 | + tags['original_name'] = name |
119 | + if tags.get('as') is None: |
120 | + tags['as'] = name |
121 | + |
122 | + annotate_exported_methods(interface) |
123 | + |
124 | + |
125 | def export_as_webservice_entry(singular_name=None, plural_name=None, |
126 | contributes_to=None, publish_web_link=True, |
127 | as_of=None, versioned_annotations=None): |
128 | @@ -168,6 +248,10 @@ |
129 | actually not be exported on its own but instead will contribute its |
130 | attributes/methods to other exported entries. |
131 | |
132 | + This function does not work with Python 3. Rather than calling this |
133 | + within the class definition, decorate the class with |
134 | + @exported_as_webservice_entry instead. |
135 | + |
136 | :param singular_name: The human-readable singular name of the entry, |
137 | eg. "paintbrush". |
138 | :param plural_name: The human-readable plural name of the entry, |
139 | @@ -192,78 +276,71 @@ |
140 | work just like the corresponding arguments to this method. |
141 | """ |
142 | _check_called_from_interface_def('export_as_webservice_entry()') |
143 | + |
144 | def mark_entry(interface): |
145 | """Class advisor that tags the interface once it is created.""" |
146 | _check_interface('export_as_webservice_entry()', interface) |
147 | - |
148 | - annotation_stack = VersionedDict() |
149 | - |
150 | - if singular_name is None: |
151 | - # By convention, interfaces are called IWord1[Word2...]. The |
152 | - # default behavior assumes this convention and yields a |
153 | - # singular name of "word1_word2". |
154 | - my_singular_name = camelcase_to_underscore_separated( |
155 | - interface.__name__[1:]) |
156 | - else: |
157 | - my_singular_name = singular_name |
158 | - |
159 | - # Turn the named arguments into a dictionary for the first |
160 | - # exported version. |
161 | - initial_version = dict( |
162 | - type=ENTRY_TYPE, singular_name=my_singular_name, |
163 | - plural_name=plural_name, contributes_to=contributes_to, |
164 | - publish_web_link=publish_web_link, exported=True, |
165 | - _as_of_was_used=(not as_of is None)) |
166 | - |
167 | - afterwards = versioned_annotations or [] |
168 | - for version, annotations in itertools.chain( |
169 | - [(as_of, initial_version)], afterwards): |
170 | - annotation_stack.push(version) |
171 | - |
172 | - for key, value in annotations.items(): |
173 | - if annotations != initial_version: |
174 | - # Make sure that the 'annotations' dict |
175 | - # contains only recognized annotations. |
176 | - if key not in ENTRY_ANNOTATION_KEYS: |
177 | - raise ValueError( |
178 | - 'Unrecognized annotation for version "%s": ' |
179 | - '"%s"' % (version, key)) |
180 | - annotation_stack[key] = value |
181 | - # If this version provides a singular name but not a |
182 | - # plural name, apply the default pluralization rule. |
183 | - if (annotations.get('singular_name') is not None |
184 | - and annotations.get('plural_name') is None): |
185 | - annotation_stack['plural_name'] = ( |
186 | - annotations['singular_name'] + 's') |
187 | - |
188 | - interface.setTaggedValue(LAZR_WEBSERVICE_EXPORTED, annotation_stack) |
189 | - |
190 | - # Set the name of the fields that didn't specify it using the |
191 | - # 'export_as' parameter in exported(). This must be done here, |
192 | - # because the field's __name__ attribute is only set when the |
193 | - # interface is created. |
194 | - for name, field in getFields(interface).items(): |
195 | - tag_stack = field.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED) |
196 | - if tag_stack is None or tag_stack.is_empty: |
197 | - continue |
198 | - if tag_stack['type'] != FIELD_TYPE: |
199 | - continue |
200 | - for version, tags in tag_stack.stack: |
201 | - # Set 'as' for every version in which the field is |
202 | - # published but no 'as' is specified. Also set |
203 | - # 'original_name' for every version in which the field |
204 | - # is published--this will help with performance |
205 | - # optimizations around permission checks. |
206 | - if tags.get('exported') != False: |
207 | - tags['original_name'] = name |
208 | - if tags.get('as') is None: |
209 | - tags['as'] = name |
210 | - |
211 | - annotate_exported_methods(interface) |
212 | + _export_as_webservice_entry( |
213 | + interface, singular_name=singular_name, plural_name=plural_name, |
214 | + contributes_to=contributes_to, publish_web_link=publish_web_link, |
215 | + as_of=as_of, versioned_annotations=versioned_annotations) |
216 | return interface |
217 | + |
218 | addClassAdvisor(mark_entry) |
219 | |
220 | |
221 | +class exported_as_webservice_entry: |
222 | + """Mark the content interface as exported on the web service as an entry. |
223 | + |
224 | + If contributes_to is a non-empty sequence of Interfaces, this entry will |
225 | + actually not be exported on its own but instead will contribute its |
226 | + attributes/methods to other exported entries. |
227 | + |
228 | + :param singular_name: The human-readable singular name of the entry, |
229 | + eg. "paintbrush". |
230 | + :param plural_name: The human-readable plural name of the entry, |
231 | + eg. "paintbrushes" |
232 | + :param contributes_to: An optional list of exported interfaces to which |
233 | + this interface contributes. |
234 | + :param publish_web_link: This parameter is ignored unless there is |
235 | + a correspondence between this web service's entries and the |
236 | + pages on some website. If that is so, and if this parameter is |
237 | + set to True, the representation of this entry will include a |
238 | + web_link pointing to the corresponding page on the website. If |
239 | + False, web_link will be omitted. |
240 | + :param as_of: The first version of the web service to feature this entry. |
241 | + :param versioned_annotations: A list of 2-tuples (version, |
242 | + {params}), with more recent web service versions earlier in |
243 | + the list and older versions later in the list. |
244 | + |
245 | + A 'params' dictionary may contain the key 'exported', which |
246 | + controls whether or not to publish the entry at all in the |
247 | + given version. It may also contain the keys 'singular_name', |
248 | + 'plural_name', 'contributes_to', or 'publish_web_link', which |
249 | + work just like the corresponding arguments to this method. |
250 | + """ |
251 | + |
252 | + def __init__(self, singular_name=None, plural_name=None, |
253 | + contributes_to=None, publish_web_link=True, |
254 | + as_of=None, versioned_annotations=None): |
255 | + self.singular_name = singular_name |
256 | + self.plural_name = plural_name |
257 | + self.contributes_to = contributes_to |
258 | + self.publish_web_link = publish_web_link |
259 | + self.as_of = as_of |
260 | + self.versioned_annotations = versioned_annotations |
261 | + |
262 | + def __call__(self, interface): |
263 | + _check_interface('exported_as_webservice_entry()', interface) |
264 | + _export_as_webservice_entry( |
265 | + interface, |
266 | + singular_name=self.singular_name, plural_name=self.plural_name, |
267 | + contributes_to=self.contributes_to, |
268 | + publish_web_link=self.publish_web_link, |
269 | + as_of=self.as_of, versioned_annotations=self.versioned_annotations) |
270 | + return interface |
271 | + |
272 | + |
273 | def exported(field, *versioned_annotations, **kwparams): |
274 | """Mark the field as part of the entry data model. |
275 | |
276 | @@ -356,9 +433,22 @@ |
277 | return field |
278 | |
279 | |
280 | +def _check_collection_default_content(name, interface): |
281 | + """Check that the interface has a method providing the default content.""" |
282 | + tag = interface.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED, {}) |
283 | + if 'collection_default_content' not in tag: |
284 | + raise TypeError( |
285 | + "%s is missing a method tagged with @collection_default_content." % |
286 | + name) |
287 | + |
288 | + |
289 | def export_as_webservice_collection(entry_schema): |
290 | """Mark the interface as exported on the web service as a collection. |
291 | |
292 | + This function does not work with Python 3. Rather than calling this |
293 | + within the class definition, decorate the class with |
294 | + @exported_as_webservice_collection instead. |
295 | + |
296 | :raises TypeError: if the interface doesn't have a method decorated with |
297 | @collection_default_content. |
298 | """ |
299 | @@ -376,24 +466,46 @@ |
300 | def mark_collection(interface): |
301 | """Class advisor that tags the interface once it is created.""" |
302 | _check_interface('export_as_webservice_collection()', interface) |
303 | - |
304 | - tag = interface.getTaggedValue(LAZR_WEBSERVICE_EXPORTED) |
305 | - if 'collection_default_content' not in tag: |
306 | - raise TypeError( |
307 | - "export_as_webservice_collection() is missing a method " |
308 | - "tagged with @collection_default_content.") |
309 | - |
310 | + _check_collection_default_content( |
311 | + 'export_as_webservice_collection()', interface) |
312 | annotate_exported_methods(interface) |
313 | return interface |
314 | |
315 | addClassAdvisor(mark_collection) |
316 | |
317 | |
318 | +class exported_as_webservice_collection: |
319 | + """Mark the interface as exported on the web service as a collection. |
320 | + |
321 | + :raises TypeError: if the interface doesn't have a method decorated with |
322 | + @collection_default_content. |
323 | + """ |
324 | + |
325 | + def __init__(self, entry_schema): |
326 | + if not IInterface.providedBy(entry_schema): |
327 | + raise TypeError("entry_schema must be an interface.") |
328 | + self.entry_schema = entry_schema |
329 | + |
330 | + def __call__(self, interface): |
331 | + _check_interface('exported_as_webservice_collection()', interface) |
332 | + _check_collection_default_content( |
333 | + 'exported_as_webservice_collection()', interface) |
334 | + tag = interface.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED) |
335 | + if tag is None: |
336 | + tag = {} |
337 | + interface.setTaggedValue(LAZR_WEBSERVICE_EXPORTED, tag) |
338 | + tag['type'] = COLLECTION_TYPE |
339 | + tag['collection_entry_schema'] = self.entry_schema |
340 | + annotate_exported_methods(interface) |
341 | + return interface |
342 | + |
343 | + |
344 | class collection_default_content: |
345 | """Decorates the method that provides the default values of a collection. |
346 | |
347 | - :raises TypeError: if not called from within an interface exported as a |
348 | - collection, or if used more than once in the same interface. |
349 | + :raises TypeError: if called from within an interface exported as |
350 | + something other than a collection, or if used more than once for the |
351 | + same version in the same interface. |
352 | """ |
353 | |
354 | def __init__(self, version=None, **params): |
355 | @@ -407,9 +519,20 @@ |
356 | """ |
357 | _check_called_from_interface_def('@collection_default_content') |
358 | |
359 | + # We used to check that this decorator is being used from within an |
360 | + # interface exported as a collection. However, it isn't possible to |
361 | + # do this when using the @exported_as_webservice_collection |
362 | + # decorator rather than the export_as_webservice_collection function |
363 | + # (which is based on class advice, and so cannot work in Python 3): |
364 | + # that decorator isn't called until the rest of the interface has |
365 | + # been defined, so it hasn't had a chance to set any tags on the |
366 | + # interface by the time decorators of methods in that interface are |
367 | + # called. The best we can do is to check that we don't already |
368 | + # positively know that the interface is exported as an entry |
369 | + # instead. |
370 | tags = _get_interface_tags() |
371 | - tag = tags.get(LAZR_WEBSERVICE_EXPORTED) |
372 | - if tag is None or tag['type'] != COLLECTION_TYPE: |
373 | + tag = tags.setdefault(LAZR_WEBSERVICE_EXPORTED, {}) |
374 | + if 'type' in tag and tag['type'] != COLLECTION_TYPE: |
375 | raise TypeError( |
376 | "@collection_default_content can only be used from within an " |
377 | "interface exported as a collection.") |
378 | @@ -494,8 +617,11 @@ |
379 | |
380 | The actual method will be wrapped in an IMethod specification once the |
381 | Interface is complete. So we save the annotations in an attribute of the |
382 | - method, and the class advisor invoked by export_as_webservice_entry() and |
383 | - export_as_webservice_collection() will do the final tagging. |
384 | + method, and either the class advisor invoked by |
385 | + export_as_webservice_entry() and export_as_webservice_collection() or |
386 | + the @exported_as_webservice_entry() and |
387 | + @exported_as_webservice_collection() decorators will do the final |
388 | + tagging. |
389 | """ |
390 | |
391 | def __call__(self, method): |
392 | |
393 | === modified file 'src/lazr/restful/docs/webservice-declarations.rst' |
394 | --- src/lazr/restful/docs/webservice-declarations.rst 2020-02-04 13:17:32 +0000 |
395 | +++ src/lazr/restful/docs/webservice-declarations.rst 2020-02-19 16:09:23 +0000 |
396 | @@ -20,7 +20,7 @@ |
397 | |
398 | Only entries are exported as data. You can mark that one of your content |
399 | interface is exported on the web service as an entry, by using the |
400 | -export_as_webservice_entry() declaration. |
401 | +exported_as_webservice_entry() declaration. |
402 | |
403 | You can mark the fields that should be part of the entry data model by |
404 | using the exported() wrapper. It takes an optional 'exported_as' parameter |
405 | @@ -34,10 +34,12 @@ |
406 | >>> from zope.interface import Interface |
407 | >>> from zope.schema import Text, TextLine, Float, List |
408 | >>> from lazr.restful.declarations import ( |
409 | - ... export_as_webservice_entry, exported) |
410 | - >>> class IBook(Interface): |
411 | + ... exported, |
412 | + ... exported_as_webservice_entry, |
413 | + ... ) |
414 | + >>> @exported_as_webservice_entry() |
415 | + ... class IBook(Interface): |
416 | ... """A simple book data model.""" |
417 | - ... export_as_webservice_entry() |
418 | ... |
419 | ... title = exported(TextLine(title=u'The book title')) |
420 | ... |
421 | @@ -94,8 +96,8 @@ |
422 | Only IField can be exported as entry fields. |
423 | |
424 | >>> from zope.interface import Attribute |
425 | - >>> class Foo(Interface): |
426 | - ... export_as_webservice_entry() |
427 | + >>> @exported_as_webservice_entry() |
428 | + ... class Foo(Interface): |
429 | ... not_a_field = exported(Attribute('A standard attribute')) |
430 | Traceback (most recent call last): |
431 | ... |
432 | @@ -104,8 +106,8 @@ |
433 | Object fields cannot be exported because they cause validation problems. |
434 | |
435 | >>> from zope.schema import Object |
436 | - >>> class UsesIObject(Interface): |
437 | - ... export_as_webservice_entry() |
438 | + >>> @exported_as_webservice_entry() |
439 | + ... class UsesIObject(Interface): |
440 | ... object = exported(Object(schema=Interface)) |
441 | Traceback (most recent call last): |
442 | TypeError: Object exported; use Reference instead. |
443 | @@ -114,28 +116,21 @@ |
444 | avoid the validation problems. |
445 | |
446 | >>> from lazr.restful.fields import Reference |
447 | - >>> class UsesIReference(Interface): |
448 | - ... export_as_webservice_entry() |
449 | + >>> @exported_as_webservice_entry() |
450 | + ... class UsesIReference(Interface): |
451 | ... object = exported(Reference(schema=Interface)) |
452 | |
453 | -In the same vein, export_as_webservice_entry() can only be used on |
454 | +In the same vein, exported_as_webservice_entry() can only be used on |
455 | Interface. |
456 | |
457 | - >>> class NotAnInterface(object): |
458 | - ... export_as_webservice_entry() |
459 | + >>> @exported_as_webservice_entry() |
460 | + ... class NotAnInterface(object): |
461 | + ... pass |
462 | Traceback (most recent call last): |
463 | ... |
464 | - TypeError: export_as_webservice_entry() can only be used on an |
465 | + TypeError: exported_as_webservice_entry() can only be used on an |
466 | interface. |
467 | |
468 | -And from within a class declaration. |
469 | - |
470 | - >>> export_as_webservice_entry() |
471 | - Traceback (most recent call last): |
472 | - ... |
473 | - TypeError: export_as_webservice_entry() can only be used from within |
474 | - an interface definition. |
475 | - |
476 | publish_web_link |
477 | ---------------- |
478 | |
479 | @@ -143,11 +138,11 @@ |
480 | lazr.restful will publish a web_link for each entry, pointing to the |
481 | corresponding website page. For a given entry type, you can suppress |
482 | this by passing in False for the `publish_web_link` argument to |
483 | -`export_as_webservice_entry`. |
484 | +`exported_as_webservice_entry`. |
485 | |
486 | >>> from zope.interface import Attribute |
487 | - >>> class INotOnTheWebsite(Interface): |
488 | - ... export_as_webservice_entry(publish_web_link=False) |
489 | + >>> @exported_as_webservice_entry(publish_web_link=False) |
490 | + ... class INotOnTheWebsite(Interface): |
491 | ... field = exported(TextLine(title=u"A field.")) |
492 | >>> print_export_tag(INotOnTheWebsite) |
493 | _as_of_was_used: False |
494 | @@ -169,24 +164,24 @@ |
495 | |
496 | >>> from zope.schema import Object |
497 | >>> from lazr.restful.fields import CollectionField |
498 | - >>> class IBookWithComments(IBook): |
499 | + >>> @exported_as_webservice_entry() |
500 | + ... class IBookWithComments(IBook): |
501 | ... """A book with some comments.""" |
502 | - ... export_as_webservice_entry() |
503 | ... |
504 | ... comments = exported(CollectionField( |
505 | ... value_type=Object(schema=ISimpleComment))) |
506 | |
507 | Top-level collections are different though, they are exported by using the |
508 | -export_as_webservice_collection() in the ``Set`` class. The method that returns |
509 | -all of the collection items must be tagged with @collection_default_content |
510 | -decorator. |
511 | +exported_as_webservice_collection() in the ``Set`` class. The method that |
512 | +returns all of the collection items must be tagged with |
513 | +@collection_default_content decorator. |
514 | |
515 | >>> from lazr.restful.declarations import ( |
516 | - ... export_as_webservice_collection, collection_default_content, |
517 | + ... exported_as_webservice_collection, collection_default_content, |
518 | ... REQUEST_USER) |
519 | - >>> class IBookSet(Interface): |
520 | + >>> @exported_as_webservice_collection(IBook) |
521 | + ... class IBookSet(Interface): |
522 | ... """Set of all the books in the system.""" |
523 | - ... export_as_webservice_collection(IBook) |
524 | ... |
525 | ... @collection_default_content() |
526 | ... def getAllBooks(): |
527 | @@ -197,9 +192,9 @@ |
528 | special REQUEST_USER marker that can be used to specify that this |
529 | parameter should contain the logged in user. |
530 | |
531 | - >>> class ICheckedOutBookSet(Interface): |
532 | + >>> @exported_as_webservice_collection(IBook) |
533 | + ... class ICheckedOutBookSet(Interface): |
534 | ... """Give access to the checked out books.""" |
535 | - ... export_as_webservice_collection(IBook) |
536 | ... |
537 | ... @collection_default_content(user=REQUEST_USER, title='') |
538 | ... def getByTitle(title, user): |
539 | @@ -227,15 +222,16 @@ |
540 | The entry schema for a collection must be provided and must be an |
541 | interface: |
542 | |
543 | - >>> class MissingEntrySchema(Interface): |
544 | - ... export_as_webservice_collection() |
545 | + >>> @exported_as_webservice_collection() |
546 | + ... class MissingEntrySchema(Interface): |
547 | + ... pass |
548 | Traceback (most recent call last): |
549 | ... |
550 | - TypeError: export_as_webservice_collection() takes exactly 1 |
551 | - argument (0 given) |
552 | + TypeError: __init__() takes exactly 2 arguments (1 given) |
553 | |
554 | - >>> class InvalidEntrySchema(Interface): |
555 | - ... export_as_webservice_collection("not an interface") |
556 | + >>> @exported_as_webservice_collection("not an interface") |
557 | + ... class InvalidEntrySchema(Interface): |
558 | + ... pass |
559 | Traceback (most recent call last): |
560 | ... |
561 | TypeError: entry_schema must be an interface. |
562 | @@ -246,17 +242,18 @@ |
563 | >>> class IDummyInterface(Interface): |
564 | ... pass |
565 | |
566 | - >>> class MissingDefaultContent(Interface): |
567 | - ... export_as_webservice_collection(IDummyInterface) |
568 | + >>> @exported_as_webservice_collection(IDummyInterface) |
569 | + ... class MissingDefaultContent(Interface): |
570 | + ... pass |
571 | Traceback (most recent call last): |
572 | ... |
573 | - TypeError: export_as_webservice_collection() is missing a method |
574 | + TypeError: exported_as_webservice_collection() is missing a method |
575 | tagged with @collection_default_content. |
576 | |
577 | As it is an error, to mark more than one method: |
578 | |
579 | - >>> class TwoDefaultContent(Interface): |
580 | - ... export_as_webservice_collection(IDummyInterface) |
581 | + >>> @exported_as_webservice_collection(IDummyInterface) |
582 | + ... class TwoDefaultContent(Interface): |
583 | ... @collection_default_content() |
584 | ... def getAll1(): |
585 | ... """A first getAll().""" |
586 | @@ -268,23 +265,16 @@ |
587 | TypeError: Only one method can be marked with |
588 | @collection_default_content for version '(earliest version)'. |
589 | |
590 | -export_as_webservice_collection() can only be used on Interface. |
591 | +exported_as_webservice_collection() can only be used on Interface. |
592 | |
593 | - >>> class NotAnInterface(object): |
594 | - ... export_as_webservice_collection(IDummyInterface) |
595 | + >>> @exported_as_webservice_collection(IDummyInterface) |
596 | + ... class NotAnInterface(object): |
597 | + ... pass |
598 | Traceback (most recent call last): |
599 | ... |
600 | - TypeError: export_as_webservice_collection() can only be used on an |
601 | + TypeError: exported_as_webservice_collection() can only be used on an |
602 | interface. |
603 | |
604 | -And from within a class declaration. |
605 | - |
606 | - >>> export_as_webservice_collection(IDummyInterface) |
607 | - Traceback (most recent call last): |
608 | - ... |
609 | - TypeError: export_as_webservice_collection() can only be used from |
610 | - within an interface definition. |
611 | - |
612 | collection_default_content() can only be used from within an Interface |
613 | declaration: |
614 | |
615 | @@ -295,17 +285,6 @@ |
616 | TypeError: @collection_default_content can only be used from within |
617 | an interface definition. |
618 | |
619 | -And the interface must have been exported as a collection: |
620 | - |
621 | - >>> class NotExported(Interface): |
622 | - ... export_as_webservice_entry() |
623 | - ... @collection_default_content() |
624 | - ... def a_function(): pass |
625 | - Traceback (most recent call last): |
626 | - ... |
627 | - TypeError: @collection_default_content can only be used from within an |
628 | - interface exported as a collection. |
629 | - |
630 | Exporting methods |
631 | ================= |
632 | |
633 | @@ -361,9 +340,9 @@ |
634 | ... operation_returns_entry, operation_returns_collection_of, |
635 | ... rename_parameters_as) |
636 | >>> from lazr.restful.interface import copy_field |
637 | - >>> class IBookSetOnSteroids(IBookSet): |
638 | + >>> @exported_as_webservice_collection(IBook) |
639 | + ... class IBookSetOnSteroids(IBookSet): |
640 | ... """IBookSet supporting some methods.""" |
641 | - ... export_as_webservice_collection(IBook) |
642 | ... |
643 | ... @collection_default_content() |
644 | ... @operation_parameters( |
645 | @@ -405,9 +384,9 @@ |
646 | >>> from lazr.restful.declarations import ( |
647 | ... call_with, export_destructor_operation, export_write_operation, |
648 | ... REQUEST_USER) |
649 | - >>> class IBookOnSteroids(IBook): |
650 | + >>> @exported_as_webservice_entry() |
651 | + ... class IBookOnSteroids(IBook): |
652 | ... """IBook with some methods.""" |
653 | - ... export_as_webservice_entry() |
654 | ... |
655 | ... @call_with(who=REQUEST_USER, kind='normal') |
656 | ... @export_write_operation() |
657 | @@ -474,9 +453,8 @@ |
658 | @export_factory_operation to specify parameters that are not part of the |
659 | schema. |
660 | |
661 | - >>> class ComplexBookFactory(Interface): |
662 | - ... export_as_webservice_entry() |
663 | - ... |
664 | + >>> @exported_as_webservice_entry() |
665 | + ... class ComplexBookFactory(Interface): |
666 | ... @operation_parameters(collection=TextLine()) |
667 | ... @export_factory_operation(IBook, ['author', 'title']) |
668 | ... def create_book(author, title, collection): |
669 | @@ -498,9 +476,8 @@ |
670 | Parameters default and required attributes are set automatically based |
671 | on the method signature. |
672 | |
673 | - >>> class ComplexParameterDefinition(Interface): |
674 | - ... export_as_webservice_entry() |
675 | - ... |
676 | + >>> @exported_as_webservice_entry() |
677 | + ... class ComplexParameterDefinition(Interface): |
678 | ... @operation_parameters( |
679 | ... required1=TextLine(), |
680 | ... required2=TextLine(default=u'Not required'), |
681 | @@ -568,8 +545,8 @@ |
682 | An error is also reported if not enough parameters are defined as |
683 | exported: |
684 | |
685 | - >>> class MissingParameter(Interface): |
686 | - ... export_as_webservice_entry() |
687 | + >>> @exported_as_webservice_entry() |
688 | + ... class MissingParameter(Interface): |
689 | ... @call_with(param1=1) |
690 | ... @operation_parameters( |
691 | ... param2=TextLine()) |
692 | @@ -583,8 +560,8 @@ |
693 | Defining a parameter not available on the method also results in an |
694 | error: |
695 | |
696 | - >>> class BadParameter(Interface): |
697 | - ... export_as_webservice_entry() |
698 | + >>> @exported_as_webservice_entry() |
699 | + ... class BadParameter(Interface): |
700 | ... @operation_parameters( |
701 | ... no_such_param=TextLine()) |
702 | ... @export_read_operation() |
703 | @@ -597,8 +574,8 @@ |
704 | But that's not a problem if the exported method actually takes arbitrary |
705 | keyword parameters: |
706 | |
707 | - >>> class AnyParameter(Interface): |
708 | - ... export_as_webservice_entry() |
709 | + >>> @exported_as_webservice_entry() |
710 | + ... class AnyParameter(Interface): |
711 | ... @operation_parameters( |
712 | ... param1=TextLine()) |
713 | ... @export_read_operation() |
714 | @@ -607,8 +584,8 @@ |
715 | When using @export_factory_operation, TypeError will also be raised if |
716 | one of the field doesn't exists in the schema: |
717 | |
718 | - >>> class MissingParameter(Interface): |
719 | - ... export_as_webservice_entry() |
720 | + >>> @exported_as_webservice_entry() |
721 | + ... class MissingParameter(Interface): |
722 | ... @export_factory_operation(IBook, ['no_such_field']) |
723 | ... def a_method(): pass |
724 | Traceback (most recent call last): |
725 | @@ -617,8 +594,8 @@ |
726 | |
727 | Or if the field name doesn't represent a field: |
728 | |
729 | - >>> class NotAField(Interface): |
730 | - ... export_as_webservice_entry() |
731 | + >>> @exported_as_webservice_entry() |
732 | + ... class NotAField(Interface): |
733 | ... @export_factory_operation(IBookOnSteroids, ['checkout']) |
734 | ... def a_method(): pass |
735 | Traceback (most recent call last): |
736 | @@ -627,8 +604,8 @@ |
737 | |
738 | Or if @operation_parameters redefine a field specified in the factory: |
739 | |
740 | - >>> class Redefinition(Interface): |
741 | - ... export_as_webservice_entry() |
742 | + >>> @exported_as_webservice_entry() |
743 | + ... class Redefinition(Interface): |
744 | ... @operation_parameters(title=TextLine()) |
745 | ... @export_factory_operation(IBookOnSteroids, ['title']) |
746 | ... def create_book(title): pass |
747 | @@ -638,8 +615,8 @@ |
748 | |
749 | All parameters definitions must be schema fields: |
750 | |
751 | - >>> class BadParameterDefinition(Interface): |
752 | - ... export_as_webservice_entry() |
753 | + >>> @exported_as_webservice_entry() |
754 | + ... class BadParameterDefinition(Interface): |
755 | ... @operation_parameters(a_param=object()) |
756 | ... @export_read_operation() |
757 | ... def a_method(): pass |
758 | @@ -673,8 +650,8 @@ |
759 | @operation_returns_collection_of will only accept an IInterface as |
760 | argument. |
761 | |
762 | - >>> class ReturnOtherThanInterface(Interface): |
763 | - ... export_as_webservice_entry() |
764 | + >>> @exported_as_webservice_entry() |
765 | + ... class ReturnOtherThanInterface(Interface): |
766 | ... @operation_returns_entry("not-an-interface") |
767 | ... @export_read_operation() |
768 | ... def a_method(**kwargs): pass |
769 | @@ -682,8 +659,8 @@ |
770 | ... |
771 | TypeError: Entry type not-an-interface does not provide IInterface. |
772 | |
773 | - >>> class ReturnOtherThanInterface(Interface): |
774 | - ... export_as_webservice_entry() |
775 | + >>> @exported_as_webservice_entry() |
776 | + ... class ReturnOtherThanInterface(Interface): |
777 | ... @operation_returns_collection_of("not-an-interface") |
778 | ... @export_read_operation() |
779 | ... def a_method(**kwargs): pass |
780 | @@ -780,9 +757,8 @@ |
781 | ... def rename(new_name): |
782 | ... """Rename the object.""" |
783 | |
784 | - >>> class IUser(IHasName): |
785 | - ... export_as_webservice_entry() |
786 | - ... |
787 | + >>> @exported_as_webservice_entry() |
788 | + ... class IUser(IHasName): |
789 | ... nickname = exported(TextLine()) |
790 | ... |
791 | ... @operation_parameters(to=Object(IHasName), msg=TextLine()) |
792 | @@ -824,9 +800,8 @@ |
793 | adapters for your entry's class instead of to the class itself. For example, |
794 | we can have an IDeveloper interface contributing to IUser. |
795 | |
796 | - >>> class IDeveloper(Interface): |
797 | - ... export_as_webservice_entry(contributes_to=[IUser]) |
798 | - ... |
799 | + >>> @exported_as_webservice_entry(contributes_to=[IUser]) |
800 | + ... class IDeveloper(Interface): |
801 | ... programming_languages = exported(List( |
802 | ... title=u'Programming Languages spoken by this developer')) |
803 | |
804 | @@ -1359,8 +1334,8 @@ |
805 | A method can be designated as a destructor for the entry. Here, the |
806 | destroy() method is designated as the destructor for IHasText. |
807 | |
808 | - >>> class IHasText(Interface): |
809 | - ... export_as_webservice_entry() |
810 | + >>> @exported_as_webservice_entry() |
811 | + ... class IHasText(Interface): |
812 | ... text = exported(TextLine(readonly=True)) |
813 | ... |
814 | ... @export_destructor_operation() |
815 | @@ -1370,8 +1345,8 @@ |
816 | |
817 | A destructor method cannot take any free arguments. |
818 | |
819 | - >>> class IHasText(Interface): |
820 | - ... export_as_webservice_entry() |
821 | + >>> @exported_as_webservice_entry() |
822 | + ... class IHasText(Interface): |
823 | ... text = exported(TextLine(readonly=True)) |
824 | ... |
825 | ... @export_destructor_operation() |
826 | @@ -1384,8 +1359,8 @@ |
827 | In version (earliest version), the "destroy" method takes 1: |
828 | "argument". |
829 | |
830 | - >>> class IHasText(Interface): |
831 | - ... export_as_webservice_entry() |
832 | + >>> @exported_as_webservice_entry() |
833 | + ... class IHasText(Interface): |
834 | ... text = exported(TextLine(readonly=True)) |
835 | ... |
836 | ... @export_destructor_operation() |
837 | @@ -1397,8 +1372,8 @@ |
838 | An entry cannot have more than one destructor. |
839 | |
840 | >>> from lazr.restful.declarations import export_destructor_operation |
841 | - >>> class IHasText(Interface): |
842 | - ... export_as_webservice_entry() |
843 | + >>> @exported_as_webservice_entry() |
844 | + ... class IHasText(Interface): |
845 | ... text = exported(TextLine(readonly=True)) |
846 | ... |
847 | ... @export_destructor_operation() |
848 | @@ -1421,8 +1396,8 @@ |
849 | set_text() method is designated as the mutator for the 'text' field. |
850 | |
851 | >>> from lazr.restful.declarations import mutator_for |
852 | - >>> class IHasText(Interface): |
853 | - ... export_as_webservice_entry() |
854 | + >>> @exported_as_webservice_entry() |
855 | + ... class IHasText(Interface): |
856 | ... text = exported(TextLine(readonly=True)) |
857 | ... |
858 | ... @mutator_for(text) |
859 | @@ -1473,8 +1448,8 @@ |
860 | |
861 | It's not necessary to expose the mutator method as a write operation. |
862 | |
863 | - >>> class IHasText(Interface): |
864 | - ... export_as_webservice_entry() |
865 | + >>> @exported_as_webservice_entry() |
866 | + ... class IHasText(Interface): |
867 | ... text = exported(TextLine(readonly=True)) |
868 | ... |
869 | ... @mutator_for(text) |
870 | @@ -1485,8 +1460,8 @@ |
871 | A mutator method must take only one argument: the new value for the |
872 | field. Taking no arguments is obviously an error. |
873 | |
874 | - >>> class ZeroArgumentMutator(Interface): |
875 | - ... export_as_webservice_entry() |
876 | + >>> @exported_as_webservice_entry() |
877 | + ... class ZeroArgumentMutator(Interface): |
878 | ... value = exported(TextLine(readonly=True)) |
879 | ... |
880 | ... @mutator_for(value) |
881 | @@ -1499,8 +1474,8 @@ |
882 | |
883 | Taking more than one argument is also an error... |
884 | |
885 | - >>> class TwoArgumentMutator(Interface): |
886 | - ... export_as_webservice_entry() |
887 | + >>> @exported_as_webservice_entry() |
888 | + ... class TwoArgumentMutator(Interface): |
889 | ... value = exported(TextLine(readonly=True)) |
890 | ... |
891 | ... @mutator_for(value) |
892 | @@ -1514,8 +1489,8 @@ |
893 | ...unless all but one of the arguments are spoken for by a call_with() |
894 | annotation. This definition does not result in a TypeError. |
895 | |
896 | - >>> class OneFixedArgumentMutator(Interface): |
897 | - ... export_as_webservice_entry() |
898 | + >>> @exported_as_webservice_entry() |
899 | + ... class OneFixedArgumentMutator(Interface): |
900 | ... value = exported(TextLine(readonly=True)) |
901 | ... |
902 | ... @mutator_for(value) |
903 | @@ -1528,8 +1503,8 @@ |
904 | A field can only have a mutator if it's read-only (not settable |
905 | directly). |
906 | |
907 | - >>> class WritableMutator(Interface): |
908 | - ... export_as_webservice_entry() |
909 | + >>> @exported_as_webservice_entry() |
910 | + ... class WritableMutator(Interface): |
911 | ... value = exported(TextLine(readonly=False)) |
912 | ... |
913 | ... @mutator_for(value) |
914 | @@ -1542,8 +1517,8 @@ |
915 | |
916 | A field can only have one mutator. |
917 | |
918 | - >>> class FieldWithTwoMutators(Interface): |
919 | - ... export_as_webservice_entry() |
920 | + >>> @exported_as_webservice_entry() |
921 | + ... class FieldWithTwoMutators(Interface): |
922 | ... value = exported(TextLine(readonly=True)) |
923 | ... |
924 | ... @mutator_for(value) |
925 | @@ -1567,8 +1542,8 @@ |
926 | |
927 | A read-write field can be published as read-only in the web service. |
928 | |
929 | - >>> class ExternallyReadOnlyField(Interface): |
930 | - ... export_as_webservice_entry() |
931 | + >>> @exported_as_webservice_entry() |
932 | + ... class ExternallyReadOnlyField(Interface): |
933 | ... value = exported(TextLine(readonly=False), readonly=True) |
934 | |
935 | >>> interfaces = generate_entry_interfaces( |
936 | @@ -1584,8 +1559,8 @@ |
937 | just by declaring it read-write. You have to provide a |
938 | mutator. |
939 | |
940 | - >>> class InternallyReadOnlyField(Interface): |
941 | - ... export_as_webservice_entry() |
942 | + >>> @exported_as_webservice_entry() |
943 | + ... class InternallyReadOnlyField(Interface): |
944 | ... value = exported(TextLine(readonly=True), readonly=False) |
945 | |
946 | >>> generate_entry_interfaces(InternallyReadOnlyField, [], 'beta') |
947 | @@ -1602,10 +1577,9 @@ |
948 | the @cache_for decorator: |
949 | |
950 | >>> from lazr.restful.declarations import cache_for |
951 | - >>> |
952 | - >>> class ICachedBookSet(IBookSet): |
953 | + >>> @exported_as_webservice_collection(IBook) |
954 | + ... class ICachedBookSet(IBookSet): |
955 | ... """IBookSet supporting caching.""" |
956 | - ... export_as_webservice_collection(IBook) |
957 | ... |
958 | ... @collection_default_content() |
959 | ... @export_read_operation() |
960 | @@ -1667,9 +1641,8 @@ |
961 | |
962 | >>> from lazr.restful.declarations import generate_operation_adapter |
963 | |
964 | - >>> class IMultiVersionCollection(Interface): |
965 | - ... export_as_webservice_collection(Interface) |
966 | - ... |
967 | + >>> @exported_as_webservice_collection(Interface) |
968 | + ... class IMultiVersionCollection(Interface): |
969 | ... @collection_default_content('2.0') |
970 | ... def content_20(): |
971 | ... """The content method for version 2.0.""" |
972 | @@ -1768,9 +1741,8 @@ |
973 | 'new_in_10' in '1.0', and renamed to 'renamed_in_30' in '3.0'. |
974 | |
975 | >>> from zope.schema import Text, Float |
976 | - >>> class IMultiVersionEntry(Interface): |
977 | - ... export_as_webservice_entry() |
978 | - ... |
979 | + >>> @exported_as_webservice_entry() |
980 | + ... class IMultiVersionEntry(Interface): |
981 | ... field = exported(TextLine()) |
982 | ... |
983 | ... field2 = exported(Text(), exported_as='unchanging_name') |
984 | @@ -1943,9 +1915,8 @@ |
985 | Why does generate_entry_interfaces need a list of version strings? |
986 | This example should make it clear. |
987 | |
988 | - >>> class IAmbiguousMultiVersion(Interface): |
989 | - ... export_as_webservice_entry() |
990 | - ... |
991 | + >>> @exported_as_webservice_entry() |
992 | + ... class IAmbiguousMultiVersion(Interface): |
993 | ... field1 = exported(TextLine(), |
994 | ... ('foo', dict(exported_as='foo_name'))) |
995 | ... field2 = exported(TextLine(), |
996 | @@ -1975,9 +1946,8 @@ |
997 | generate_entry_interfaces() modified the class to reflect the original |
998 | list of versions.) |
999 | |
1000 | - >>> class IAmbiguousMultiVersion(Interface): |
1001 | - ... export_as_webservice_entry() |
1002 | - ... |
1003 | + >>> @exported_as_webservice_entry() |
1004 | + ... class IAmbiguousMultiVersion(Interface): |
1005 | ... field1 = exported(TextLine(), |
1006 | ... ('foo', dict(exported_as='foo_name'))) |
1007 | ... field2 = exported(TextLine(), |
1008 | @@ -2009,9 +1979,8 @@ |
1009 | You'll get an error if you annotate a field with a version that turns |
1010 | out not to be included in the version list. |
1011 | |
1012 | - >>> class INonexistentVersionEntry(Interface): |
1013 | - ... export_as_webservice_entry() |
1014 | - ... |
1015 | + >>> @exported_as_webservice_entry() |
1016 | + ... class INonexistentVersionEntry(Interface): |
1017 | ... field = exported(TextLine(), |
1018 | ... ('2.0', dict(exported_as='foo')), |
1019 | ... ('1.0', dict(exported_as='bar'))) |
1020 | @@ -2026,9 +1995,8 @@ |
1021 | You'll get an error if you put an earlier version's annotations on top |
1022 | of a later version. |
1023 | |
1024 | - >>> class IWrongOrderEntry(Interface): |
1025 | - ... export_as_webservice_entry() |
1026 | - ... |
1027 | + >>> @exported_as_webservice_entry() |
1028 | + ... class IWrongOrderEntry(Interface): |
1029 | ... field = exported(TextLine(), |
1030 | ... ('1.0', dict(exported_as='bar')), |
1031 | ... ('2.0', dict(exported_as='foo'))) |
1032 | @@ -2042,9 +2010,8 @@ |
1033 | You'll get an error if you define annotations twice for the same |
1034 | version. This can happen because you repeated the version annotations: |
1035 | |
1036 | - >>> class IDuplicateEntry(Interface): |
1037 | - ... export_as_webservice_entry() |
1038 | - ... |
1039 | + >>> @exported_as_webservice_entry() |
1040 | + ... class IDuplicateEntry(Interface): |
1041 | ... field = exported(TextLine(), |
1042 | ... ('beta', dict(exported_as='another_beta_name')), |
1043 | ... ('beta', dict(exported_as='beta_name'))) |
1044 | @@ -2059,9 +2026,8 @@ |
1045 | using keyword arguments, and then explicitly defined conflicting |
1046 | values. |
1047 | |
1048 | - >>> class IDuplicateEntry(Interface): |
1049 | - ... export_as_webservice_entry() |
1050 | - ... |
1051 | + >>> @exported_as_webservice_entry() |
1052 | + ... class IDuplicateEntry(Interface): |
1053 | ... field = exported(TextLine(), |
1054 | ... ('beta', dict(exported_as='beta_name')), |
1055 | ... exported_as='earliest_name') |
1056 | @@ -2077,18 +2043,16 @@ |
1057 | You'll get an error if you include an unrecognized key in a field's |
1058 | version definition. |
1059 | |
1060 | - >>> class InvalidMultiVersionEntry(Interface): |
1061 | - ... export_as_webservice_entry() |
1062 | - ... |
1063 | + >>> @exported_as_webservice_entry() |
1064 | + ... class InvalidMultiVersionEntry(Interface): |
1065 | ... field = exported(TextLine(), |
1066 | ... ('3.0', dict(not_recognized='this will error'))) |
1067 | Traceback (most recent call last): |
1068 | ... |
1069 | ValueError: Unrecognized annotation for version "3.0": "not_recognized" |
1070 | |
1071 | - >>> class InvalidMultiVersionEntry(Interface): |
1072 | - ... export_as_webservice_entry() |
1073 | - ... |
1074 | + >>> @exported_as_webservice_entry() |
1075 | + ... class InvalidMultiVersionEntry(Interface): |
1076 | ... field = exported(TextLine(), not_recognized='this will error') |
1077 | Traceback (most recent call last): |
1078 | ... |
1079 | @@ -2098,9 +2062,8 @@ |
1080 | every version, even when an interface does not change at all between |
1081 | versions. (This could be optimized away.) |
1082 | |
1083 | - >>> class IUnchangingEntry(Interface): |
1084 | - ... export_as_webservice_entry() |
1085 | - ... |
1086 | + >>> @exported_as_webservice_entry() |
1087 | + ... class IUnchangingEntry(Interface): |
1088 | ... field = exported(TextLine(), |
1089 | ... ('3.0', dict(exported_as='30_name')), |
1090 | ... ('beta', dict(exported_as='unchanging_name'))) |
1091 | @@ -2118,10 +2081,8 @@ |
1092 | 2.0, 1.0, and in an unnamed pre-1.0 version. |
1093 | |
1094 | >>> from lazr.restful.declarations import operation_for_version |
1095 | - >>> class IMultiVersionMethod(Interface): |
1096 | - ... export_as_webservice_entry() |
1097 | - ... |
1098 | - ... |
1099 | + >>> @exported_as_webservice_entry() |
1100 | + ... class IMultiVersionMethod(Interface): |
1101 | ... @cache_for(300) |
1102 | ... @operation_for_version('3.0') |
1103 | ... |
1104 | @@ -2298,8 +2259,8 @@ |
1105 | Let's define an operation that's introduced in 1.0 and removed in 2.0. |
1106 | |
1107 | >>> from lazr.restful.declarations import operation_removed_in_version |
1108 | - >>> class DisappearingMultiversionMethod(Interface): |
1109 | - ... export_as_webservice_entry() |
1110 | + >>> @exported_as_webservice_entry() |
1111 | + ... class DisappearingMultiversionMethod(Interface): |
1112 | ... @operation_removed_in_version(2.0) |
1113 | ... @operation_parameters(arg=Float()) |
1114 | ... @export_read_operation() |
1115 | @@ -2352,9 +2313,8 @@ |
1116 | In this example, the type of the operation, the type and number of the |
1117 | arguments, and the return value change in version 1.0. |
1118 | |
1119 | - >>> class ReadOrWriteMethod(Interface): |
1120 | - ... export_as_webservice_entry() |
1121 | - ... |
1122 | + >>> @exported_as_webservice_entry() |
1123 | + ... class ReadOrWriteMethod(Interface): |
1124 | ... @operation_parameters(arg=TextLine(), arg2=TextLine()) |
1125 | ... @export_write_operation() |
1126 | ... @operation_removed_in_version(1.0) |
1127 | @@ -2403,9 +2363,8 @@ |
1128 | |
1129 | Different versions can define different mutator methods for the same field. |
1130 | |
1131 | - >>> class IDifferentMutators(Interface): |
1132 | - ... export_as_webservice_entry() |
1133 | - ... |
1134 | + >>> @exported_as_webservice_entry() |
1135 | + ... class IDifferentMutators(Interface): |
1136 | ... field = exported(TextLine(readonly=True)) |
1137 | ... |
1138 | ... @mutator_for(field) |
1139 | @@ -2427,9 +2386,8 @@ |
1140 | |
1141 | But you can't define two mutators for the same field in the same version. |
1142 | |
1143 | - >>> class IDuplicateMutator(Interface): |
1144 | - ... export_as_webservice_entry() |
1145 | - ... |
1146 | + >>> @exported_as_webservice_entry() |
1147 | + ... class IDuplicateMutator(Interface): |
1148 | ... field = exported(TextLine(readonly=True)) |
1149 | ... |
1150 | ... @mutator_for(field) |
1151 | @@ -2455,9 +2413,8 @@ |
1152 | giving its name), and then define another one explicitly (giving the |
1153 | name of the earliest version.) |
1154 | |
1155 | - >>> class IImplicitAndExplicitMutator(Interface): |
1156 | - ... export_as_webservice_entry() |
1157 | - ... |
1158 | + >>> @exported_as_webservice_entry() |
1159 | + ... class IImplicitAndExplicitMutator(Interface): |
1160 | ... field = exported(TextLine(readonly=True)) |
1161 | ... |
1162 | ... @mutator_for(field) |
1163 | @@ -2490,9 +2447,8 @@ |
1164 | because the generate_entry_interfaces call modified the original |
1165 | class's annotations in place.) |
1166 | |
1167 | - >>> class IImplicitAndExplicitMutator(Interface): |
1168 | - ... export_as_webservice_entry() |
1169 | - ... |
1170 | + >>> @exported_as_webservice_entry() |
1171 | + ... class IImplicitAndExplicitMutator(Interface): |
1172 | ... field = exported(TextLine(readonly=True)) |
1173 | ... |
1174 | ... @mutator_for(field) |
1175 | @@ -2522,9 +2478,8 @@ |
1176 | earliest version, but not in '1.0'. This is fine: the 1.0 value for |
1177 | 'fixed2' will be inherited from the previous version. |
1178 | |
1179 | - >>> class IGoodDestructorEntry(Interface): |
1180 | - ... export_as_webservice_entry() |
1181 | - ... |
1182 | + >>> @exported_as_webservice_entry() |
1183 | + ... class IGoodDestructorEntry(Interface): |
1184 | ... @call_with(fixed1="value3") |
1185 | ... @operation_for_version('1.0') |
1186 | ... @export_destructor_operation() |
1187 | @@ -2542,9 +2497,8 @@ |
1188 | 'fixed2' is a problem. The fact that 'fixed2' is fixed in 3.0 doesn't |
1189 | help; the method is incompletely specified in 2.0. |
1190 | |
1191 | - >>> class IBadDestructorEntry(Interface): |
1192 | - ... export_as_webservice_entry() |
1193 | - ... |
1194 | + >>> @exported_as_webservice_entry() |
1195 | + ... class IBadDestructorEntry(Interface): |
1196 | ... @call_with(fixed2="value4") |
1197 | ... @operation_for_version('2.0') |
1198 | ... @export_destructor_operation() |
1199 | @@ -2683,8 +2637,8 @@ |
1200 | that named operation annotations proceed from the top down rather than |
1201 | the bottom up.) |
1202 | |
1203 | - >>> class WrongOrderVersions(Interface): |
1204 | - ... export_as_webservice_entry() |
1205 | + >>> @exported_as_webservice_entry() |
1206 | + ... class WrongOrderVersions(Interface): |
1207 | ... @export_operation_as('10_name') |
1208 | ... @operation_for_version("1.0") |
1209 | ... @operation_parameters(arg=Float()) |
1210 | @@ -2707,8 +2661,8 @@ |
1211 | Here's a class in which a named operation is removed in version 1.0 |
1212 | and then annotated without being reinstated. |
1213 | |
1214 | - >>> class AnnotatingARemovedMethod(Interface): |
1215 | - ... export_as_webservice_entry() |
1216 | + >>> @exported_as_webservice_entry() |
1217 | + ... class AnnotatingARemovedMethod(Interface): |
1218 | ... @operation_parameters(arg=TextLine()) |
1219 | ... @export_operation_as('already_been_removed') |
1220 | ... @operation_removed_in_version("2.0") |
1221 | @@ -2740,9 +2694,8 @@ |
1222 | Let's consider an entry that defines a mutator in the very first |
1223 | version of the web service and never removes it. |
1224 | |
1225 | - >>> class IBetaMutatorEntry(Interface): |
1226 | - ... export_as_webservice_entry() |
1227 | - ... |
1228 | + >>> @exported_as_webservice_entry() |
1229 | + ... class IBetaMutatorEntry(Interface): |
1230 | ... field = exported(TextLine(readonly=True)) |
1231 | ... |
1232 | ... @mutator_for(field) |
1233 | @@ -2799,9 +2752,8 @@ |
1234 | Here's an entry that defines a mutator method in version 2.0, after |
1235 | the cutoff point. |
1236 | |
1237 | - >>> class I20MutatorEntry(Interface): |
1238 | - ... export_as_webservice_entry() |
1239 | - ... |
1240 | + >>> @exported_as_webservice_entry() |
1241 | + ... class I20MutatorEntry(Interface): |
1242 | ... field = exported(TextLine(readonly=True)) |
1243 | ... |
1244 | ... @mutator_for(field) |
1245 | @@ -2843,9 +2795,8 @@ |
1246 | Here's a named operation that was defined in '1.0' and promoted to a |
1247 | mutator in '3.0'. |
1248 | |
1249 | - >>> class IOperationPromotedToMutator(Interface): |
1250 | - ... export_as_webservice_entry() |
1251 | - ... |
1252 | + >>> @exported_as_webservice_entry() |
1253 | + ... class IOperationPromotedToMutator(Interface): |
1254 | ... field = exported(TextLine(readonly=True)) |
1255 | ... |
1256 | ... @mutator_for(field) |
1257 | @@ -2915,9 +2866,8 @@ |
1258 | as 'set_value' in version 2.0, and a third operation to be published as |
1259 | 'set_value' in version 3.0. |
1260 | |
1261 | - >>> class IMutatorPlusNamedOperationEntry(Interface): |
1262 | - ... export_as_webservice_entry() |
1263 | - ... |
1264 | + >>> @exported_as_webservice_entry() |
1265 | + ... class IMutatorPlusNamedOperationEntry(Interface): |
1266 | ... field = exported(TextLine(readonly=True)) |
1267 | ... |
1268 | ... @mutator_for(field) |
1269 | @@ -2981,9 +2931,8 @@ |
1270 | the 'beta' version of the web service. (We have to redefine the class |
1271 | to avoid conflicting registrations.) |
1272 | |
1273 | - >>> class IBetaMutatorEntry2(IBetaMutatorEntry): |
1274 | - ... export_as_webservice_entry() |
1275 | - ... |
1276 | + >>> @exported_as_webservice_entry() |
1277 | + ... class IBetaMutatorEntry2(IBetaMutatorEntry): |
1278 | ... field = exported(TextLine(readonly=True)) |
1279 | ... |
1280 | ... @mutator_for(field) |
1281 | @@ -3026,9 +2975,8 @@ |
1282 | Again, we have to publish a new entry class, to avoid conflicting |
1283 | registrations. |
1284 | |
1285 | - >>> class IBetaMutatorEntry3(Interface): |
1286 | - ... export_as_webservice_entry() |
1287 | - ... |
1288 | + >>> @exported_as_webservice_entry() |
1289 | + ... class IBetaMutatorEntry3(Interface): |
1290 | ... field = exported(TextLine(readonly=True)) |
1291 | ... |
1292 | ... @mutator_for(field) |
1293 | |
1294 | === modified file 'src/lazr/restful/example/base/interfaces.py' |
1295 | --- src/lazr/restful/example/base/interfaces.py 2020-02-04 11:52:59 +0000 |
1296 | +++ src/lazr/restful/example/base/interfaces.py 2020-02-19 16:09:23 +0000 |
1297 | @@ -29,11 +29,19 @@ |
1298 | from lazr.restful.fields import CollectionField, Reference |
1299 | from lazr.restful.interfaces import IByteStorage, ITopLevelEntryLink |
1300 | from lazr.restful.declarations import ( |
1301 | - collection_default_content, export_as_webservice_collection, |
1302 | - export_as_webservice_entry, export_destructor_operation, |
1303 | - export_factory_operation, export_read_operation, export_write_operation, |
1304 | - exported, operation_parameters, operation_returns_collection_of, |
1305 | - operation_returns_entry, webservice_error) |
1306 | + collection_default_content, |
1307 | + export_destructor_operation, |
1308 | + export_factory_operation, |
1309 | + export_read_operation, |
1310 | + export_write_operation, |
1311 | + exported, |
1312 | + exported_as_webservice_collection, |
1313 | + exported_as_webservice_entry, |
1314 | + operation_parameters, |
1315 | + operation_returns_collection_of, |
1316 | + operation_returns_entry, |
1317 | + webservice_error, |
1318 | + ) |
1319 | |
1320 | |
1321 | class AlreadyNew(Exception): |
1322 | @@ -73,9 +81,9 @@ |
1323 | """Traverse to a contained object.""" |
1324 | |
1325 | |
1326 | +@exported_as_webservice_entry(plural_name='dishes') |
1327 | class IDish(ILocation): |
1328 | """A dish, annotated for export to the web service.""" |
1329 | - export_as_webservice_entry(plural_name='dishes') |
1330 | name = exported(TextLine(title=u"Name", required=True)) |
1331 | recipes = exported(CollectionField( |
1332 | title=u"Recipes in this cookbook", |
1333 | @@ -85,9 +93,9 @@ |
1334 | """Remove one of this dish's recipes.""" |
1335 | |
1336 | |
1337 | +@exported_as_webservice_entry() |
1338 | class IRecipe(ILocation): |
1339 | """A recipe, annotated for export to the web service.""" |
1340 | - export_as_webservice_entry() |
1341 | id = exported(Int(title=u"Unique ID", required=True)) |
1342 | dish = exported(Reference(title=u"Dish", schema=IDish)) |
1343 | cookbook = exported(Reference(title=u"Cookbook", schema=Interface)) |
1344 | @@ -104,9 +112,9 @@ |
1345 | """Delete the recipe.""" |
1346 | |
1347 | |
1348 | +@exported_as_webservice_entry() |
1349 | class ICookbook(IHasGet, ILocation): |
1350 | """A cookbook, annotated for export to the web service.""" |
1351 | - export_as_webservice_entry() |
1352 | name = exported(TextLine(title=u"Name", required=True)) |
1353 | copyright_date = exported( |
1354 | Date(title=u"Copyright Date", |
1355 | @@ -165,6 +173,7 @@ |
1356 | IRecipe['cookbook'].schema = ICookbook |
1357 | |
1358 | |
1359 | +@exported_as_webservice_entry() |
1360 | class ICookbookSubclass(ICookbook): |
1361 | """A published subclass of ICookbook. |
1362 | |
1363 | @@ -172,16 +181,15 @@ |
1364 | a published entry interface that subclasses another published |
1365 | entry interface. |
1366 | """ |
1367 | - export_as_webservice_entry() |
1368 | |
1369 | |
1370 | class IFeaturedCookbookLink(ITopLevelEntryLink): |
1371 | """A marker interface.""" |
1372 | |
1373 | |
1374 | +@exported_as_webservice_collection(ICookbook) |
1375 | class ICookbookSet(IHasGet): |
1376 | """The set of all cookbooks, annotated for export to the web service.""" |
1377 | - export_as_webservice_collection(ICookbook) |
1378 | |
1379 | @collection_default_content() |
1380 | def getCookbooks(): |
1381 | @@ -214,18 +222,18 @@ |
1382 | featured = Attribute("The currently featured cookbook.") |
1383 | |
1384 | |
1385 | +@exported_as_webservice_collection(IDish) |
1386 | class IDishSet(IHasGet): |
1387 | """The set of all dishes, annotated for export to the web service.""" |
1388 | - export_as_webservice_collection(IDish) |
1389 | |
1390 | @collection_default_content() |
1391 | def getDishes(): |
1392 | """Return the list of dishes.""" |
1393 | |
1394 | |
1395 | +@exported_as_webservice_collection(IRecipe) |
1396 | class IRecipeSet(IHasGet): |
1397 | """The set of all recipes, annotated for export to the web service.""" |
1398 | - export_as_webservice_collection(IRecipe) |
1399 | |
1400 | @collection_default_content() |
1401 | def getRecipes(): |
1402 | |
1403 | === modified file 'src/lazr/restful/example/base_extended/comments.py' |
1404 | --- src/lazr/restful/example/base_extended/comments.py 2020-02-04 11:52:59 +0000 |
1405 | +++ src/lazr/restful/example/base_extended/comments.py 2020-02-19 16:09:23 +0000 |
1406 | @@ -7,11 +7,13 @@ |
1407 | from lazr.restful.example.base.interfaces import IRecipe |
1408 | |
1409 | from lazr.restful.declarations import ( |
1410 | - export_as_webservice_entry, exported) |
1411 | - |
1412 | - |
1413 | + exported, |
1414 | + exported_as_webservice_entry, |
1415 | + ) |
1416 | + |
1417 | + |
1418 | +@exported_as_webservice_entry(contributes_to=[IRecipe]) |
1419 | class IHasComments(Interface): |
1420 | - export_as_webservice_entry(contributes_to=[IRecipe]) |
1421 | comments = exported( |
1422 | List(title=u'Comments made by users', value_type=Text())) |
1423 | |
1424 | |
1425 | === modified file 'src/lazr/restful/example/multiversion/resources.py' |
1426 | --- src/lazr/restful/example/multiversion/resources.py 2020-02-04 11:52:59 +0000 |
1427 | +++ src/lazr/restful/example/multiversion/resources.py 2020-02-19 16:09:23 +0000 |
1428 | @@ -12,11 +12,20 @@ |
1429 | from zope.location.interfaces import ILocation |
1430 | |
1431 | from lazr.restful.declarations import ( |
1432 | - collection_default_content, export_as_webservice_collection, |
1433 | - export_as_webservice_entry, export_destructor_operation, |
1434 | - export_operation_as, export_read_operation, export_write_operation, |
1435 | - exported, mutator_for, operation_for_version, operation_parameters, |
1436 | - operation_removed_in_version, operation_returns_collection_of) |
1437 | + collection_default_content, |
1438 | + export_destructor_operation, |
1439 | + export_operation_as, |
1440 | + export_read_operation, |
1441 | + export_write_operation, |
1442 | + exported, |
1443 | + exported_as_webservice_collection, |
1444 | + exported_as_webservice_entry, |
1445 | + mutator_for, |
1446 | + operation_for_version, |
1447 | + operation_parameters, |
1448 | + operation_removed_in_version, |
1449 | + operation_returns_collection_of, |
1450 | + ) |
1451 | |
1452 | # Our implementations of these classes can be based on the |
1453 | # implementations from the WSGI example. |
1454 | @@ -26,8 +35,8 @@ |
1455 | |
1456 | # Our interfaces _will_ diverge from the WSGI example interfaces, so |
1457 | # define them separately. |
1458 | +@exported_as_webservice_entry() |
1459 | class IKeyValuePair(ILocation): |
1460 | - export_as_webservice_entry() |
1461 | key = exported(Text(title=u"The key")) |
1462 | value = exported(Text(title=u"The value")) |
1463 | a_comment = exported(Text(title=u"A comment on this key-value pair.", |
1464 | @@ -61,9 +70,8 @@ |
1465 | """A destructor that simply sets .deleted to True.""" |
1466 | |
1467 | |
1468 | +@exported_as_webservice_collection(IKeyValuePair) |
1469 | class IPairSet(ILocation): |
1470 | - export_as_webservice_collection(IKeyValuePair) |
1471 | - |
1472 | # In versions 2.0 and 3.0, the collection of key-value pairs |
1473 | # includes all pairs. |
1474 | @collection_default_content("2.0") |
1475 | |
1476 | === modified file 'src/lazr/restful/example/wsgi/README.txt' |
1477 | --- src/lazr/restful/example/wsgi/README.txt 2009-09-16 16:00:46 +0000 |
1478 | +++ src/lazr/restful/example/wsgi/README.txt 2020-02-19 16:09:23 +0000 |
1479 | @@ -76,7 +76,7 @@ |
1480 | lazr.restful.example.wsgi.resources. First we define an interface |
1481 | (IPairSet) that's decorated with lazr.restful decorators: |
1482 | |
1483 | -1. export_as_webservice_collection(IKeyValuePair) tells lazr.restful |
1484 | +1. @exported_as_webservice_collection(IKeyValuePair) tells lazr.restful |
1485 | that an IPairSet should be published as a collection resource, as |
1486 | opposed to being published as an entry or not published at all. The |
1487 | "IKeyValuePair" lets lazr.restful know that this is a collection of |
1488 | @@ -96,7 +96,7 @@ |
1489 | lazr.restful.example.wsgi.resources. First we define an interface |
1490 | (IKeyValuePair) that's decorated with lazr.restful decorators: |
1491 | |
1492 | -1. export_as_webservice_entry() tells lazr.restful that an |
1493 | +1. @exported_as_webservice_entry() tells lazr.restful that an |
1494 | IKeyValuePair should be published as an entry, as opposed to being |
1495 | published as an entry or not published at all. |
1496 | |
1497 | @@ -140,7 +140,7 @@ |
1498 | 3. There are no more path fragments, so traversal is complete. The |
1499 | PairSet object will be published as a collection resource. Why a |
1500 | collection? Because PairSet implements IPairSet, which is published |
1501 | - as a collection. (Remember export_as_webservice_collection()?) |
1502 | + as a collection. (Remember @exported_as_webservice_collection()?) |
1503 | |
1504 | You don't have to write all the traversal code your web service will |
1505 | ever use. There's a set of default rules that take over once your |
1506 | @@ -158,7 +158,7 @@ |
1507 | object. |
1508 | |
1509 | 4. Because IKeyValuePair is published as an entry (remember |
1510 | - export_as_webservice_entry?), the custom traversal code now |
1511 | + @exported_as_webservice_entry?), the custom traversal code now |
1512 | stops. What happens next is entirely controlled by lazr.restful. |
1513 | Because of this, it doesn't make sense to have an entry subclass |
1514 | TraverseWithGet or implement IPublishTraverse. |
1515 | |
1516 | === modified file 'src/lazr/restful/example/wsgi/resources.py' |
1517 | --- src/lazr/restful/example/wsgi/resources.py 2020-02-04 11:52:59 +0000 |
1518 | +++ src/lazr/restful/example/wsgi/resources.py 2020-02-19 16:09:23 +0000 |
1519 | @@ -13,21 +13,23 @@ |
1520 | from zope.location.interfaces import ILocation |
1521 | |
1522 | from lazr.restful.declarations import ( |
1523 | - collection_default_content, export_as_webservice_collection, |
1524 | - export_as_webservice_entry, exported) |
1525 | + collection_default_content, |
1526 | + exported, |
1527 | + exported_as_webservice_collection, |
1528 | + exported_as_webservice_entry, |
1529 | + ) |
1530 | from lazr.restful.interfaces import IServiceRootResource |
1531 | from lazr.restful.simple import TraverseWithGet |
1532 | |
1533 | |
1534 | +@exported_as_webservice_entry() |
1535 | class IKeyValuePair(ILocation): |
1536 | - export_as_webservice_entry() |
1537 | key = exported(Text(title=u"The key")) |
1538 | value = exported(Text(title=u"The value")) |
1539 | |
1540 | |
1541 | +@exported_as_webservice_collection(IKeyValuePair) |
1542 | class IPairSet(ILocation): |
1543 | - export_as_webservice_collection(IKeyValuePair) |
1544 | - |
1545 | @collection_default_content() |
1546 | def getPairs(): |
1547 | """Return the key-value pairs.""" |
1548 | |
1549 | === modified file 'src/lazr/restful/testing/webservice.py' |
1550 | --- src/lazr/restful/testing/webservice.py 2020-02-04 13:17:32 +0000 |
1551 | +++ src/lazr/restful/testing/webservice.py 2020-02-19 16:09:23 +0000 |
1552 | @@ -47,7 +47,7 @@ |
1553 | |
1554 | from lazr.restful.declarations import ( |
1555 | collection_default_content, exported, |
1556 | - export_as_webservice_collection, export_as_webservice_entry, |
1557 | + exported_as_webservice_collection, exported_as_webservice_entry, |
1558 | export_read_operation, operation_parameters) |
1559 | from lazr.restful.interfaces import ( |
1560 | IServiceRootResource, IWebServiceClientRequest, |
1561 | @@ -546,12 +546,12 @@ |
1562 | """) |
1563 | |
1564 | |
1565 | +@exported_as_webservice_entry() |
1566 | class IGenericEntry(Interface): |
1567 | """A simple, reusable entry interface for use in tests. |
1568 | |
1569 | The entry publishes one field and one named operation. |
1570 | """ |
1571 | - export_as_webservice_entry() |
1572 | |
1573 | # pylint: disable-msg=E0213 |
1574 | a_field = exported( |
1575 | @@ -569,9 +569,9 @@ |
1576 | """ |
1577 | |
1578 | |
1579 | +@exported_as_webservice_collection(IGenericEntry) |
1580 | class IGenericCollection(Interface): |
1581 | """A simple collection containing `IGenericEntry`, for use in tests.""" |
1582 | - export_as_webservice_collection(IGenericEntry) |
1583 | |
1584 | # pylint: disable-msg=E0211 |
1585 | @collection_default_content() |
1586 | |
1587 | === modified file 'src/lazr/restful/tests/test_declarations.py' |
1588 | --- src/lazr/restful/tests/test_declarations.py 2020-02-13 00:11:04 +0000 |
1589 | +++ src/lazr/restful/tests/test_declarations.py 2020-02-19 16:09:23 +0000 |
1590 | @@ -4,6 +4,8 @@ |
1591 | |
1592 | from __future__ import absolute_import, print_function |
1593 | |
1594 | +import sys |
1595 | + |
1596 | import testtools |
1597 | from zope.component import ( |
1598 | adapter, |
1599 | @@ -34,13 +36,17 @@ |
1600 | from lazr.restful.declarations import ( |
1601 | accessor_for, |
1602 | call_with, |
1603 | + collection_default_content, |
1604 | error_status, |
1605 | + export_as_webservice_collection, |
1606 | export_as_webservice_entry, |
1607 | - exported, |
1608 | export_read_operation, |
1609 | export_write_operation, |
1610 | + exported, |
1611 | + exported_as_webservice_entry, |
1612 | generate_entry_interfaces, |
1613 | generate_operation_adapter, |
1614 | + LAZR_WEBSERVICE_EXPORTED, |
1615 | LAZR_WEBSERVICE_NAME, |
1616 | mutator_for, |
1617 | operation_for_version, |
1618 | @@ -137,8 +143,8 @@ |
1619 | # There can only be one accessor defined for a given attribute |
1620 | # at a given version. |
1621 | def declare_too_many_accessors(): |
1622 | + @exported_as_webservice_entry(contributes_to=[IProduct]) |
1623 | class IHasTooManyAccessors(Interface): |
1624 | - export_as_webservice_entry(contributes_to=[IProduct]) |
1625 | needs_an_accessor = exported( |
1626 | TextLine(title=u'This needs an accessor', readonly=True)) |
1627 | |
1628 | @@ -241,8 +247,9 @@ |
1629 | def test_redacted_fields_for_empty_entry(self): |
1630 | # An entry which doesn't export any fields/operations will have no |
1631 | # redacted fields. |
1632 | + @exported_as_webservice_entry() |
1633 | class IEmpty(Interface): |
1634 | - export_as_webservice_entry() |
1635 | + pass |
1636 | @implementer(IEmpty) |
1637 | class Empty: |
1638 | pass |
1639 | @@ -300,8 +307,8 @@ |
1640 | # A contributing interface can only contribute to exported interfaces. |
1641 | class INotExported(Interface): |
1642 | pass |
1643 | + @exported_as_webservice_entry(contributes_to=[INotExported]) |
1644 | class IContributor(Interface): |
1645 | - export_as_webservice_entry(contributes_to=[INotExported]) |
1646 | title = exported(TextLine(title=u'The project title')) |
1647 | self.assertRaises( |
1648 | AttemptToContributeToNonExportedInterface, |
1649 | @@ -333,6 +340,150 @@ |
1650 | 'product', EntryAdapterUtility(adapter.__class__).singular_type) |
1651 | |
1652 | |
1653 | +class TestExportAsWebserviceEntry(testtools.TestCase): |
1654 | + """Tests for export_as_webservice_entry.""" |
1655 | + |
1656 | + def setUp(self): |
1657 | + super(TestExportAsWebserviceEntry, self).setUp() |
1658 | + if sys.version_info[0] >= 3: |
1659 | + self.skipTest( |
1660 | + 'export_as_webservice_entry is only supported on Python 2') |
1661 | + |
1662 | + def test_works_on_interface(self): |
1663 | + # export_as_webservice_entry works on an interface. |
1664 | + class IFoo(Interface): |
1665 | + export_as_webservice_entry() |
1666 | + |
1667 | + self.assertTrue( |
1668 | + IFoo.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)['exported']) |
1669 | + |
1670 | + def test_requires_interface(self): |
1671 | + # export_as_webservice_entry can only be used on Interface. |
1672 | + def export_non_interface(): |
1673 | + class NotAnInterface(object): |
1674 | + export_as_webservice_entry() |
1675 | + |
1676 | + exception = self.assertRaises(TypeError, export_non_interface) |
1677 | + self.assertEqual( |
1678 | + 'export_as_webservice_entry() can only be used on an interface.', |
1679 | + str(exception)) |
1680 | + |
1681 | + def test_must_be_within_interface_definition(self): |
1682 | + # export_as_webservice_entry can only be used from within a |
1683 | + # interface definition. |
1684 | + exception = self.assertRaises(TypeError, export_as_webservice_entry) |
1685 | + self.assertEqual( |
1686 | + 'export_as_webservice_entry() can only be used from within an ' |
1687 | + 'interface definition.', |
1688 | + str(exception)) |
1689 | + |
1690 | + |
1691 | +class TestExportAsWebserviceCollection(testtools.TestCase): |
1692 | + """Tests for export_as_webservice_collection.""" |
1693 | + |
1694 | + def setUp(self): |
1695 | + super(TestExportAsWebserviceCollection, self).setUp() |
1696 | + if sys.version_info[0] >= 3: |
1697 | + self.skipTest( |
1698 | + 'export_as_webservice_collection is only supported on ' |
1699 | + 'Python 2') |
1700 | + |
1701 | + def test_works_on_interface_with_tagged_method(self): |
1702 | + # export_as_webservice_collection works on an interface that has an |
1703 | + # entry schema and a method tagged with @collection_default_content. |
1704 | + class IFoo(Interface): |
1705 | + export_as_webservice_collection(Interface) |
1706 | + |
1707 | + @collection_default_content() |
1708 | + def getAll(): |
1709 | + pass |
1710 | + |
1711 | + self.assertEqual( |
1712 | + {None: ('getAll', {})}, |
1713 | + IFoo.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)[ |
1714 | + 'collection_default_content']) |
1715 | + |
1716 | + def test_requires_entry_schema(self): |
1717 | + # export_as_webservice_collection requires an entry schema. |
1718 | + def export_without_entry_schema(): |
1719 | + class MissingEntrySchema(Interface): |
1720 | + export_as_webservice_collection() |
1721 | + |
1722 | + exception = self.assertRaises(TypeError, export_without_entry_schema) |
1723 | + self.assertEqual( |
1724 | + 'export_as_webservice_collection() takes exactly 1 argument (0 ' |
1725 | + 'given)', |
1726 | + str(exception)) |
1727 | + |
1728 | + def test_requires_entry_schema_as_interface(self): |
1729 | + # export_as_webservice_collection requires the entry schema to be an |
1730 | + # interface. |
1731 | + def export_with_invalid_entry_schema(): |
1732 | + class InvalidEntrySchema(Interface): |
1733 | + export_as_webservice_collection('not an interface') |
1734 | + |
1735 | + exception = self.assertRaises( |
1736 | + TypeError, export_with_invalid_entry_schema) |
1737 | + self.assertEqual('entry_schema must be an interface.', str(exception)) |
1738 | + |
1739 | + def test_requires_tagged_method(self): |
1740 | + # export_as_webservice_collection can only be used on a collection |
1741 | + # that has a method marked as exporting the default content. |
1742 | + def export_missing_default_content(): |
1743 | + class MissingDefaultContent(Interface): |
1744 | + export_as_webservice_collection(Interface) |
1745 | + |
1746 | + exception = self.assertRaises( |
1747 | + TypeError, export_missing_default_content) |
1748 | + self.assertEqual( |
1749 | + 'export_as_webservice_collection() is missing a method tagged ' |
1750 | + 'with @collection_default_content.', |
1751 | + str(exception)) |
1752 | + |
1753 | + def test_refuses_multiple_tagged_methods(self): |
1754 | + # export_as_webservice_collection cannot be used on a collection |
1755 | + # that has multiple methods marked as exporting the default content. |
1756 | + def export_two_default_content(): |
1757 | + class TwoDefaultContent(Interface): |
1758 | + export_as_webservice_collection(Interface) |
1759 | + |
1760 | + @collection_default_content() |
1761 | + def getAll1(): |
1762 | + """A first getAll().""" |
1763 | + |
1764 | + @collection_default_content() |
1765 | + def getAll2(): |
1766 | + """Another getAll().""" |
1767 | + |
1768 | + exception = self.assertRaises(TypeError, export_two_default_content) |
1769 | + self.assertEqual( |
1770 | + "Only one method can be marked with @collection_default_content " |
1771 | + "for version '(earliest version)'.", |
1772 | + str(exception)) |
1773 | + |
1774 | + def test_requires_interface(self): |
1775 | + # export_as_webservice_collection can only be used on Interface. |
1776 | + def export_non_interface(): |
1777 | + class NotAnInterface(object): |
1778 | + export_as_webservice_collection(Interface) |
1779 | + |
1780 | + exception = self.assertRaises(TypeError, export_non_interface) |
1781 | + self.assertEqual( |
1782 | + 'export_as_webservice_collection() can only be used on an ' |
1783 | + 'interface.', |
1784 | + str(exception)) |
1785 | + |
1786 | + def test_must_be_within_interface_definition(self): |
1787 | + # export_as_webservice_collection can only be used from within an |
1788 | + # interface definition. |
1789 | + exception = self.assertRaises( |
1790 | + TypeError, export_as_webservice_collection, Interface) |
1791 | + self.assertEqual( |
1792 | + 'export_as_webservice_collection() can only be used from within ' |
1793 | + 'an interface definition.', |
1794 | + str(exception)) |
1795 | + |
1796 | + |
1797 | class NotExportedException(Exception): |
1798 | pass |
1799 | |
1800 | @@ -351,8 +502,9 @@ |
1801 | pass |
1802 | |
1803 | |
1804 | +@exported_as_webservice_entry() |
1805 | class IExported(Interface): |
1806 | - export_as_webservice_entry() |
1807 | + pass |
1808 | |
1809 | |
1810 | class TestFindExportedInterfaces(testtools.TestCase): |
1811 | @@ -394,8 +546,8 @@ |
1812 | self.assertExportsNames(Module, ['ExportedExceptionOne']) |
1813 | |
1814 | |
1815 | +@exported_as_webservice_entry() |
1816 | class IProduct(Interface): |
1817 | - export_as_webservice_entry() |
1818 | title = exported(TextLine(title=u'The product title')) |
1819 | # Need to define the three attributes below because we have a test which |
1820 | # wraps a Product object with a security proxy and later uses adapters |
1821 | @@ -415,8 +567,8 @@ |
1822 | _bug_target_name = None |
1823 | |
1824 | |
1825 | +@exported_as_webservice_entry() |
1826 | class IProject(Interface): |
1827 | - export_as_webservice_entry() |
1828 | title = exported(TextLine(title=u'The project title')) |
1829 | |
1830 | |
1831 | @@ -426,8 +578,8 @@ |
1832 | _bug_count = 0 |
1833 | |
1834 | |
1835 | +@exported_as_webservice_entry(contributes_to=[IProduct, IProject]) |
1836 | class IHasBugs(Interface): |
1837 | - export_as_webservice_entry(contributes_to=[IProduct, IProject]) |
1838 | bug_count = exported(Int(title=u'Number of bugs')) |
1839 | not_exported = TextLine(title=u'Not exported') |
1840 | bug_target_name = exported( |
1841 | @@ -463,14 +615,14 @@ |
1842 | pass |
1843 | |
1844 | |
1845 | +@exported_as_webservice_entry(contributes_to=[IProduct]) |
1846 | class IHasBugs2(Interface): |
1847 | - export_as_webservice_entry(contributes_to=[IProduct]) |
1848 | bug_count = exported(Int(title=u'Number of bugs')) |
1849 | not_exported = TextLine(title=u'Not exported') |
1850 | |
1851 | |
1852 | +@exported_as_webservice_entry(contributes_to=[IProduct]) |
1853 | class IHasBugs3(Interface): |
1854 | - export_as_webservice_entry(contributes_to=[IProduct]) |
1855 | not_exported = TextLine(title=u'Not exported') |
1856 | |
1857 | @export_read_operation() |
1858 | @@ -504,8 +656,8 @@ |
1859 | pass |
1860 | |
1861 | |
1862 | +@exported_as_webservice_entry() |
1863 | class IBranch(Interface): |
1864 | - export_as_webservice_entry() |
1865 | name = TextLine(title=u'The branch name') |
1866 | |
1867 | |
1868 | @@ -516,8 +668,8 @@ |
1869 | self.name = name |
1870 | |
1871 | |
1872 | +@exported_as_webservice_entry(contributes_to=[IProduct]) |
1873 | class IHasBranches(Interface): |
1874 | - export_as_webservice_entry(contributes_to=[IProduct]) |
1875 | not_exported = TextLine(title=u'Not exported') |
1876 | development_branch = exported( |
1877 | Reference(schema=IBranch, readonly=True), |
1878 | @@ -571,32 +723,36 @@ |
1879 | |
1880 | # Classes for TestEntryMultiversion. |
1881 | |
1882 | +@exported_as_webservice_entry(as_of="2.0") |
1883 | class INotInitiallyExported(Interface): |
1884 | # An entry that's not exported in the first version of the web |
1885 | # service. |
1886 | - export_as_webservice_entry(as_of="2.0") |
1887 | - |
1888 | - |
1889 | + pass |
1890 | + |
1891 | + |
1892 | +@exported_as_webservice_entry( |
1893 | + versioned_annotations=[('2.0', dict(exported=False))]) |
1894 | class INotPresentInLaterVersion(Interface): |
1895 | # An entry that's only exported in the first version of the web |
1896 | # service. |
1897 | - export_as_webservice_entry( |
1898 | - versioned_annotations=[('2.0', dict(exported=False))]) |
1899 | - |
1900 | - |
1901 | + pass |
1902 | + |
1903 | + |
1904 | +@exported_as_webservice_entry( |
1905 | + singular_name="octopus", plural_name="octopi", |
1906 | + versioned_annotations=[ |
1907 | + ('2.0', dict(singular_name="fish", plural_name="fishes"))]) |
1908 | class IHasDifferentNamesInDifferentVersions(Interface): |
1909 | # An entry that has different names in different versions. |
1910 | - export_as_webservice_entry( |
1911 | - singular_name="octopus", plural_name="octopi", |
1912 | - versioned_annotations=[ |
1913 | - ('2.0', dict(singular_name="fish", plural_name="fishes"))]) |
1914 | - |
1915 | - |
1916 | + pass |
1917 | + |
1918 | + |
1919 | +@exported_as_webservice_entry( |
1920 | + singular_name="frog", |
1921 | + versioned_annotations=[('2.0', dict(singular_name="toad"))]) |
1922 | class IHasDifferentSingularNamesInDifferentVersions(Interface): |
1923 | # An entry that has different names in different versions. |
1924 | - export_as_webservice_entry( |
1925 | - singular_name="frog", |
1926 | - versioned_annotations=[('2.0', dict(singular_name="toad"))]) |
1927 | + pass |
1928 | |
1929 | |
1930 | class TestEntryMultiversion(TestCaseWithWebServiceFixtures): |
1931 | @@ -640,10 +796,11 @@ |
1932 | # You can't define an entry class that includes an unrecognized |
1933 | # annotation in its versioned_annotations. |
1934 | try: |
1935 | + @exported_as_webservice_entry( |
1936 | + versioned_annotations=[ |
1937 | + ('2.0', dict(no_such_annotation=True))]) |
1938 | class IUsesNonexistentAnnotation(Interface): |
1939 | - export_as_webservice_entry( |
1940 | - versioned_annotations=[ |
1941 | - ('2.0', dict(no_such_annotation=True))]) |
1942 | + pass |
1943 | self.fail("Expected ValueError.") |
1944 | except ValueError as exception: |
1945 | self.assertEqual( |
1946 | @@ -671,58 +828,55 @@ |
1947 | |
1948 | # Classes for TestReqireExplicitVersions |
1949 | |
1950 | +@exported_as_webservice_entry() |
1951 | class IEntryExportedWithoutAsOf(Interface): |
1952 | - export_as_webservice_entry() |
1953 | - |
1954 | - |
1955 | + pass |
1956 | + |
1957 | + |
1958 | +@exported_as_webservice_entry(as_of="1.0") |
1959 | class IEntryExportedWithAsOf(Interface): |
1960 | - export_as_webservice_entry(as_of="1.0") |
1961 | - |
1962 | - |
1963 | + pass |
1964 | + |
1965 | + |
1966 | +@exported_as_webservice_entry(as_of="2.0") |
1967 | class IEntryExportedAsOfLaterVersion(Interface): |
1968 | - export_as_webservice_entry(as_of="2.0") |
1969 | - |
1970 | - |
1971 | + pass |
1972 | + |
1973 | + |
1974 | +@exported_as_webservice_entry(as_of="1.0") |
1975 | class IFieldExportedWithoutAsOf(Interface): |
1976 | - export_as_webservice_entry(as_of="1.0") |
1977 | - |
1978 | field = exported(TextLine(), exported=True) |
1979 | |
1980 | |
1981 | +@exported_as_webservice_entry(as_of="1.0") |
1982 | class IFieldExportedToEarliestVersionUsingAsOf(Interface): |
1983 | - export_as_webservice_entry(as_of="1.0") |
1984 | - |
1985 | field = exported(TextLine(), as_of='1.0') |
1986 | |
1987 | |
1988 | +@exported_as_webservice_entry(as_of="1.0") |
1989 | class IFieldExportedToLatestVersionUsingAsOf(Interface): |
1990 | - export_as_webservice_entry(as_of="1.0") |
1991 | - |
1992 | field = exported(TextLine(), as_of='2.0') |
1993 | |
1994 | |
1995 | +@exported_as_webservice_entry(as_of="1.0") |
1996 | class IFieldDefiningAttributesBeforeAsOf(Interface): |
1997 | - export_as_webservice_entry(as_of="1.0") |
1998 | - |
1999 | field = exported(TextLine(), ('1.0', dict(exported=True)), |
2000 | as_of='2.0') |
2001 | |
2002 | + |
2003 | +@exported_as_webservice_entry(as_of="1.0") |
2004 | class IFieldAsOfNonexistentVersion(Interface): |
2005 | - export_as_webservice_entry(as_of="1.0") |
2006 | - |
2007 | field = exported(TextLine(), as_of='nosuchversion') |
2008 | |
2009 | |
2010 | +@exported_as_webservice_entry(as_of="1.0") |
2011 | class IFieldDoubleDefinition(Interface): |
2012 | - export_as_webservice_entry(as_of="1.0") |
2013 | - |
2014 | field = exported(TextLine(), ('2.0', dict(exported_as='name2')), |
2015 | exported_as='name2', as_of='2.0') |
2016 | |
2017 | |
2018 | +@exported_as_webservice_entry(as_of="1.0") |
2019 | class IFieldImplicitOperationDefinition(Interface): |
2020 | - export_as_webservice_entry(as_of="1.0") |
2021 | - |
2022 | @call_with(value="2.0") |
2023 | @operation_for_version('2.0') |
2024 | @call_with(value="1.0") |
2025 | @@ -731,9 +885,8 @@ |
2026 | pass |
2027 | |
2028 | |
2029 | +@exported_as_webservice_entry(as_of="1.0") |
2030 | class IFieldExplicitOperationDefinition(Interface): |
2031 | - export_as_webservice_entry(as_of="1.0") |
2032 | - |
2033 | @call_with(value="2.0") |
2034 | @operation_for_version('2.0') |
2035 | @call_with(value="1.0") |
2036 | @@ -922,47 +1075,43 @@ |
2037 | pass |
2038 | |
2039 | |
2040 | +@exported_as_webservice_entry() |
2041 | class IReferencesNotPublished(Interface): |
2042 | - export_as_webservice_entry() |
2043 | field = exported(Reference(schema=INotPublished)) |
2044 | |
2045 | |
2046 | +@exported_as_webservice_entry() |
2047 | class IReferencesCollectionOfNotPublished(Interface): |
2048 | - export_as_webservice_entry() |
2049 | field = exported( |
2050 | CollectionField(value_type=Reference(schema=INotPublished))) |
2051 | |
2052 | |
2053 | +@exported_as_webservice_entry() |
2054 | class IOperationReturnsNotPublished(Interface): |
2055 | - export_as_webservice_entry() |
2056 | - |
2057 | @operation_returns_entry(INotPublished) |
2058 | @export_read_operation() |
2059 | def get_impossible_object(): |
2060 | pass |
2061 | |
2062 | |
2063 | +@exported_as_webservice_entry() |
2064 | class IOperationReturnsNotPublishedCollection(Interface): |
2065 | - export_as_webservice_entry() |
2066 | - |
2067 | @operation_returns_collection_of(INotPublished) |
2068 | @export_read_operation() |
2069 | def get_impossible_objects(): |
2070 | pass |
2071 | |
2072 | |
2073 | +@exported_as_webservice_entry() |
2074 | class IOperationAcceptsNotPublished(Interface): |
2075 | - export_as_webservice_entry() |
2076 | - |
2077 | @operation_parameters(arg=Reference(schema=INotPublished)) |
2078 | @export_write_operation() |
2079 | def use_impossible_object(arg): |
2080 | pass |
2081 | |
2082 | |
2083 | +@exported_as_webservice_entry() |
2084 | class IOperationAcceptsCollectionOfNotPublished(Interface): |
2085 | - export_as_webservice_entry() |
2086 | - |
2087 | @operation_parameters( |
2088 | arg=CollectionField(value_type=Reference(schema=INotPublished))) |
2089 | @export_write_operation() |
2090 | @@ -970,31 +1119,35 @@ |
2091 | pass |
2092 | |
2093 | |
2094 | +@exported_as_webservice_entry(as_of='2.0') |
2095 | class IPublishedTooLate(Interface): |
2096 | - export_as_webservice_entry(as_of='2.0') |
2097 | - |
2098 | - |
2099 | + pass |
2100 | + |
2101 | + |
2102 | +@exported_as_webservice_entry(as_of='1.0') |
2103 | class IReferencesPublishedTooLate(Interface): |
2104 | - export_as_webservice_entry(as_of='1.0') |
2105 | field = exported(Reference(schema=IPublishedTooLate)) |
2106 | |
2107 | |
2108 | +@exported_as_webservice_entry(as_of='1.0') |
2109 | class IPublishedEarly(Interface): |
2110 | - export_as_webservice_entry(as_of='1.0') |
2111 | - |
2112 | - |
2113 | + pass |
2114 | + |
2115 | + |
2116 | +@exported_as_webservice_entry(as_of='2.0') |
2117 | class IPublishedLate(Interface): |
2118 | - export_as_webservice_entry(as_of='2.0') |
2119 | field = exported(Reference(schema=IPublishedEarly)) |
2120 | |
2121 | |
2122 | +@exported_as_webservice_entry( |
2123 | + as_of='1.0', versioned_annotations=[ |
2124 | + VersionedObject('2.0', dict(exported=False))]) |
2125 | class IPublishedAndThenRemoved(Interface): |
2126 | - export_as_webservice_entry( |
2127 | - as_of='1.0', versioned_annotations=[ |
2128 | - VersionedObject('2.0', dict(exported=False))]) |
2129 | - |
2130 | + pass |
2131 | + |
2132 | + |
2133 | +@exported_as_webservice_entry(as_of='1.0') |
2134 | class IReferencesPublishedAndThenRemoved(Interface): |
2135 | - export_as_webservice_entry(as_of='1.0') |
2136 | field = exported(Reference(schema=IPublishedAndThenRemoved)) |
2137 | |
2138 | |
2139 | |
2140 | === modified file 'src/lazr/restful/tests/test_webservice.py' |
2141 | --- src/lazr/restful/tests/test_webservice.py 2020-02-13 00:11:04 +0000 |
2142 | +++ src/lazr/restful/tests/test_webservice.py 2020-02-19 16:09:23 +0000 |
2143 | @@ -58,7 +58,10 @@ |
2144 | ResourceGETOperation, |
2145 | ) |
2146 | from lazr.restful.declarations import ( |
2147 | - exported, export_as_webservice_entry, LAZR_WEBSERVICE_NAME) |
2148 | + exported, |
2149 | + exported_as_webservice_entry, |
2150 | + LAZR_WEBSERVICE_NAME, |
2151 | + ) |
2152 | from lazr.restful.testing.webservice import ( |
2153 | create_web_service_request, |
2154 | DummyAbsoluteURL, |
2155 | @@ -227,9 +230,9 @@ |
2156 | IAbsoluteURL) |
2157 | |
2158 | |
2159 | +@exported_as_webservice_entry() |
2160 | class IHasOneField(Interface): |
2161 | """An entry with a single field.""" |
2162 | - export_as_webservice_entry() |
2163 | a_field = exported(TextLine(title=u"A field.")) |
2164 | |
2165 | |
2166 | @@ -240,9 +243,9 @@ |
2167 | self.a_field = value |
2168 | |
2169 | |
2170 | +@exported_as_webservice_entry() |
2171 | class IHasTwoFields(Interface): |
2172 | """An entry with two fields.""" |
2173 | - export_as_webservice_entry() |
2174 | a_field = exported(TextLine(title=u"A field.")) |
2175 | another_field = exported(TextLine(title=u"Another field.")) |
2176 | |
2177 | @@ -317,9 +320,9 @@ |
2178 | self.assertFalse('web_link' in self.wadl) |
2179 | |
2180 | |
2181 | +@exported_as_webservice_entry(publish_web_link=False) |
2182 | class IHasNoWebLink(Interface): |
2183 | """An entry that does not publish a web_link.""" |
2184 | - export_as_webservice_entry(publish_web_link=False) |
2185 | a_field = exported(TextLine(title=u"A field.")) |
2186 | |
2187 | |
2188 | @@ -361,9 +364,9 @@ |
2189 | return super(InterfaceRestrictedField, self).bind(context) |
2190 | |
2191 | |
2192 | +@exported_as_webservice_entry() |
2193 | class IHasRestrictedField(Interface): |
2194 | """An entry with an InterfaceRestrictedField.""" |
2195 | - export_as_webservice_entry() |
2196 | a_field = exported(InterfaceRestrictedField(Interface)) |
2197 | |
2198 | |
2199 | @@ -374,9 +377,9 @@ |
2200 | self.a_field = value |
2201 | |
2202 | |
2203 | +@exported_as_webservice_entry() |
2204 | class IHasFieldExportedAsDifferentName(Interface): |
2205 | """An entry with a field exported as a different name.""" |
2206 | - export_as_webservice_entry() |
2207 | a_field = exported(TextLine(title=u"A field."), exported_as='field') |
2208 | |
2209 | |
2210 | @@ -616,9 +619,9 @@ |
2211 | UNICODE = Item(u"Uni\u00e7ode", "Uni\u00e7ode choice") |
2212 | |
2213 | |
2214 | +@exported_as_webservice_entry() |
2215 | class ICanBeSetToUnicodeValue(Interface): |
2216 | """An entry with an InterfaceRestrictedField.""" |
2217 | - export_as_webservice_entry() |
2218 | a_field = exported(Choice( |
2219 | vocabulary=UnicodeChoice, |
2220 | title=u"A value that might be ASCII or Unicode.", |
2221 | @@ -662,9 +665,8 @@ |
2222 | """Test the docstring generation.""" |
2223 | |
2224 | # This one is used to test when docstrings are missing. |
2225 | + @exported_as_webservice_entry() |
2226 | class IUndocumentedEntry(Interface): |
2227 | - export_as_webservice_entry() |
2228 | - |
2229 | a_field = exported(TextLine()) |
2230 | |
2231 | testmodule_objects = [ |
2232 | @@ -768,8 +770,9 @@ |
2233 | def make_entry(name): |
2234 | """Make an entity with some attibutes to expose as a web service.""" |
2235 | code = """ |
2236 | + @exported_as_webservice_entry(singular_name='%(name)s') |
2237 | class %(name)s(Interface): |
2238 | - export_as_webservice_entry(singular_name='%(name)s') |
2239 | + pass |
2240 | """ % locals() |
2241 | |
2242 | for letter in 'rstuvwxyz': |
2243 | @@ -816,9 +819,9 @@ |
2244 | class DuplicateSingularNameTestCase(DuplicateNameTestCase): |
2245 | """Test AssertionError when resource types share a singular name.""" |
2246 | |
2247 | + @exported_as_webservice_entry('generic_entry') |
2248 | class IDuplicate(Interface): |
2249 | """An entry that reuses the singular name of IGenericEntry.""" |
2250 | - export_as_webservice_entry('generic_entry') |
2251 | |
2252 | testmodule_objects = [IGenericEntry, IDuplicate] |
2253 | |
2254 | @@ -830,9 +833,9 @@ |
2255 | class DuplicatePluralNameTestCase(DuplicateNameTestCase): |
2256 | """Test AssertionERror when resource types share a plural name.""" |
2257 | |
2258 | + @exported_as_webservice_entry(plural_name='generic_entrys') |
2259 | class IDuplicate(Interface): |
2260 | """An entry that reuses the plural name of IGenericEntry.""" |
2261 | - export_as_webservice_entry(plural_name='generic_entrys') |
2262 | |
2263 | testmodule_objects = [IGenericEntry, IDuplicate] |
2264 | |
2265 | @@ -890,9 +893,9 @@ |
2266 | endInteraction() |
2267 | |
2268 | |
2269 | +@exported_as_webservice_entry() |
2270 | class ITestEntry(IEntry): |
2271 | """Interface for a test entry.""" |
2272 | - export_as_webservice_entry() |
2273 | |
2274 | |
2275 | @implementer(ITestEntry) |