Merge lp:~leonardr/lazr.restful/register-operations-when-version-list-is-known into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Merged at revision: not available
Proposed branch: lp:~leonardr/lazr.restful/register-operations-when-version-list-is-known
Merge into: lp:lazr.restful
Diff against target: 226 lines (+36/-66)
3 files modified
src/lazr/restful/declarations.py (+1/-5)
src/lazr/restful/docs/webservice-declarations.txt (+19/-19)
src/lazr/restful/metazcml.py (+16/-42)
To merge this branch: bzr merge lp:~leonardr/lazr.restful/register-operations-when-version-list-is-known
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+19985@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

The web service registration code runs in the first stage of ZCML processing. Originally, the adapter classes for entries, collections, and named operations were all generated in this first stage. Then came the multi-version code, and there was no way to generate a set of entries without knowing the list of active versions published by the web site.

Unfortunately, the list of active versions is a property of the IWebServiceConfiguration utility, and utilities are not available in the first stage of ZCML processing. So I wrote code that used context.action() to defer the entry generation code until the second stage of ZCML processing, when utilities are available.

The project I'm working on now (no longer exporting mutator methods as named operations) requires that the list of active versions be available when generating the adapters for named operations. This branch does the same trick for named operations as I did earlier for entries: uses context.action() to defer the generation code until the second stage of ZCML processing.

A side effect of this is that when naming the adapters for named operations, we can use the actual name of the first version instead of "__Earliest". I got rid of almost all instances of "__Earliest" in the tests. (There was one I couldn't get rid of, and since that's not the point of my project and it wasn't hurting anything, I've left it alone for now.)

Revision history for this message
Brad Crittenden (bac) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/lazr/restful/declarations.py'
--- src/lazr/restful/declarations.py 2010-02-18 15:17:09 +0000
+++ src/lazr/restful/declarations.py 2010-02-23 17:50:32 +0000
@@ -1162,11 +1162,7 @@
1162 "version '%s'" % (method.__name__, version))1162 "version '%s'" % (method.__name__, version))
1163 tag = match[0]1163 tag = match[0]
1164 if version is None:1164 if version is None:
1165 # We need to incorporate the version into a Python class name,1165 version = getUtility(IWebServiceConfiguration).active_versions[0]
1166 # but we won't find out the name of the earliest version until
1167 # runtime. Use a generic string that won't conflict with a
1168 # real version string.
1169 version = "__Earliest"
11701166
1171 bases = (BaseResourceOperationAdapter, )1167 bases = (BaseResourceOperationAdapter, )
1172 if tag['type'] == 'read_operation':1168 if tag['type'] == 'read_operation':
11731169
=== modified file 'src/lazr/restful/docs/webservice-declarations.txt'
--- src/lazr/restful/docs/webservice-declarations.txt 2010-02-16 16:14:01 +0000
+++ src/lazr/restful/docs/webservice-declarations.txt 2010-02-23 17:50:32 +0000
@@ -1060,15 +1060,15 @@
1060 >>> IResourceGETOperation.implementedBy(read_method_adapter_factory)1060 >>> IResourceGETOperation.implementedBy(read_method_adapter_factory)
1061 True1061 True
10621062
1063The defined adapter is named GET_<interface>_<exported_name>___Earliest1063The defined adapter is named GET_<interface>_<exported_name>_beta
1064and uses the ResourceOperation base class. The "___Earliest" indicates1064and uses the ResourceOperation base class. The "_beta" indicates
1065that the adapter will be used in the earliest version of the web1065that the adapter will be used in the earliest version of the web
1066service, and any subsequent versions, until a newer implementation1066service, and any subsequent versions, until a newer implementation
1067supercedes it.1067supercedes it.
10681068
1069 >>> from lazr.restful import ResourceOperation1069 >>> from lazr.restful import ResourceOperation
1070 >>> read_method_adapter_factory.__name__1070 >>> read_method_adapter_factory.__name__
1071 'GET_IBookSetOnSteroids_searchBooks___Earliest'1071 'GET_IBookSetOnSteroids_searchBooks_beta'
1072 >>> issubclass(read_method_adapter_factory, ResourceOperation)1072 >>> issubclass(read_method_adapter_factory, ResourceOperation)
1073 True1073 True
10741074
@@ -1129,10 +1129,10 @@
1129 >>> IResourcePOSTOperation.implementedBy(write_method_adapter_factory)1129 >>> IResourcePOSTOperation.implementedBy(write_method_adapter_factory)
1130 True1130 True
11311131
1132The generated adapter class name is POST_<interface>_<operation>___Earliest.1132The generated adapter class name is POST_<interface>_<operation>_beta.
11331133
1134 >>> print write_method_adapter_factory.__name__1134 >>> print write_method_adapter_factory.__name__
1135 POST_IBookOnSteroids_checkout___Earliest1135 POST_IBookOnSteroids_checkout_beta
11361136
1137The adapter's params property also contains the available parameters1137The adapter's params property also contains the available parameters
1138(for which there are none in this case.)1138(for which there are none in this case.)
@@ -1178,10 +1178,10 @@
1178 False1178 False
11791179
1180The generated adapter class name is also1180The generated adapter class name is also
1181POST_<interface>_<operation>___Earliest.1181POST_<interface>_<operation>_beta.
11821182
1183 >>> print write_method_adapter_factory.__name__1183 >>> print write_method_adapter_factory.__name__
1184 POST_IBookOnSteroids_checkout___Earliest1184 POST_IBookOnSteroids_checkout_beta
11851185
1186The adapter's params property also contains the available parameters.1186The adapter's params property also contains the available parameters.
11871187
@@ -1253,10 +1253,10 @@
1253 True1253 True
12541254
1255The generated adapter class name is1255The generated adapter class name is
1256DELETE_<interface>_<operation>___Earliest.1256DELETE_<interface>_<operation>_beta.
12571257
1258 >>> print destructor_method_adapter_factory.__name__1258 >>> print destructor_method_adapter_factory.__name__
1259 DELETE_IBookOnSteroids_destroy___Earliest1259 DELETE_IBookOnSteroids_destroy_beta
12601260
12611261
1262=== Destructor ===1262=== Destructor ===
@@ -2038,7 +2038,7 @@
2038 >>> method = IMultiVersionMethod['a_method']2038 >>> method = IMultiVersionMethod['a_method']
2039 >>> adapter_earliest_factory = generate_operation_adapter(method, None)2039 >>> adapter_earliest_factory = generate_operation_adapter(method, None)
2040 >>> print adapter_earliest_factory.__name__2040 >>> print adapter_earliest_factory.__name__
2041 GET_IMultiVersionMethod_a_method___Earliest2041 GET_IMultiVersionMethod_a_method_beta
20422042
2043 >>> method_earliest = adapter_earliest_factory(data_object, request)2043 >>> method_earliest = adapter_earliest_factory(data_object, request)
2044 >>> print method_earliest.call(required="foo")2044 >>> print method_earliest.call(required="foo")
@@ -2537,15 +2537,15 @@
2537 >>> adapter_registry.lookup(2537 >>> adapter_registry.lookup(
2538 ... (IBookSetOnSteroids, request_interface),2538 ... (IBookSetOnSteroids, request_interface),
2539 ... IResourceGETOperation, 'searchBooks')2539 ... IResourceGETOperation, 'searchBooks')
2540 <class '...GET_IBookSetOnSteroids_searchBooks___Earliest'>2540 <class '...GET_IBookSetOnSteroids_searchBooks_beta'>
2541 >>> adapter_registry.lookup(2541 >>> adapter_registry.lookup(
2542 ... (IBookSetOnSteroids, request_interface),2542 ... (IBookSetOnSteroids, request_interface),
2543 ... IResourcePOSTOperation, 'create_book')2543 ... IResourcePOSTOperation, 'create_book')
2544 <class '...POST_IBookSetOnSteroids_create_book___Earliest'>2544 <class '...POST_IBookSetOnSteroids_create_book_beta'>
2545 >>> adapter_registry.lookup(2545 >>> adapter_registry.lookup(
2546 ... (IBookOnSteroids, request_interface),2546 ... (IBookOnSteroids, request_interface),
2547 ... IResourcePOSTOperation, 'checkout')2547 ... IResourcePOSTOperation, 'checkout')
2548 <class '...POST_IBookOnSteroids_checkout___Earliest'>2548 <class '...POST_IBookOnSteroids_checkout_beta'>
25492549
2550There is also a 'index.html' view on the IWebServiceClientRequest2550There is also a 'index.html' view on the IWebServiceClientRequest
2551registered for the InvalidEmail exception.2551registered for the InvalidEmail exception.
@@ -2603,17 +2603,17 @@
2603 ... export_as_webservice_entry()2603 ... export_as_webservice_entry()
2604 ... @operation_parameters(arg=TextLine())2604 ... @operation_parameters(arg=TextLine())
2605 ... @export_operation_as('already_been_removed')2605 ... @export_operation_as('already_been_removed')
2606 ... @operation_removed_in_version("1.0")2606 ... @operation_removed_in_version("2.0")
2607 ... @operation_parameters(arg=Float())2607 ... @operation_parameters(arg=Float())
2608 ... @export_read_operation()2608 ... @export_read_operation()
2609 ... @operation_for_version("2.0")2609 ... @operation_for_version("1.0")
2610 ... def method(arg):2610 ... def method(arg):
2611 ... """A method."""2611 ... """A method."""
26122612
2613 >>> register_test_module('annotatingremoved', AnnotatingARemovedMethod)2613 >>> register_test_module('annotatingremoved', AnnotatingARemovedMethod)
2614 Traceback (most recent call last):2614 Traceback (most recent call last):
2615 ...2615 ...
2616 ZopeXMLConfigurationError: ...2616 ConfigurationExecutionError: ... Method "method" contains
2617 AssertionError: Method "method" contains annotations for version2617 annotations for version "2.0", even though it's not published in
2618 "1.0", even though it's not published in that version. The bad2618 that version. The bad annotations are: "params", "as".
2619 annotations are: "params", "as".2619 ...
26202620
=== modified file 'src/lazr/restful/metazcml.py'
--- src/lazr/restful/metazcml.py 2010-02-15 02:24:19 +0000
+++ src/lazr/restful/metazcml.py 2010-02-23 17:50:32 +0000
@@ -201,10 +201,13 @@
201 )201 )
202 else:202 else:
203 raise AssertionError('Unknown export type: %s' % tag['type'])203 raise AssertionError('Unknown export type: %s' % tag['type'])
204 register_webservice_operations(context, interface)204 context.action(
205205 discriminator=('webservice versioned operations', interface),
206206 args=(context, interface),
207def register_webservice_operations(context, interface):207 callable=generate_and_register_webservice_operations)
208
209
210def generate_and_register_webservice_operations(context, interface):
208 """Create and register adapters for all exported methods.211 """Create and register adapters for all exported methods.
209212
210 Different versions of the web service may publish the same213 Different versions of the web service may publish the same
@@ -218,11 +221,8 @@
218221
219 # First, make sure that this method was annotated with the222 # First, make sure that this method was annotated with the
220 # versions in the right order.223 # versions in the right order.
221 context.action(224 ensure_correct_version_ordering(
222 discriminator=('webservice version ordering', interface, method),225 interface.__name__ + '.' + method.__name__, tag.dict_names)
223 callable=ensure_correct_version_ordering,
224 args=(interface.__name__ + '.' + method.__name__, tag.dict_names)
225 )
226226
227 operation_name = None227 operation_name = None
228 # If an operation's name does not change between version n and228 # If an operation's name does not change between version n and
@@ -294,21 +294,19 @@
294 # version, or if the operation was removed in this294 # version, or if the operation was removed in this
295 # version, we need to block lookups of the previous name295 # version, we need to block lookups of the previous name
296 # from working.296 # from working.
297 check = (previous_operation_name, previous_operation_provides,
298 operation_name, operation_provides, version, factory)
299 if (operation_name != previous_operation_name297 if (operation_name != previous_operation_name
300 and previous_operation_name is not None):298 and previous_operation_name is not None):
301 _add_versioned_adapter_action(299 register_adapter_for_version(
302 context, interface, previous_operation_provides,300 _mask_adapter_registration, interface, version,
303 previous_operation_name, version,301 previous_operation_provides, previous_operation_name,
304 _mask_adapter_registration)302 context.info)
305303
306 # If the operation exists in this version (ie. its name is304 # If the operation exists in this version (ie. its name is
307 # not None), register it using this version's name.305 # not None), register it using this version's name.
308 if operation_name is not None:306 if operation_name is not None:
309 _add_versioned_adapter_action(307 register_adapter_for_version(
310 context, interface, operation_provides, operation_name,308 factory, interface, version, operation_provides,
311 version, factory)309 operation_name, context.info)
312 previous_operation_name = operation_name310 previous_operation_name = operation_name
313 previous_operation_provides = operation_provides311 previous_operation_provides = operation_provides
314312
@@ -322,30 +320,6 @@
322 return None320 return None
323321
324322
325def _add_versioned_adapter_action(
326 context, interface, provides, name, version, factory):
327 """Helper function to register a versioned operation factory.
328
329 :param context: The context on which to add a registration action.
330 :param interface: The IEntry subclass that publishes the named
331 operation.
332 :param provides: The IResourceOperation subclass provided by the
333 factory.
334 :param name: The name of the named operation in this version.
335 :param version: The version of the web service that publishes this
336 named operation.
337 :param factory: The object that handles invocations of the named
338 operation.
339 """
340 context.action(
341 discriminator=(
342 'webservice versioned adapter',
343 (interface, IWebServiceClientRequest), provides, name, version),
344 callable=register_adapter_for_version,
345 args=(factory, interface, version, provides, name, context.info),
346 )
347
348
349def register_exception_view(context, exception):323def register_exception_view(context, exception):
350 """Register WebServiceExceptionView to handle exception on the webservice.324 """Register WebServiceExceptionView to handle exception on the webservice.
351 """325 """

Subscribers

People subscribed via source and target branches