Merge lp:~leonardr/launchpadlib/web-root into lp:launchpadlib

Proposed by Leonard Richardson
Status: Merged
Merged at revision: not available
Proposed branch: lp:~leonardr/launchpadlib/web-root
Merge into: lp:launchpadlib
Diff against target: 280 lines
3 files modified
src/launchpadlib/launchpad.py (+6/-21)
src/launchpadlib/tests/test_launchpad.py (+53/-21)
src/launchpadlib/uris.py (+95/-0)
To merge this branch: bzr merge lp:~leonardr/launchpadlib/web-root
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+13856@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

This is the first part of a multi-part branch. It creates a "web_roots" dict similar to the existing "service_roots" dict, which maps nicknames for servers (dogfood, edge, etc.) to the root URI for the corresponding Launchpad website.

The larger purpose of this branch is to lay the groundwork for SimpleLaunchpadBrowser, a class that can communicate with the Launchpad website in lieu of the user's web browser. SimpleLaunchpadBrowser will be able to ask for request tokens and exchange a request token for an access token. This, in turn, will let people write trusted token-exchange clients that go in launchpadlib, rather than untrustable one-off clients. Then I can finally mark bug 387297 as fixed.

This branch will let users point SimpleLaunchpadBrowser at a Launchpad instance by specifying its nickname, just as they can point Launchpad at an instance of the web service by specifying its name.

To avoid import conflicts in future branches, and to keep all the hard-coded URIs in one place, I moved service_roots out of the Launchpad class and into a new file, uris.py. I also refactored the lookup code in the Launchpad class into a utility function, lookup_service_root. I don't know how often lookup_service_root will be called, but the corresponding function for the website, lookup_web_root, will be called a lot.

lp:~leonardr/launchpadlib/web-root updated
66. By Leonard Richardson

Fixed up tests and added error checking in response to feedback.

Revision history for this message
Abel Deuring (adeuring) wrote :

Hi Leonard,

nice code! thanks for the additonal changes we dicussed on IRC

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/launchpadlib/launchpad.py'
2--- src/launchpadlib/launchpad.py 2009-10-22 14:04:19 +0000
3+++ src/launchpadlib/launchpad.py 2009-10-23 16:59:11 +0000
4@@ -33,14 +33,9 @@
5 CollectionWithKeyBasedLookup, HostedFile, ServiceRoot)
6 from launchpadlib.credentials import AccessToken, Credentials
7 from oauth.oauth import OAuthRequest, OAuthSignatureMethod_PLAINTEXT
8-
9+from launchpadlib import uris
10
11 OAUTH_REALM = 'https://api.launchpad.net'
12-LPNET_SERVICE_ROOT = 'https://api.launchpad.net/beta/'
13-EDGE_SERVICE_ROOT = 'https://api.edge.launchpad.net/beta/'
14-STAGING_SERVICE_ROOT = 'https://api.staging.launchpad.net/beta/'
15-DEV_SERVICE_ROOT = 'https://api.launchpad.dev/beta/'
16-DOGFOOD_SERVICE_ROOT = 'https://api.dogfood.launchpad.net/beta/'
17
18
19 class PersonSet(CollectionWithKeyBasedLookup):
20@@ -86,16 +81,7 @@
21 'projects': PillarSet,
22 }
23
24- service_roots = dict(
25- production=LPNET_SERVICE_ROOT,
26- edge=EDGE_SERVICE_ROOT,
27- staging=STAGING_SERVICE_ROOT,
28- dogfood=DOGFOOD_SERVICE_ROOT,
29- dev=DEV_SERVICE_ROOT,
30- )
31-
32-
33- def __init__(self, credentials, service_root=STAGING_SERVICE_ROOT,
34+ def __init__(self, credentials, service_root=uris.STAGING_SERVICE_ROOT,
35 cache=None, timeout=None, proxy_info=None):
36 """Root access to the Launchpad API.
37
38@@ -109,7 +95,7 @@
39
40 @classmethod
41 def login(cls, consumer_name, token_string, access_secret,
42- service_root=STAGING_SERVICE_ROOT,
43+ service_root=uris.STAGING_SERVICE_ROOT,
44 cache=None, timeout=None, proxy_info=None):
45 """Convenience for setting up access credentials.
46
47@@ -138,7 +124,7 @@
48
49 @classmethod
50 def get_token_and_login(cls, consumer_name,
51- service_root=STAGING_SERVICE_ROOT,
52+ service_root=uris.STAGING_SERVICE_ROOT,
53 cache=None, timeout=None, proxy_info=None):
54 """Get credentials from Launchpad and log into the service root.
55
56@@ -181,7 +167,7 @@
57
58 @classmethod
59 def login_with(cls, consumer_name,
60- service_root=STAGING_SERVICE_ROOT,
61+ service_root=uris.STAGING_SERVICE_ROOT,
62 launchpadlib_dir=None, timeout=None, proxy_info=None):
63 """Log in to Launchpad with possibly cached credentials.
64
65@@ -219,8 +205,7 @@
66 launchpadlib_dir = os.path.join(home_dir, '.launchpadlib')
67 launchpadlib_dir = os.path.expanduser(launchpadlib_dir)
68 # Determine the real service root.
69- if service_root in cls.service_roots:
70- service_root = cls.service_roots[service_root]
71+ service_root = uris.lookup_service_root(service_root)
72 # Each service root has its own cache and credential dirs.
73 scheme, host_name, path, query, fragment = urlparse.urlsplit(
74 service_root)
75
76=== modified file 'src/launchpadlib/tests/test_launchpad.py'
77--- src/launchpadlib/tests/test_launchpad.py 2009-09-29 14:54:55 +0000
78+++ src/launchpadlib/tests/test_launchpad.py 2009-10-23 16:59:11 +0000
79@@ -26,7 +26,7 @@
80
81 from launchpadlib.credentials import AccessToken, Credentials
82 from launchpadlib.launchpad import Launchpad
83-
84+from launchpadlib import uris
85
86 class NoNetworkLaunchpad(Launchpad):
87 """A Launchpad instance for tests with no network access.
88@@ -39,7 +39,6 @@
89 passed_in_kwargs = None
90 credentials = None
91 get_token_and_login_called = False
92- service_roots = dict(example='http://api.example.com/beta')
93
94 def __init__(self, credentials, **kw):
95 self.credentials = credentials
96@@ -58,6 +57,40 @@
97 return launchpad
98
99
100+class TestNameLookups(unittest.TestCase):
101+ """Test the utility functions in the 'uris' module."""
102+
103+ def setUp(self):
104+ self.aliases = sorted(
105+ ['production', 'edge', 'staging', 'dogfood', 'dev'])
106+
107+ def test_short_names(self):
108+ # Ensure the short service names are all supported.
109+ self.assertEqual(sorted(uris.service_roots.keys()), self.aliases)
110+ self.assertEqual(sorted(uris.web_roots.keys()), self.aliases)
111+
112+ def test_lookups(self):
113+ """Ensure that short service names turn into long service names."""
114+
115+ # If the service name is a known alias, lookup methods convert
116+ # it to a URL.
117+ for alias in self.aliases:
118+ self.assertEqual(
119+ uris.lookup_service_root(alias), uris.service_roots[alias])
120+ self.assertEqual(
121+ uris.lookup_web_root(alias), uris.web_roots[alias])
122+
123+ # If the service name is a valid URL, lookup methods let it
124+ # through.
125+ other_root = "http://some-other-server.com"
126+ self.assertEqual(uris.lookup_service_root(other_root), other_root)
127+ self.assertEqual(uris.lookup_web_root(other_root), other_root)
128+
129+ # Otherwise, lookup methods raise an exception.
130+ not_a_url = "not-a-url"
131+ self.assertRaises(ValueError, uris.lookup_service_root, not_a_url)
132+ self.assertRaises(ValueError, uris.lookup_web_root, not_a_url)
133+
134 class TestLaunchpadLoginWith(unittest.TestCase):
135 """Tests for Launchpad.login_with()."""
136
137@@ -205,25 +238,24 @@
138
139 def test_short_service_name(self):
140 # A short service name is converted to the full service root URL.
141- launchpad = NoNetworkLaunchpad.login_with('app name', 'example')
142- self.assertEqual(
143- launchpad.passed_in_kwargs['service_root'],
144- 'http://api.example.com/beta')
145-
146- def test_short_service_name_bad(self):
147- # A short service name that does not match one of the pre-defined
148- # service root names is passed through.
149- launchpad = NoNetworkLaunchpad.login_with('app name', 'foo')
150- self.assertEqual(
151- launchpad.passed_in_kwargs['service_root'],
152- 'foo')
153-
154- def test_short_service_names(self):
155- # Ensure the short service names are all supported.
156- expected = ['production', 'edge', 'staging', 'dogfood', 'dev']
157- self.assertEqual(
158- sorted(Launchpad.service_roots.keys()),
159- sorted(expected))
160+ launchpad = NoNetworkLaunchpad.login_with('app name', 'staging')
161+ self.assertEqual(
162+ launchpad.passed_in_kwargs['service_root'],
163+ 'https://api.staging.launchpad.net/beta/')
164+
165+ # A full URL as the service name is left alone.
166+ launchpad = NoNetworkLaunchpad.login_with(
167+ 'app name', uris.service_roots['staging'])
168+ self.assertEqual(
169+ launchpad.passed_in_kwargs['service_root'],
170+ uris.service_roots['staging'])
171+
172+ # A short service name that does not match one of the
173+ # pre-defined service root names, and is not a valid URL,
174+ # raises an exception.
175+ launchpad = ('app name', 'https://')
176+ self.assertRaises(
177+ ValueError, NoNetworkLaunchpad.login_with, 'app name', 'foo')
178
179
180 def test_suite():
181
182=== added file 'src/launchpadlib/uris.py'
183--- src/launchpadlib/uris.py 1970-01-01 00:00:00 +0000
184+++ src/launchpadlib/uris.py 2009-10-23 16:59:11 +0000
185@@ -0,0 +1,95 @@
186+# Copyright 2009 Canonical Ltd.
187+
188+# This file is part of launchpadlib.
189+#
190+# launchpadlib is free software: you can redistribute it and/or modify it
191+# under the terms of the GNU Lesser General Public License as published by the
192+# Free Software Foundation, version 3 of the License.
193+#
194+# launchpadlib is distributed in the hope that it will be useful, but WITHOUT
195+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
196+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
197+# for more details.
198+#
199+# You should have received a copy of the GNU Lesser General Public License
200+# along with launchpadlib. If not, see <http://www.gnu.org/licenses/>.
201+
202+"""Launchpad-specific URIs and convenience lookup functions.
203+
204+The code in this module lets users say "edge" when they mean
205+"https://api.edge.launchpad.net/beta".
206+"""
207+
208+__metaclass__ = type
209+__all__ = [
210+ 'lookup_service_root',
211+ 'lookup_web_root',
212+ ]
213+
214+from urlparse import urlparse
215+
216+LPNET_SERVICE_ROOT = 'https://api.launchpad.net/beta/'
217+EDGE_SERVICE_ROOT = 'https://api.edge.launchpad.net/beta/'
218+STAGING_SERVICE_ROOT = 'https://api.staging.launchpad.net/beta/'
219+DEV_SERVICE_ROOT = 'https://api.launchpad.dev/beta/'
220+DOGFOOD_SERVICE_ROOT = 'https://api.dogfood.launchpad.net/beta/'
221+
222+LPNET_WEB_ROOT = 'https://launchpad.net/'
223+EDGE_WEB_ROOT = 'https://edge.launchpad.net/'
224+STAGING_WEB_ROOT = 'https://staging.launchpad.net/'
225+DEV_WEB_ROOT = 'https://launchpad.dev/'
226+DOGFOOD_WEB_ROOT = 'https://dogfood.launchpad.net/'
227+
228+
229+service_roots = dict(
230+ production=LPNET_SERVICE_ROOT,
231+ edge=EDGE_SERVICE_ROOT,
232+ staging=STAGING_SERVICE_ROOT,
233+ dogfood=DOGFOOD_SERVICE_ROOT,
234+ dev=DEV_SERVICE_ROOT,
235+ )
236+
237+
238+web_roots = dict(
239+ production=LPNET_WEB_ROOT,
240+ edge=EDGE_WEB_ROOT,
241+ staging=STAGING_WEB_ROOT,
242+ dogfood=DOGFOOD_WEB_ROOT,
243+ dev=DEV_WEB_ROOT,
244+ )
245+
246+
247+def _dereference_alias(root, aliases):
248+ """Dereference what might a URL or an alias for a URL."""
249+ if root in aliases:
250+ return aliases[root]
251+
252+ # It's not an alias. Is it a valid URL?
253+ parsed = urlparse(root)
254+ if parsed.scheme != "" and parsed.netloc != "":
255+ return root
256+
257+ # It's not an alias or a valid URL.
258+ raise ValueError("%s is not a valid URL or an alias for any Launchpad "
259+ "server" % root)
260+
261+
262+def lookup_service_root(service_root):
263+ """Dereference an alias to a service root.
264+
265+ A recognized server alias such as "edge" gets turned into the
266+ appropriate URI. A URI gets returned as is. Any other string raises a
267+ ValueError.
268+ """
269+ return _dereference_alias(service_root, service_roots)
270+
271+
272+def lookup_web_root(web_root):
273+ """Dereference an alias to a website root.
274+
275+ A recognized server alias such as "edge" gets turned into the
276+ appropriate URI. A URI gets returned as is. Any other string raises a
277+ ValueError.
278+ """
279+ return _dereference_alias(web_root, web_roots)
280+

Subscribers

People subscribed via source and target branches