Merge lp:~leonardr/lazr.restful/generate-multiversion-collections into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Approved by: Edwin Grubbs
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~leonardr/lazr.restful/generate-multiversion-collections
Merge into: lp:lazr.restful
Diff against target: 450 lines (+210/-63)
6 files modified
src/lazr/restful/declarations.py (+41/-15)
src/lazr/restful/docs/webservice-declarations.txt (+99/-12)
src/lazr/restful/example/multiversion/resources.py (+10/-1)
src/lazr/restful/example/multiversion/root.py (+2/-1)
src/lazr/restful/example/multiversion/tests/introduction.txt (+27/-7)
src/lazr/restful/metazcml.py (+31/-27)
To merge this branch: bzr merge lp:~leonardr/lazr.restful/generate-multiversion-collections
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Approve
Review via email: mp+18153@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

This branch makes the @collection_default_content annotation
version-aware. For different versions of the web service you can
specify different methods to be used as the collection's find()
implementation, and different values for that method's keyword
arguments.

I was able to refactor some code because this is the second branch
that makes our code generation version-aware. I also simplified the
implementation of register_adapter_for_version. Previously if the
version in question was None (indicating the earliest version), I
looked up which version was the earliest and then looked up its
version-specific marker interface. Now I simply use the generic web
service request interface, IWebServiceClientRequest. This prevents
test failures where all that changed is that a lookup that failed with
IWebServiceClientRequest needed to be changed to
IWebServiceBetaClientRequest.

Here are some questions I asked myself during development. They all had to do
with whether version differences were better solved with annotations
or left to navigation code.

Q: Do I need an analogue to @operation_removed_in_version?
A: No. If a collection disappears in a certain version, it needs to
   disappear from the navigation. Hiding the
   collection_default_content for that version won't solve anything.

   The reason we need @operation_removed_in_version is that lookup of
   named operation happens after the navigation is done. The
   navigation takes you to an entry or collection, and then the named
   operation lookup needs to succeed or fail depending on the version.

   If it turns out to be too much trouble to change the navigation, we
   could create a @collection_removed_in_version and make the
   lazr.restful behavior to raise a 404 instead of calling find(), but
   for now I say YAGNI.

Q: What happens if a collection changes its name between versions?
A: That, too is a navigation issue. And again, we had to deal with it
   specially for named operations because operation lookup is not
   navigation.

Q: What happens if a collection doesn't have a
   @collection_default_content for the earliest version? Don't I
   need to prevent that?
A: That corresponds to a case where the collection didn't exist in the
   earliest version and was added later. This is a legitimate case,
   and it will work fine if accompanied by appropriate navigation
   code.

Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (5.4 KiB)

Hi Leonard,

This is a nice refactoring and improvement. I only have minor comments below.

merge-conditional

-Edwin

>=== modified file 'src/lazr/restful/declarations.py'
>--- src/lazr/restful/declarations.py 2010-01-26 20:05:25 +0000
>+++ src/lazr/restful/declarations.py 2010-01-27 22:49:27 +0000
>@@ -202,7 +202,7 @@
> collection, or if used more than once in the same interface.
> """
>
>- def __init__(self, **params):
>+ def __init__(self, version=None, **params):
> """Create the decorator marking the default collection method.
>
> :param params: Optional parameter values to use when calling the

The docstring should explain how the version parameter should be used.

>@@ -891,20 +898,26 @@
> return method(**params)
>
>
>-def generate_collection_adapter(interface):
>+def generate_collection_adapter(interface, version=None):
> """Create a class adapting from interface to ICollection."""
> _check_tagged_interface(interface, 'collection')
>
> tag = interface.getTaggedValue(LAZR_WEBSERVICE_EXPORTED)
>- method_name = tag['collection_default_content']
>+ default_content_by_version = tag['collection_default_content']
>+ assert (version in default_content_by_version), (
>+ "'%s' isn't tagged for export to web service "
>+ "version '%s'." % (interface.__name__, version))
>+ method_name, params = default_content_by_version[version]
> entry_schema = tag['collection_entry_schema']
> class_dict = {
> 'entry_schema' : CollectionEntrySchema(entry_schema),
>- 'method_name': tag['collection_default_content'],
>- 'params': tag['collection_default_content_params'],
>+ 'method_name': method_name,
>+ 'params': params,
> '__doc__': interface.__doc__,
> }
>- classname = "%sCollectionAdapter" % interface.__name__[1:]
>+ classname =_versioned_class_name(

s/=_/= _/

>+ "%sCollectionAdapter" % interface.__name__[1:],
>+ version)
> factory = type(classname, (BaseCollectionAdapter,), class_dict)
>
> protect_schema(factory, ICollection)
>@@ -1016,8 +1029,9 @@
> if return_type is None:
> return_type = None
>
>- name = '%s_%s_%s_%s' % (prefix, method.interface.__name__, tag['as'],
>- version)
>+ name = _versioned_class_name(
>+ '%s_%s_%s' % (prefix, method.interface.__name__, tag['as']),
>+ version)
> class_dict = {'params' : tuple(tag['params'].values()),
> 'return_type' : return_type,
> '_export_info': tag,
>@@ -1026,8 +1040,20 @@
>
> if tag['type'] == 'write_operation':
> class_dict['send_modification_event'] = True
>- factory = type(make_identifier_safe(name), bases, class_dict)
>+ factory = type(name, bases, class_dict)
> classImplements(factory, provides)
> protect_schema(factory, provides)
>
> return factory
>+
>+
>+def _versioned_class_name(base_name, version):
>+ """Create a class name incorporating the given version string."""
>+ if version is None:
>+ # We need to incorporate the version into a Python class name,
>+ # but we won't find out t...

Read more...

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-01-26 20:05:25 +0000
+++ src/lazr/restful/declarations.py 2010-01-27 18:14:15 +0000
@@ -202,7 +202,7 @@
202 collection, or if used more than once in the same interface.202 collection, or if used more than once in the same interface.
203 """203 """
204204
205 def __init__(self, **params):205 def __init__(self, version=None, **params):
206 """Create the decorator marking the default collection method.206 """Create the decorator marking the default collection method.
207207
208 :param params: Optional parameter values to use when calling the208 :param params: Optional parameter values to use when calling the
@@ -218,19 +218,26 @@
218 "@collection_default_content can only be used from within an "218 "@collection_default_content can only be used from within an "
219 "interface exported as a collection.")219 "interface exported as a collection.")
220220
221 if 'collection_default_content' in tag:221 default_content_methods = tag.setdefault(
222 'collection_default_content', {})
223
224 if version in default_content_methods:
225 if version is None:
226 version = "(earliest)"
222 raise TypeError(227 raise TypeError(
223 "only one method should be marked with "228 "Only one method can be marked with "
224 "@collection_default_content.")229 "@collection_default_content for version %s." % version)
225230 self.version = version
226 tag['collection_default_content_params'] = params231 self.params = params
227232
228 def __call__(self, f):233 def __call__(self, f):
229 """Annotates the collection with the name of the method to call."""234 """Annotates the collection with the name of the method to call."""
230 tag = _get_interface_tags()[LAZR_WEBSERVICE_EXPORTED]235 tag = _get_interface_tags()[LAZR_WEBSERVICE_EXPORTED]
231 tag['collection_default_content'] = f.__name__236 tag['collection_default_content'][self.version] = (
237 f.__name__, self.params)
232 return f238 return f
233239
240
234WEBSERVICE_ERROR = '__lazr_webservice_error__'241WEBSERVICE_ERROR = '__lazr_webservice_error__'
235242
236def webservice_error(status):243def webservice_error(status):
@@ -891,20 +898,26 @@
891 return method(**params)898 return method(**params)
892899
893900
894def generate_collection_adapter(interface):901def generate_collection_adapter(interface, version=None):
895 """Create a class adapting from interface to ICollection."""902 """Create a class adapting from interface to ICollection."""
896 _check_tagged_interface(interface, 'collection')903 _check_tagged_interface(interface, 'collection')
897904
898 tag = interface.getTaggedValue(LAZR_WEBSERVICE_EXPORTED)905 tag = interface.getTaggedValue(LAZR_WEBSERVICE_EXPORTED)
899 method_name = tag['collection_default_content']906 default_content_by_version = tag['collection_default_content']
907 assert (version in default_content_by_version), (
908 "'%s' isn't tagged for export to web service "
909 "version '%s'." % (interface.__name__, version))
910 method_name, params = default_content_by_version[version]
900 entry_schema = tag['collection_entry_schema']911 entry_schema = tag['collection_entry_schema']
901 class_dict = {912 class_dict = {
902 'entry_schema' : CollectionEntrySchema(entry_schema),913 'entry_schema' : CollectionEntrySchema(entry_schema),
903 'method_name': tag['collection_default_content'],914 'method_name': method_name,
904 'params': tag['collection_default_content_params'],915 'params': params,
905 '__doc__': interface.__doc__,916 '__doc__': interface.__doc__,
906 }917 }
907 classname = "%sCollectionAdapter" % interface.__name__[1:]918 classname =_versioned_class_name(
919 "%sCollectionAdapter" % interface.__name__[1:],
920 version)
908 factory = type(classname, (BaseCollectionAdapter,), class_dict)921 factory = type(classname, (BaseCollectionAdapter,), class_dict)
909922
910 protect_schema(factory, ICollection)923 protect_schema(factory, ICollection)
@@ -1016,8 +1029,9 @@
1016 if return_type is None:1029 if return_type is None:
1017 return_type = None1030 return_type = None
10181031
1019 name = '%s_%s_%s_%s' % (prefix, method.interface.__name__, tag['as'],1032 name = _versioned_class_name(
1020 version)1033 '%s_%s_%s' % (prefix, method.interface.__name__, tag['as']),
1034 version)
1021 class_dict = {'params' : tuple(tag['params'].values()),1035 class_dict = {'params' : tuple(tag['params'].values()),
1022 'return_type' : return_type,1036 'return_type' : return_type,
1023 '_export_info': tag,1037 '_export_info': tag,
@@ -1026,8 +1040,20 @@
10261040
1027 if tag['type'] == 'write_operation':1041 if tag['type'] == 'write_operation':
1028 class_dict['send_modification_event'] = True1042 class_dict['send_modification_event'] = True
1029 factory = type(make_identifier_safe(name), bases, class_dict)1043 factory = type(name, bases, class_dict)
1030 classImplements(factory, provides)1044 classImplements(factory, provides)
1031 protect_schema(factory, provides)1045 protect_schema(factory, provides)
10321046
1033 return factory1047 return factory
1048
1049
1050def _versioned_class_name(base_name, version):
1051 """Create a class name incorporating the given version string."""
1052 if version is None:
1053 # We need to incorporate the version into a Python class name,
1054 # but we won't find out the name of the earliest version until
1055 # runtime. Use a generic string that won't conflict with a
1056 # real version string.
1057 version = "__Earliest"
1058 name = "%s_%s" % (base_name, version)
1059 return make_identifier_safe(name)
10341060
=== modified file 'src/lazr/restful/docs/webservice-declarations.txt'
--- src/lazr/restful/docs/webservice-declarations.txt 2010-01-25 19:26:30 +0000
+++ src/lazr/restful/docs/webservice-declarations.txt 2010-01-27 18:14:15 +0000
@@ -170,15 +170,14 @@
170tagged value.170tagged value.
171171
172 >>> print_export_tag(IBookSet)172 >>> print_export_tag(IBookSet)
173 collection_default_content: 'getAllBooks'173 collection_default_content: {None: ('getAllBooks', {})}
174 collection_default_content_params: {}
175 collection_entry_schema: <InterfaceClass __builtin__.IBook>174 collection_entry_schema: <InterfaceClass __builtin__.IBook>
176 type: 'collection'175 type: 'collection'
177176
178 >>> print_export_tag(ICheckedOutBookSet)177 >>> print_export_tag(ICheckedOutBookSet)
179 collection_default_content: 'getByTitle'178 collection_default_content: {None: ('getByTitle',
180 collection_default_content_params: {'title': '',179 {'title': '',
181 'user': <object...>}180 'user': <object object ...>})}
182 collection_entry_schema: <InterfaceClass __builtin__.IBook>181 collection_entry_schema: <InterfaceClass __builtin__.IBook>
183 type: 'collection'182 type: 'collection'
184183
@@ -223,8 +222,8 @@
223 ... """Another getAll()."""222 ... """Another getAll()."""
224 Traceback (most recent call last):223 Traceback (most recent call last):
225 ...224 ...
226 TypeError: only one method should be marked with225 TypeError: Only one method can be marked with
227 @collection_default_content.226 @collection_default_content for version (earliest).
228227
229export_as_webservice_collection() can only be used on Interface.228export_as_webservice_collection() can only be used on Interface.
230229
@@ -1452,7 +1451,6 @@
1452It is possible to cache a server response in the browser cache using1451It is possible to cache a server response in the browser cache using
1453the @cache_for decorator:1452the @cache_for decorator:
14541453
1455 >>> from lazr.restful.testing.webservice import FakeRequest
1456 >>> from lazr.restful.declarations import cache_for1454 >>> from lazr.restful.declarations import cache_for
1457 >>>1455 >>>
1458 >>> class ICachedBookSet(IBookSet):1456 >>> class ICachedBookSet(IBookSet):
@@ -1472,14 +1470,13 @@
1472 ...1470 ...
1473 ... def getAllBooks(self):1471 ... def getAllBooks(self):
1474 ... return self.books1472 ... return self.books
1475 >>>1473
1476 >>> request = FakeRequest()
1477 >>> read_method_adapter_factory = generate_operation_adapter(1474 >>> read_method_adapter_factory = generate_operation_adapter(
1478 ... ICachedBookSet['getAllBooks'])1475 ... ICachedBookSet['getAllBooks'])
1479 >>> read_method_adapter = read_method_adapter_factory(1476 >>> read_method_adapter = read_method_adapter_factory(
1480 ... CachedBookSet(['Cool book']), request)1477 ... CachedBookSet(['Cool book']), request)
1481 >>> print read_method_adapter.call()1478 >>> print read_method_adapter.call()
1482 ["Cool book"]1479 ['Cool book']
1483 >>> print request.response.headers1480 >>> print request.response.headers
1484 {'Content-Type': 'application/json', 'Cache-control': 'max-age=60'}1481 {'Content-Type': 'application/json', 'Cache-control': 'max-age=60'}
14851482
@@ -1509,6 +1506,96 @@
1509Different versions of the webservice can publish the same data model1506Different versions of the webservice can publish the same data model
1510object in totally different ways.1507object in totally different ways.
15111508
1509Collections
1510-----------
1511
1512A collection's contents are determined by calling one of its
1513methods. Which method is called, and with which arguments, can vary
1514across versions.
1515
1516 >>> from lazr.restful.declarations import generate_operation_adapter
1517
1518 >>> class IMultiVersionCollection(Interface):
1519 ... export_as_webservice_collection(Interface)
1520 ...
1521 ... @collection_default_content('2.0')
1522 ... def content_20():
1523 ... """The content method for version 2.0."""
1524 ...
1525 ... @collection_default_content('1.0', argument='1.0 value')
1526 ... @collection_default_content(argument='pre-1.0 value')
1527 ... def content_pre_20(argument):
1528 ... """The content method for versions before 2.0"""
1529
1530Here's a simple implementation of IMultiVersionCollection. It'll
1531illustrate how the different versions of the web service invoke
1532different methods to find the collection contents.
1533
1534 >>> class MultiVersionCollection():
1535 ... """Simple IMultiVersionCollection implementation."""
1536 ... implements(IMultiVersionCollection)
1537 ...
1538 ... def content_20(self):
1539 ... return ["contents", "for", "version", "2.0"]
1540 ...
1541 ... def content_pre_20(self, argument):
1542 ... return ["you", "passed", "in", argument]
1543
1544By passing a version string into generate_collection_adapter(), we can
1545get different adapter classes for different versions of the web
1546service. We'll be invoking each version against the same data model
1547object. Here it is:
1548
1549 >>> data_object = MultiVersionCollection()
1550
1551Passing in None to generate_collection_adapter gets us the collection
1552as it appears in the earliest version of the web service. The
1553content_pre_20() method is invoked with the 'argument' parameter equal
1554to "pre-1.0 value".
1555
1556 >>> interface = IMultiVersionCollection
1557 >>> adapter_earliest_factory = generate_collection_adapter(
1558 ... interface, None)
1559 >>> print adapter_earliest_factory.__name__
1560 MultiVersionCollectionCollectionAdapter___Earliest
1561
1562 >>> collection_earliest = adapter_earliest_factory(data_object, request)
1563 >>> print collection_earliest.find()
1564 ['you', 'passed', 'in', 'pre-1.0 value']
1565
1566Passing in '1.0' gets us the collection as it appears in the 1.0
1567version of the web service. Note that the argument passed in to
1568content_pre_20() is different, and so the returned contents are
1569slightly different.
1570
1571 >>> adapter_10_factory = generate_collection_adapter(interface, '1.0')
1572 >>> print adapter_10_factory.__name__
1573 MultiVersionCollectionCollectionAdapter_1_0
1574
1575 >>> collection_10 = adapter_10_factory(data_object, request)
1576 >>> print collection_10.find()
1577 ['you', 'passed', 'in', '1.0 value']
1578
1579Passing in '2.0' gets us a collection with totally different contents,
1580because a totally different method is being called.
1581
1582 >>> adapter_20_factory = generate_collection_adapter(interface, '2.0')
1583 >>> print adapter_20_factory.__name__
1584 MultiVersionCollectionCollectionAdapter_2_0
1585
1586 >>> collection_20 = adapter_20_factory(data_object, request)
1587 >>> print collection_20.find()
1588 ['contents', 'for', 'version', '2.0']
1589
1590An error occurs when we try to generate an adapter for a version
1591that's not mentioned in the annotations.
1592
1593 >>> generate_collection_adapter(interface, 'NoSuchVersion')
1594 Traceback (most recent call last):
1595 ...
1596 AssertionError: 'IMultiVersionCollection' isn't tagged for export
1597 to web service version 'NoSuchVersion'.
1598
1512Named operations1599Named operations
1513----------------1600----------------
15141601
@@ -1865,8 +1952,8 @@
1865 >>> from zope.component import getGlobalSiteManager, getUtility1952 >>> from zope.component import getGlobalSiteManager, getUtility
1866 >>> adapter_registry = getGlobalSiteManager().adapters1953 >>> adapter_registry = getGlobalSiteManager().adapters
18671954
1868 >>> request_interface = getUtility(IWebServiceVersion, name='beta')
1869 >>> from lazr.restful.interfaces import IWebServiceClientRequest1955 >>> from lazr.restful.interfaces import IWebServiceClientRequest
1956 >>> request_interface = IWebServiceClientRequest
1870 >>> adapter_registry.lookup(1957 >>> adapter_registry.lookup(
1871 ... (IBookSetOnSteroids, request_interface),1958 ... (IBookSetOnSteroids, request_interface),
1872 ... IResourceGETOperation, 'searchBooks')1959 ... IResourceGETOperation, 'searchBooks')
18731960
=== modified file 'src/lazr/restful/example/multiversion/resources.py'
--- src/lazr/restful/example/multiversion/resources.py 2010-01-21 18:26:58 +0000
+++ src/lazr/restful/example/multiversion/resources.py 2010-01-27 18:14:15 +0000
@@ -31,10 +31,17 @@
31class IPairSet(ILocation):31class IPairSet(ILocation):
32 export_as_webservice_collection(IKeyValuePair)32 export_as_webservice_collection(IKeyValuePair)
3333
34 @collection_default_content()34 # In versions 2.0 and 3.0, the collection of key-value pairs
35 # includes all pairs.
36 @collection_default_content("2.0")
35 def getPairs():37 def getPairs():
36 """Return the key-value pairs."""38 """Return the key-value pairs."""
3739
40 # Before 2.0, it only includes pairs whose values are not None.
41 @collection_default_content('beta')
42 def getNonEmptyPairs():
43 """Return the key-value pairs that don't map to None."""
44
38 def get(request, name):45 def get(request, name):
39 """Retrieve a key-value pair by its key."""46 """Retrieve a key-value pair by its key."""
4047
@@ -59,6 +66,8 @@
59 def find_for_value(self, value):66 def find_for_value(self, value):
60 return [pair for pair in self.pairs if value == pair.value]67 return [pair for pair in self.pairs if value == pair.value]
6168
69 def getNonEmptyPairs(self):
70 return [pair for pair in self.pairs if pair.value is not None]
6271
63class KeyValuePair(BasicKeyValuePair):72class KeyValuePair(BasicKeyValuePair):
64 implements(IKeyValuePair)73 implements(IKeyValuePair)
6574
=== modified file 'src/lazr/restful/example/multiversion/root.py'
--- src/lazr/restful/example/multiversion/root.py 2010-01-21 17:57:02 +0000
+++ src/lazr/restful/example/multiversion/root.py 2010-01-27 18:14:15 +0000
@@ -46,7 +46,8 @@
46 pairset = PairSet()46 pairset = PairSet()
47 pairset.pairs = [47 pairset.pairs = [
48 KeyValuePair(pairset, "foo", "bar"),48 KeyValuePair(pairset, "foo", "bar"),
49 KeyValuePair(pairset, "1", "2")49 KeyValuePair(pairset, "1", "2"),
50 KeyValuePair(pairset, "Some", None)
50 ]51 ]
51 collections = dict(pairs=(IKeyValuePair, pairset))52 collections = dict(pairs=(IKeyValuePair, pairset))
52 return collections, {}53 return collections, {}
5354
=== modified file 'src/lazr/restful/example/multiversion/tests/introduction.txt'
--- src/lazr/restful/example/multiversion/tests/introduction.txt 2010-01-21 20:07:54 +0000
+++ src/lazr/restful/example/multiversion/tests/introduction.txt 2010-01-27 18:14:15 +0000
@@ -71,13 +71,33 @@
71Collections and entries71Collections and entries
72=======================72=======================
7373
74The web service presents a single collection of key-value pairs.74The web service presents a single collection of key-value pairs. In
7575versions previous to 2.0, the collection omits key-value pairs where
76 >>> body = webservice.get('/pairs').jsonBody()76the value is None. In 2.0 and 3.0, all key-value pairs are published.
77 >>> for entry in body['entries']:77
78 ... print entry['self_link'], entry['key'], entry['value']78 >>> from operator import itemgetter
79 http://multiversion.dev/3.0/pairs/foo foo bar79 >>> def show_pairs(version):
80 http://multiversion.dev/3.0/pairs/1 1 280 ... body = webservice.get('/pairs', api_version=version).jsonBody()
81 ... for entry in sorted(body['entries'], key=itemgetter('key')):
82 ... print "%s: %s" % (entry['key'], entry['value'])
83
84 >>> show_pairs('beta')
85 1: 2
86 foo: bar
87
88 >>> show_pairs('1.0')
89 1: 2
90 foo: bar
91
92 >>> show_pairs('2.0')
93 1: 2
94 Some: None
95 foo: bar
96
97 >>> show_pairs('3.0')
98 1: 2
99 Some: None
100 foo: bar
81101
82 >>> body = webservice.get('/pairs/foo').jsonBody()102 >>> body = webservice.get('/pairs/foo').jsonBody()
83 >>> print body['key'], body['value']103 >>> print body['key'], body['value']
84104
=== modified file 'src/lazr/restful/metazcml.py'
--- src/lazr/restful/metazcml.py 2010-01-25 19:51:11 +0000
+++ src/lazr/restful/metazcml.py 2010-01-27 18:14:15 +0000
@@ -52,23 +52,21 @@
52 calls Zope's handler('registerAdapter').52 calls Zope's handler('registerAdapter').
53 """53 """
54 if version_name is None:54 if version_name is None:
55 # When we were processing annotations we didn't know the name55 # This adapter is for the earliest supported version. Register
56 # of the earliest supported version. We know this now.56 # it against the generic IWebServiceClientRequest interface,
57 utility = getUtility(IWebServiceConfiguration)57 # which is the superclass of the marker interfaces for every
58 if len(utility.active_versions) > 0:58 # specific version.
59 version_name = utility.active_versions[0]59 marker = IWebServiceClientRequest
60 else:60 else:
61 # This service only publishes a 'development' version.61 # Look up the marker interface for the given version. This
62 version_name = utility.latest_version_uri_prefix62 # will also ensure the given version string has an
63 # Make sure the given version string has an63 # IWebServiceVersion utility registered for it, and is not
64 # IWebServiceVersion utility registered for it, and is not64 # just a random string.
65 # just a random string.65 marker = getUtility(IWebServiceVersion, name=version_name)
66 marker = getUtility(IWebServiceVersion, name=version_name)
6766
68 handler('registerAdapter', factory, (interface, marker),67 handler('registerAdapter', factory, (interface, marker),
69 provides, name, info)68 provides, name, info)
7069
71
72def find_exported_interfaces(module):70def find_exported_interfaces(module):
73 """Find all the interfaces in a module marked for export.71 """Find all the interfaces in a module marked for export.
7472
@@ -111,23 +109,29 @@
111 web_interface = generate_entry_interface(interface)109 web_interface = generate_entry_interface(interface)
112 factory = generate_entry_adapter(interface, web_interface)110 factory = generate_entry_adapter(interface, web_interface)
113 provides = IEntry111 provides = IEntry
112 context.action(
113 discriminator=('adapter', interface, provides, ''),
114 callable=handler,
115 # XXX leonardr bug=503948 Refactor this code once both
116 # entries and collections are multiversion.
117 args=('registerAdapter',
118 factory, (interface, IWebServiceClientRequest),
119 provides, '', context.info),
120 )
114 elif tag['type'] == 'collection':121 elif tag['type'] == 'collection':
115 factory = generate_collection_adapter(interface)122 for version in tag['collection_default_content'].keys():
116 provides = ICollection123 factory = generate_collection_adapter(interface, version)
124 provides = ICollection
125 context.action(
126 discriminator=(
127 'webservice versioned adapter', interface, provides,
128 '', version),
129 callable=register_adapter_for_version,
130 args=(factory, interface, version, provides, '',
131 context.info),
132 )
117 else:133 else:
118 raise AssertionError('Unknown export type: %s' % tag['type'])134 raise AssertionError('Unknown export type: %s' % tag['type'])
119 context.action(
120 discriminator=('adapter', interface, provides, ''),
121 callable=handler,
122 # XXX leonardr bug=503948 Register the adapter against a
123 # generic IWebServiceClientRequest. It will be picked up
124 # for all versions of the web service. Later on, this will
125 # be changed to register different adapters for different
126 # versions.
127 args=('registerAdapter',
128 factory, (interface, IWebServiceClientRequest),
129 provides, '', context.info),
130 )
131 register_webservice_operations(context, interface)135 register_webservice_operations(context, interface)
132136
133137

Subscribers

People subscribed via source and target branches