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
=== modified file 'src/launchpadlib/launchpad.py'
--- src/launchpadlib/launchpad.py 2009-10-22 14:04:19 +0000
+++ src/launchpadlib/launchpad.py 2009-10-23 16:59:11 +0000
@@ -33,14 +33,9 @@
33 CollectionWithKeyBasedLookup, HostedFile, ServiceRoot)33 CollectionWithKeyBasedLookup, HostedFile, ServiceRoot)
34from launchpadlib.credentials import AccessToken, Credentials34from launchpadlib.credentials import AccessToken, Credentials
35from oauth.oauth import OAuthRequest, OAuthSignatureMethod_PLAINTEXT35from oauth.oauth import OAuthRequest, OAuthSignatureMethod_PLAINTEXT
3636from launchpadlib import uris
3737
38OAUTH_REALM = 'https://api.launchpad.net'38OAUTH_REALM = 'https://api.launchpad.net'
39LPNET_SERVICE_ROOT = 'https://api.launchpad.net/beta/'
40EDGE_SERVICE_ROOT = 'https://api.edge.launchpad.net/beta/'
41STAGING_SERVICE_ROOT = 'https://api.staging.launchpad.net/beta/'
42DEV_SERVICE_ROOT = 'https://api.launchpad.dev/beta/'
43DOGFOOD_SERVICE_ROOT = 'https://api.dogfood.launchpad.net/beta/'
4439
4540
46class PersonSet(CollectionWithKeyBasedLookup):41class PersonSet(CollectionWithKeyBasedLookup):
@@ -86,16 +81,7 @@
86 'projects': PillarSet,81 'projects': PillarSet,
87 }82 }
8883
89 service_roots = dict(84 def __init__(self, credentials, service_root=uris.STAGING_SERVICE_ROOT,
90 production=LPNET_SERVICE_ROOT,
91 edge=EDGE_SERVICE_ROOT,
92 staging=STAGING_SERVICE_ROOT,
93 dogfood=DOGFOOD_SERVICE_ROOT,
94 dev=DEV_SERVICE_ROOT,
95 )
96
97
98 def __init__(self, credentials, service_root=STAGING_SERVICE_ROOT,
99 cache=None, timeout=None, proxy_info=None):85 cache=None, timeout=None, proxy_info=None):
100 """Root access to the Launchpad API.86 """Root access to the Launchpad API.
10187
@@ -109,7 +95,7 @@
10995
110 @classmethod96 @classmethod
111 def login(cls, consumer_name, token_string, access_secret,97 def login(cls, consumer_name, token_string, access_secret,
112 service_root=STAGING_SERVICE_ROOT,98 service_root=uris.STAGING_SERVICE_ROOT,
113 cache=None, timeout=None, proxy_info=None):99 cache=None, timeout=None, proxy_info=None):
114 """Convenience for setting up access credentials.100 """Convenience for setting up access credentials.
115101
@@ -138,7 +124,7 @@
138124
139 @classmethod125 @classmethod
140 def get_token_and_login(cls, consumer_name,126 def get_token_and_login(cls, consumer_name,
141 service_root=STAGING_SERVICE_ROOT,127 service_root=uris.STAGING_SERVICE_ROOT,
142 cache=None, timeout=None, proxy_info=None):128 cache=None, timeout=None, proxy_info=None):
143 """Get credentials from Launchpad and log into the service root.129 """Get credentials from Launchpad and log into the service root.
144130
@@ -181,7 +167,7 @@
181167
182 @classmethod168 @classmethod
183 def login_with(cls, consumer_name,169 def login_with(cls, consumer_name,
184 service_root=STAGING_SERVICE_ROOT,170 service_root=uris.STAGING_SERVICE_ROOT,
185 launchpadlib_dir=None, timeout=None, proxy_info=None):171 launchpadlib_dir=None, timeout=None, proxy_info=None):
186 """Log in to Launchpad with possibly cached credentials.172 """Log in to Launchpad with possibly cached credentials.
187173
@@ -219,8 +205,7 @@
219 launchpadlib_dir = os.path.join(home_dir, '.launchpadlib')205 launchpadlib_dir = os.path.join(home_dir, '.launchpadlib')
220 launchpadlib_dir = os.path.expanduser(launchpadlib_dir)206 launchpadlib_dir = os.path.expanduser(launchpadlib_dir)
221 # Determine the real service root.207 # Determine the real service root.
222 if service_root in cls.service_roots:208 service_root = uris.lookup_service_root(service_root)
223 service_root = cls.service_roots[service_root]
224 # Each service root has its own cache and credential dirs.209 # Each service root has its own cache and credential dirs.
225 scheme, host_name, path, query, fragment = urlparse.urlsplit(210 scheme, host_name, path, query, fragment = urlparse.urlsplit(
226 service_root)211 service_root)
227212
=== modified file 'src/launchpadlib/tests/test_launchpad.py'
--- src/launchpadlib/tests/test_launchpad.py 2009-09-29 14:54:55 +0000
+++ src/launchpadlib/tests/test_launchpad.py 2009-10-23 16:59:11 +0000
@@ -26,7 +26,7 @@
2626
27from launchpadlib.credentials import AccessToken, Credentials27from launchpadlib.credentials import AccessToken, Credentials
28from launchpadlib.launchpad import Launchpad28from launchpadlib.launchpad import Launchpad
2929from launchpadlib import uris
3030
31class NoNetworkLaunchpad(Launchpad):31class NoNetworkLaunchpad(Launchpad):
32 """A Launchpad instance for tests with no network access.32 """A Launchpad instance for tests with no network access.
@@ -39,7 +39,6 @@
39 passed_in_kwargs = None39 passed_in_kwargs = None
40 credentials = None40 credentials = None
41 get_token_and_login_called = False41 get_token_and_login_called = False
42 service_roots = dict(example='http://api.example.com/beta')
4342
44 def __init__(self, credentials, **kw):43 def __init__(self, credentials, **kw):
45 self.credentials = credentials44 self.credentials = credentials
@@ -58,6 +57,40 @@
58 return launchpad57 return launchpad
5958
6059
60class TestNameLookups(unittest.TestCase):
61 """Test the utility functions in the 'uris' module."""
62
63 def setUp(self):
64 self.aliases = sorted(
65 ['production', 'edge', 'staging', 'dogfood', 'dev'])
66
67 def test_short_names(self):
68 # Ensure the short service names are all supported.
69 self.assertEqual(sorted(uris.service_roots.keys()), self.aliases)
70 self.assertEqual(sorted(uris.web_roots.keys()), self.aliases)
71
72 def test_lookups(self):
73 """Ensure that short service names turn into long service names."""
74
75 # If the service name is a known alias, lookup methods convert
76 # it to a URL.
77 for alias in self.aliases:
78 self.assertEqual(
79 uris.lookup_service_root(alias), uris.service_roots[alias])
80 self.assertEqual(
81 uris.lookup_web_root(alias), uris.web_roots[alias])
82
83 # If the service name is a valid URL, lookup methods let it
84 # through.
85 other_root = "http://some-other-server.com"
86 self.assertEqual(uris.lookup_service_root(other_root), other_root)
87 self.assertEqual(uris.lookup_web_root(other_root), other_root)
88
89 # Otherwise, lookup methods raise an exception.
90 not_a_url = "not-a-url"
91 self.assertRaises(ValueError, uris.lookup_service_root, not_a_url)
92 self.assertRaises(ValueError, uris.lookup_web_root, not_a_url)
93
61class TestLaunchpadLoginWith(unittest.TestCase):94class TestLaunchpadLoginWith(unittest.TestCase):
62 """Tests for Launchpad.login_with()."""95 """Tests for Launchpad.login_with()."""
6396
@@ -205,25 +238,24 @@
205238
206 def test_short_service_name(self):239 def test_short_service_name(self):
207 # A short service name is converted to the full service root URL.240 # A short service name is converted to the full service root URL.
208 launchpad = NoNetworkLaunchpad.login_with('app name', 'example')241 launchpad = NoNetworkLaunchpad.login_with('app name', 'staging')
209 self.assertEqual(242 self.assertEqual(
210 launchpad.passed_in_kwargs['service_root'],243 launchpad.passed_in_kwargs['service_root'],
211 'http://api.example.com/beta')244 'https://api.staging.launchpad.net/beta/')
212245
213 def test_short_service_name_bad(self):246 # A full URL as the service name is left alone.
214 # A short service name that does not match one of the pre-defined247 launchpad = NoNetworkLaunchpad.login_with(
215 # service root names is passed through.248 'app name', uris.service_roots['staging'])
216 launchpad = NoNetworkLaunchpad.login_with('app name', 'foo')249 self.assertEqual(
217 self.assertEqual(250 launchpad.passed_in_kwargs['service_root'],
218 launchpad.passed_in_kwargs['service_root'],251 uris.service_roots['staging'])
219 'foo')252
220253 # A short service name that does not match one of the
221 def test_short_service_names(self):254 # pre-defined service root names, and is not a valid URL,
222 # Ensure the short service names are all supported.255 # raises an exception.
223 expected = ['production', 'edge', 'staging', 'dogfood', 'dev']256 launchpad = ('app name', 'https://')
224 self.assertEqual(257 self.assertRaises(
225 sorted(Launchpad.service_roots.keys()),258 ValueError, NoNetworkLaunchpad.login_with, 'app name', 'foo')
226 sorted(expected))
227259
228260
229def test_suite():261def test_suite():
230262
=== added file 'src/launchpadlib/uris.py'
--- src/launchpadlib/uris.py 1970-01-01 00:00:00 +0000
+++ src/launchpadlib/uris.py 2009-10-23 16:59:11 +0000
@@ -0,0 +1,95 @@
1# Copyright 2009 Canonical Ltd.
2
3# This file is part of launchpadlib.
4#
5# launchpadlib is free software: you can redistribute it and/or modify it
6# under the terms of the GNU Lesser General Public License as published by the
7# Free Software Foundation, version 3 of the License.
8#
9# launchpadlib is distributed in the hope that it will be useful, but WITHOUT
10# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12# for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with launchpadlib. If not, see <http://www.gnu.org/licenses/>.
16
17"""Launchpad-specific URIs and convenience lookup functions.
18
19The code in this module lets users say "edge" when they mean
20"https://api.edge.launchpad.net/beta".
21"""
22
23__metaclass__ = type
24__all__ = [
25 'lookup_service_root',
26 'lookup_web_root',
27 ]
28
29from urlparse import urlparse
30
31LPNET_SERVICE_ROOT = 'https://api.launchpad.net/beta/'
32EDGE_SERVICE_ROOT = 'https://api.edge.launchpad.net/beta/'
33STAGING_SERVICE_ROOT = 'https://api.staging.launchpad.net/beta/'
34DEV_SERVICE_ROOT = 'https://api.launchpad.dev/beta/'
35DOGFOOD_SERVICE_ROOT = 'https://api.dogfood.launchpad.net/beta/'
36
37LPNET_WEB_ROOT = 'https://launchpad.net/'
38EDGE_WEB_ROOT = 'https://edge.launchpad.net/'
39STAGING_WEB_ROOT = 'https://staging.launchpad.net/'
40DEV_WEB_ROOT = 'https://launchpad.dev/'
41DOGFOOD_WEB_ROOT = 'https://dogfood.launchpad.net/'
42
43
44service_roots = dict(
45 production=LPNET_SERVICE_ROOT,
46 edge=EDGE_SERVICE_ROOT,
47 staging=STAGING_SERVICE_ROOT,
48 dogfood=DOGFOOD_SERVICE_ROOT,
49 dev=DEV_SERVICE_ROOT,
50 )
51
52
53web_roots = dict(
54 production=LPNET_WEB_ROOT,
55 edge=EDGE_WEB_ROOT,
56 staging=STAGING_WEB_ROOT,
57 dogfood=DOGFOOD_WEB_ROOT,
58 dev=DEV_WEB_ROOT,
59 )
60
61
62def _dereference_alias(root, aliases):
63 """Dereference what might a URL or an alias for a URL."""
64 if root in aliases:
65 return aliases[root]
66
67 # It's not an alias. Is it a valid URL?
68 parsed = urlparse(root)
69 if parsed.scheme != "" and parsed.netloc != "":
70 return root
71
72 # It's not an alias or a valid URL.
73 raise ValueError("%s is not a valid URL or an alias for any Launchpad "
74 "server" % root)
75
76
77def lookup_service_root(service_root):
78 """Dereference an alias to a service root.
79
80 A recognized server alias such as "edge" gets turned into the
81 appropriate URI. A URI gets returned as is. Any other string raises a
82 ValueError.
83 """
84 return _dereference_alias(service_root, service_roots)
85
86
87def lookup_web_root(web_root):
88 """Dereference an alias to a website root.
89
90 A recognized server alias such as "edge" gets turned into the
91 appropriate URI. A URI gets returned as is. Any other string raises a
92 ValueError.
93 """
94 return _dereference_alias(web_root, web_roots)
95

Subscribers

People subscribed via source and target branches