Merge lp:~leonardr/launchpadlib/web-root into lp:launchpadlib
- web-root
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Abel Deuring (community) | code | Approve | |
Review via email: mp+13856@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote : | # |
- 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 | + |
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 SimpleLaunchpad Browser, a class that can communicate with the Launchpad website in lieu of the user's web browser. SimpleLaunchpad Browser 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 SimpleLaunchpad Browser 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.