Merge lp:~leonardr/lazr.restful/bump-version into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Merged at revision: not available
Proposed branch: lp:~leonardr/lazr.restful/bump-version
Merge into: lp:lazr.restful
Diff against target: 193 lines (+113/-14)
6 files modified
src/lazr/restful/NEWS.txt (+9/-8)
src/lazr/restful/_resource.py (+27/-3)
src/lazr/restful/example/multiversion/tests/introduction.txt (+2/-1)
src/lazr/restful/example/multiversion/tests/wadl.txt (+67/-0)
src/lazr/restful/tales.py (+7/-1)
src/lazr/restful/version.txt (+1/-1)
To merge this branch: bzr merge lp:~leonardr/lazr.restful/bump-version
Reviewer Review Type Date Requested Status
Michael Nelson (community) code Approve
Review via email: mp+19107@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

This branch adds multi-version tests for the WADL generation. When I tried to give multi-version features to lazr.restfulclient, it didn't work because the WADL was malformed. The two main problems were:

1. Unless you got the WADL for the latest version, lazr.restful saw multiple registrations for an entry adapter and crashed. This is because lazr.restful was looking for registrations that matched the marker interface (let's say) IWebServiceVersion1_0. But IWebServiceVersion2_0 and IWebServiceVersion3_0 subclass IWebServiceVersion1_0, so the entry adpater registrations for those versions were picked up as well.

2. The stub function that signals the removal of a named operation as of a particular version was being treated as a (malformed) named operation in its own right.

I fixed these problems in ways that should make sense when you look at the code.

This branch also fixes a test failure and prepares a new release of lazr.restful. (I thought that would be the only purpose of this branch, until lazr.restfulclient started failing on me.)

115. By Leonard Richardson

Added missing file.

Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (5.1 KiB)

Thanks for all the changes Leonard. IRC log below for the record (and sorry Bjorn if you read this - I normally put the quoted diff, but I had to rush to finish this one on time!)

17:02 < noodles775> leonardr: on line 45 of the MP diff, is this really the earliest version, or the earliest *active* version? (which
                    line 47 seems to imply?)
17:03 < leonardr> noodles775: inactive versions don't exist in the lazr.restful installation, only in peoples' memories, so the
                  concepts are equivalent
17:03 < leonardr> i can change the wording

17:04 < noodles775> leonardr: in which case, why can len(config.active_versions) == 0, but we still have an earliest_version? or is
                    that what you'll re-word?
17:04 < leonardr> noodles775: it's kind of confusing but i don't want to change the interface becuase that'll cause backward
                  incompatibility problems
17:04 < leonardr> there are two fields in the interface
17:04 < leonardr> active_versions and latest_version_uri_prefix
17:05 < leonardr> active_versions is a list, the other is a stirng
17:05 < leonardr> lazr.restful serves web services for all of those strings
17:05 -!- danilos [~danilo@canonical/launchpad/danilos] has quit [Ping timeout: 265 seconds]
17:05 < leonardr> so if active_versions is =['beta', '1.0'] and latest_version_uri_prefix is 'devel' there are three versions being
                  served
17:05 < leonardr> if active_versions is [] and latest_version_uri_prefix is 'beta' then there is one version
17:05 < leonardr> though i don't recommend doing that
17:06 < noodles775> So if active_versions is =['beta', '1.0'] and latest_version_uri_prefix is 'devel', why are we evaluating
                    earliest_version = 'beta', is that right?
17:06 < noodles775> leonardr: ^^
17:07 < leonardr> noodles775: i'm having trouble parsing that sentence, but i can tell you that in that case earliest_version is 'beta'
17:07 < leonardr> latest_version_uri_prefix can only be the earliest version if active_versions is empty
17:07 < noodles775> OK, as the code implies, I'm just confused by the names.
17:08 < leonardr> noodles775: i'm kind of leaning towards treating the last item in active_versions as latest_version_uri_prefix

17:08 < leonardr> but that would be a separate job
17:09 -!- salgado-lunch is now known as salgado
17:09 < noodles775> Right, that would make sense. (do you mind adding an XXX?)
17:09 < leonardr> sure
17:11 < noodles775> leonardr: just a style question, is the comment on the empty line 69 intentional?
17:11 < leonardr> yeah, it's like a para break. i don't know if we allow that though
17:12 < noodles775> Yeah, I think a blank line (without the comment) serves just as well, but if the style guide doesn't say either
                    way, whatever you prefer :)
17:12 < leonardr> style guide doesn't mention it
17:13 < leonardr> if i see a blank line i think the first comment was talking about the blank line and the second comment is different
17:13 < noodles775> leonardr: s/marker_interface/version_marker on line 72
17:13 < noodles775> sure
17:19 -!- danilos [~danilo@canonical/launchpad/danilos] has joined #launchp...

Read more...

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/lazr/restful/NEWS.txt'
2--- src/lazr/restful/NEWS.txt 2010-01-12 17:41:25 +0000
3+++ src/lazr/restful/NEWS.txt 2010-02-11 15:31:14 +0000
4@@ -2,19 +2,20 @@
5 NEWS for lazr.restful
6 =====================
7
8-Development
9-===========
10+0.9.18 (2010-02-11)
11+===================
12
13 Special note: this version contains backwards-incompatible
14 changes. You *must* change your configuration object to get your code
15-to work in this version! See "active_versions" below.
16+to work in this version! See "active_versions" and
17+"latest_version_uri_prefix" below.
18
19 Added a versioning system for web services. Clients can now request
20-the "trunk" of a web service as well as one published version. Apart
21-from the URIs served, the two web services are exactly the same. There
22-is no way to serve two different versions of a web service without
23-defining both versions from scratch. (There will eventually be
24-annotations to make this easy.)
25+any number of distinct versions as well as a floating "trunk" which is
26+always the most recent version. By using version-aware annotations,
27+developers can publish the same data model differently over time. See
28+the example web service in example/multiversion/ to see how the
29+annotations work.
30
31 This release introduces a new field to IWebServiceConfiguration:
32 latest_version_uri_prefix. If you are rolling your own
33
34=== modified file 'src/lazr/restful/_resource.py'
35--- src/lazr/restful/_resource.py 2010-02-03 21:22:19 +0000
36+++ src/lazr/restful/_resource.py 2010-02-11 15:31:14 +0000
37@@ -1598,6 +1598,14 @@
38 collection_classes = []
39 singular_names = {}
40 plural_names = {}
41+
42+ # Determine the name of the earliest version. We'll be using this later.
43+ config = getUtility(IWebServiceConfiguration)
44+ if len(config.active_versions) > 0:
45+ earliest_version = config.active_versions[0]
46+ else:
47+ earliest_version = config.latest_version_uri_prefix
48+
49 for registration in sorted(site_manager.registeredAdapters()):
50 provided = registration.provided
51 if IInterface.providedBy(provided):
52@@ -1610,10 +1618,26 @@
53 # of the classes with schemas, which we do describe.
54
55 # Make sure we have a registration relevant to
56- # this version. A given entry may be have one
57+ # this version. A given entry may have one
58 # registration for every web service version.
59- schema, version = registration.required
60- if not version.providedBy(self.request):
61+ schema, version_marker = registration.required
62+
63+ if (version_marker is IWebServiceClientRequest
64+ and self.request.version != earliest_version):
65+ # We are generating WADL for some version
66+ # other than the earliest version, and this is
67+ # a registration for the earliest version. We
68+ # can ignore it.
69+ #
70+ # We need this special test because the normal
71+ # test (below) is useless when
72+ # marker_interface is
73+ # IWebServiceClientRequest. Since all request
74+ # objects provide IWebServiceClientRequest
75+ # directly, it will always show up in
76+ # providedBy(self.request).
77+ continue
78+ if not version_marker in providedBy(self.request):
79 continue
80
81 # Make sure that no other entry class is using this
82
83=== modified file 'src/lazr/restful/example/multiversion/tests/introduction.txt'
84--- src/lazr/restful/example/multiversion/tests/introduction.txt 2010-02-08 18:59:13 +0000
85+++ src/lazr/restful/example/multiversion/tests/introduction.txt 2010-02-11 15:31:14 +0000
86@@ -232,7 +232,8 @@
87 If an entry field is not published in a certain version, the
88 corresponding field resource does not exist for that version.
89
90- >>> webservice.get('/pairs/foo/deleted', api_version='beta')
91+ >>> print webservice.get('/pairs/foo/deleted', api_version='beta').body
92+ Object: ...
93 Traceback (most recent call last):
94 ...
95 NotFound: ... name: u'deleted'
96
97=== added file 'src/lazr/restful/example/multiversion/tests/wadl.txt'
98--- src/lazr/restful/example/multiversion/tests/wadl.txt 1970-01-01 00:00:00 +0000
99+++ src/lazr/restful/example/multiversion/tests/wadl.txt 2010-02-11 15:31:14 +0000
100@@ -0,0 +1,67 @@
101+Multi-version WADL documents
102+****************************
103+
104+A given version of the web service generates a WADL document which
105+describes that version only. Let's go through the WADL documents for
106+the different versions and see how they differ.
107+
108+ >>> from lazr.restful.testing.webservice import WebServiceCaller
109+ >>> webservice = WebServiceCaller(domain='multiversion.dev')
110+
111+We'll start with a helper function that retrieves the WADL description
112+for a given version of the key-value web service, and decomposes the
113+top-level tags in the WADL document into a dictionary for easy access
114+later. This works because all versions of the web service publish a
115+single top-level collection and a single entry type, so the document's
116+top-level structure is always the same.
117+
118+ >>> from lxml import etree
119+ >>> from lxml.etree import _Comment
120+ >>> def wadl_contents_for_version(version):
121+ ... """Parse the key-value service's WADL into a dictionary."""
122+ ... wadl = webservice.get(
123+ ... '/', media_type='application/vnd.sun.wadl+xml',
124+ ... api_version=version).body
125+ ... tree = etree.fromstring(wadl)
126+ ...
127+ ... keys = ("base service_root service_root_json pair_collection "
128+ ... "pair_entry pair_full_json pair_diff_jaon pair_page "
129+ ... "pair_page_json hosted_file hosted_file_representation"
130+ ... ).split()
131+ ...
132+ ... tags = [child for child in tree if not isinstance(child, _Comment)]
133+ ... contents = {}
134+ ... for i in range(0, len(keys)):
135+ ... contents[keys[i]] = tags[i]
136+ ... return contents
137+
138+Let's take a look at the differences. in 'beta', the 'by_value' method
139+is not present at all.
140+
141+ >>> contents = wadl_contents_for_version('beta')
142+ >>> print contents['base'].attrib['base']
143+ http://multiversion.dev/beta/
144+
145+ >>> pair_collection = contents['pair_collection']
146+ >>> sorted([method.attrib['id'] for method in pair_collection])
147+ ['key_value_pairs-get']
148+
149+In '2.0', the by_value method is called 'byValue'.
150+
151+ >>> contents = wadl_contents_for_version('2.0')
152+ >>> print contents['base'].attrib['base']
153+ http://multiversion.dev/2.0/
154+
155+ >>> pair_collection = contents['pair_collection']
156+ >>> sorted([method.attrib['id'] for method in pair_collection])
157+ ['key_value_pairs-byValue', 'key_value_pairs-get']
158+
159+In '3.0', the method changes its name to 'byValue'.
160+
161+ >>> contents = wadl_contents_for_version('3.0')
162+ >>> print contents['base'].attrib['base']
163+ http://multiversion.dev/3.0/
164+
165+ >>> pair_collection = contents['pair_collection']
166+ >>> sorted([method.attrib['id'] for method in pair_collection])
167+ ['key_value_pairs-by_value', 'key_value_pairs-get']
168
169=== modified file 'src/lazr/restful/tales.py'
170--- src/lazr/restful/tales.py 2010-01-11 18:27:43 +0000
171+++ src/lazr/restful/tales.py 2010-02-11 15:31:14 +0000
172@@ -291,7 +291,13 @@
173 for interface in (IResourceGETOperation, IResourcePOSTOperation):
174 operations.extend(getGlobalSiteManager().adapters.lookupAll(
175 (model_class, request_interface), interface))
176- return [{'name' : name, 'op' : op} for name, op in operations]
177+
178+ # An operation that was present in an earlier version but was
179+ # removed in the current version will show up in this list as
180+ # an stub function that returns None. Since we don't want that
181+ # operation to show up in this version, we'll filter it out.
182+ return [{'name' : name, 'op' : op} for name, op in operations
183+ if IResourceOperation.implementedBy(op)]
184
185
186 class WadlEntryInterfaceAdapterAPI(WadlResourceAdapterAPI):
187
188=== modified file 'src/lazr/restful/version.txt'
189--- src/lazr/restful/version.txt 2009-11-10 14:30:52 +0000
190+++ src/lazr/restful/version.txt 2010-02-11 15:31:14 +0000
191@@ -1,1 +1,1 @@
192-0.9.17
193+0.9.18

Subscribers

People subscribed via source and target branches