Merge lp:~wallyworld/launchpadlib/services-serviceroot into lp:launchpadlib

Proposed by Ian Booth
Status: Needs review
Proposed branch: lp:~wallyworld/launchpadlib/services-serviceroot
Merge into: lp:launchpadlib
Diff against target: 225 lines (+118/-4)
7 files modified
.bzrignore (+1/-0)
src/launchpadlib/NEWS.txt (+5/-0)
src/launchpadlib/__init__.py (+1/-1)
src/launchpadlib/docs/toplevel.txt (+8/-0)
src/launchpadlib/launchpad.py (+14/-1)
src/launchpadlib/testing/testing-wadl.xml (+75/-0)
src/launchpadlib/testing/tests/test_launchpad.py (+14/-2)
To merge this branch: bzr merge lp:~wallyworld/launchpadlib/services-serviceroot
Reviewer Review Type Date Requested Status
Robert Collins (community) Needs Fixing
Review via email: mp+101338@code.launchpad.net

Commit message

Add top level services collection to enable easy access to named Launchpad services.

Description of the change

== Implementation ==

This branch adds a new top level 'services' collection, allowing easy access to named Launchpad services. A Launchpad service implements IService and is traversed to via +service/<name>

Using launchpadlib:

lp = Launchpad(...)
service = lp.services['myservice']
service.some_method()

Some drive-by lint was also done.

== Tests ==

Extend the testing-wadl.xml file and add a new test test_services_collection_get_service.
The test simply ensures the new ServiceSet class is properly setup allowing the services top level collection to be used to locate a service.

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  .bzrignore
  src/launchpadlib/NEWS.txt
  src/launchpadlib/__init__.py
  src/launchpadlib/launchpad.py
  src/launchpadlib/docs/toplevel.txt
  src/launchpadlib/testing/testing-wadl.xml
  src/launchpadlib/testing/tests/test_launchpad.py

./src/launchpadlib/docs/toplevel.txt
     121: want exceeds 78 characters.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

This seems to special case all the services; if the bug about interface support in launchpadlib were fixed, it wouldn't need special casing. As it stands, this is adding code rather than maintaining LoC count, and doing so when a generic approach will be ~ the same size and much more powerful doesn't make sense.

review: Needs Fixing
Revision history for this message
Ian Booth (wallyworld) wrote :

I have now landed the fix for the relative url issue so that launchpadlib.load() works with relative URLs eg '+services/sharing'. If I understand the discussion on the list correctly, I think I am now in a position to be able to land this branch as is. Any library fixes for the pattern this branch re-uses can come later.

Unmerged revisions

130. By Ian Booth on 2012-04-10

Lint

129. By Ian Booth on 2012-04-10

Add a test

128. By Ian Booth on 2012-04-10

Add new services top level collection

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2010-05-22 12:48:48 +0000
3+++ .bzrignore 2012-04-10 09:05:22 +0000
4@@ -11,3 +11,4 @@
5 dist
6 eggs
7 _trial_temp
8+MANIFEST
9
10=== modified file 'src/launchpadlib/NEWS.txt'
11--- src/launchpadlib/NEWS.txt 2011-12-05 15:05:31 +0000
12+++ src/launchpadlib/NEWS.txt 2012-04-10 09:05:22 +0000
13@@ -2,6 +2,11 @@
14 NEWS for launchpadlib
15 =====================
16
17+1.9.13 (2012-04-10)
18+===================
19+- Add new top level "services" collection to allow easy access to
20+ named services defined in Launchpad. [bug=974139]
21+
22 1.9.12 (2011-12-05)
23 ===================
24 - Move keyring base64 encoding to KeyringCredential and be more
25
26=== modified file 'src/launchpadlib/__init__.py'
27--- src/launchpadlib/__init__.py 2011-12-05 15:05:31 +0000
28+++ src/launchpadlib/__init__.py 2012-04-10 09:05:22 +0000
29@@ -14,4 +14,4 @@
30 # You should have received a copy of the GNU Lesser General Public License
31 # along with launchpadlib. If not, see <http://www.gnu.org/licenses/>.
32
33-__version__ = '1.9.12'
34+__version__ = '1.9.13'
35
36=== modified file 'src/launchpadlib/docs/toplevel.txt'
37--- src/launchpadlib/docs/toplevel.txt 2011-07-27 05:33:26 +0000
38+++ src/launchpadlib/docs/toplevel.txt 2012-04-10 09:05:22 +0000
39@@ -54,6 +54,14 @@
40 ...
41 ubuntu
42
43+The service collection does lookups by service name.
44+
45+ >>> sharing_service = launchpad.services('sharing')
46+ >>> print sharing_service.name
47+ send: 'GET /.../sharing ...'
48+ ...
49+ sharing
50+
51 The person collection does lookups by a person's Launchpad
52 name.
53
54
55=== modified file 'src/launchpadlib/launchpad.py'
56--- src/launchpadlib/launchpad.py 2011-11-10 15:29:38 +0000
57+++ src/launchpadlib/launchpad.py 2012-04-10 09:05:22 +0000
58@@ -106,6 +106,19 @@
59 collection_of = 'distribution'
60
61
62+class ServiceSet(CollectionWithKeyBasedLookup):
63+ """A custom subclass capable of service lookup by service name."""
64+
65+ def _get_url_from_id(self, key):
66+ """Transform a service name into the URL to a service."""
67+ return str(
68+ self._root._root_uri.ensureSlash()) + '+services/' + str(key)
69+
70+ # All services are different so we need to ensure each service
71+ # representation is retrieved when needed.
72+ collection_of = None
73+
74+
75 class LaunchpadOAuthAwareHttp(RestfulHttp):
76 """Detects expired/invalid OAuth tokens and tries to get a new token."""
77
78@@ -157,6 +170,7 @@
79 'people': PersonSet,
80 'project_groups': ProjectGroupSet,
81 'projects': ProjectSet,
82+ 'services': ServiceSet,
83 }
84 RESOURCE_TYPE_CLASSES.update(ServiceRoot.RESOURCE_TYPE_CLASSES)
85
86@@ -569,7 +583,6 @@
87 argument_name, argument_value, object_value,
88 object_name, argument_name, object_name))
89
90-
91 @classmethod
92 def _get_paths(cls, service_root, launchpadlib_dir=None):
93 """Locate launchpadlib-related user paths and ensure they exist.
94
95=== modified file 'src/launchpadlib/testing/testing-wadl.xml'
96--- src/launchpadlib/testing/testing-wadl.xml 2011-09-13 15:37:51 +0000
97+++ src/launchpadlib/testing/testing-wadl.xml 2012-04-10 09:05:22 +0000
98@@ -36,6 +36,12 @@
99 <wadl:link resource_type="https://api.example.com/testing/#bugs"/>
100 </wadl:param>
101
102+ <wadl:param style="plain"
103+ path="$['services_collection_link']"
104+ name="services_collection_link">
105+ <wadl:link resource_type="https://api.example.com/testing/#services"/>
106+ </wadl:param>
107+
108 <wadl:param style="plain" path="$['me_link']"
109 name="me_link">
110 <wadl:link resource_type="https://api.example.com/testing/#person"/>
111@@ -343,4 +349,73 @@
112 </wadl:param>
113 </wadl:representation>
114
115+ <wadl:resource_type id="services">
116+ <wadl:doc xmlns="http://www.w3.org/1999/xhtml">
117+ Interface representing the set of services.
118+ </wadl:doc>
119+ <wadl:method name="GET" id="services-get">
120+ <wadl:response>
121+ <wadl:representation
122+ href="https://api.example.com/testing/#service-page"/>
123+ <wadl:representation
124+ mediaType="application/vnd.sun.wadl+xml"
125+ id="services-wadl"/>
126+ </wadl:response>
127+ </wadl:method>
128+
129+ <wadl:method id="services-getByName" name="GET">
130+ <wadl:request>
131+ <wadl:param style="query" name="ws.op"
132+ required="true"
133+ fixed="getByName">
134+ </wadl:param>
135+ <wadl:param style="query" required="true"
136+ name="name">
137+ </wadl:param>
138+ </wadl:request>
139+ <wadl:response>
140+ <wadl:representation
141+ href="https://api.example.com/testing/#service-full"/>
142+ </wadl:response>
143+ </wadl:method>
144+
145+ </wadl:resource_type>
146+
147+ <wadl:resource_type id="service">
148+ <wadl:doc xmlns="http://www.w3.org/1999/xhtml">
149+A Launchpad service.
150+</wadl:doc>
151+ <wadl:method name="GET" id="service-get">
152+ <wadl:response>
153+ <wadl:representation
154+ href="https://api.example.com/testing/#service-full"/>
155+ <wadl:representation
156+ mediaType="application/xhtml+xml" id="service-xhtml"/>
157+ <wadl:representation
158+ mediaType="application/vnd.sun.wadl+xml"
159+ id="service-wadl"/>
160+ </wadl:response>
161+ </wadl:method>
162+
163+ </wadl:resource_type>
164+
165+ <wadl:representation mediaType="application/json"
166+ id="service-full">
167+ <wadl:param style="plain" name="self_link" path="$['self_link']">
168+ <wadl:link resource_type="https://api.example.com/testing/#service"/>
169+ </wadl:param>
170+ <wadl:param style="plain" name="web_link"
171+ path="$['web_link']">
172+ <wadl:link/>
173+ </wadl:param>
174+ <wadl:param style="plain" name="resource_type_link" path="$['resource_type_link']">
175+ <wadl:link/>
176+ </wadl:param>
177+ <wadl:param style="plain" name="http_etag" path="$['http_etag']">
178+ </wadl:param>
179+ <wadl:param style="plain" required="true"
180+ path="$['name']" name="name">
181+ </wadl:param>
182+ </wadl:representation>
183+
184 </wadl:application>
185
186=== modified file 'src/launchpadlib/testing/tests/test_launchpad.py'
187--- src/launchpadlib/testing/tests/test_launchpad.py 2011-09-13 13:48:51 +0000
188+++ src/launchpadlib/testing/tests/test_launchpad.py 2012-04-10 09:05:22 +0000
189@@ -69,7 +69,8 @@
190 bug = dict(id="1", title="Bug #1")
191 self.launchpad.bugs = dict(entries=[bug])
192 [bug] = list(self.launchpad.bugs)
193- self.assertEqual("<FakeResource bug 1 at %s>" % hex(id(bug)), repr(bug))
194+ self.assertEqual(
195+ "<FakeResource bug 1 at %s>" % hex(id(bug)), repr(bug))
196
197
198 class FakeLaunchpadTest(ResourcedTestCase):
199@@ -116,7 +117,8 @@
200 An L{IntegrityError} is raised if an attribute is set on a
201 L{FakeLaunchpad} instance that isn't present in the WADL definition.
202 """
203- self.assertRaises(IntegrityError, setattr, self.launchpad, "foo", "bar")
204+ self.assertRaises(
205+ IntegrityError, setattr, self.launchpad, "foo", "bar")
206
207 def test_get_undefined_resource(self):
208 """
209@@ -317,6 +319,16 @@
210 branch = self.launchpad.branches.getByUniqueName("foo")
211 self.assertEqual("lp:~user/project/branch2", branch.bzr_identity)
212
213+ def test_services_collection_get_service(self):
214+ """
215+ Test that the services collection can be used to locate a named
216+ service.
217+ """
218+ service = dict(name="myservice")
219+ self.launchpad.services = dict(getByName=lambda name: service)
220+ service = self.launchpad.services.getByName("myservice")
221+ self.assertEqual("myservice", service.name)
222+
223 def test_replace_property_with_invalid_value(self):
224 """Values set on fake resource objects are validated."""
225 self.launchpad.me = dict(name="foo")

Subscribers

People subscribed via source and target branches