Merge lp:~leonardr/launchpadlib/oauth-in-restfulclient into lp:launchpadlib

Proposed by Leonard Richardson
Status: Merged
Approved by: Gavin Panella
Approved revision: 61
Merged at revision: not available
Proposed branch: lp:~leonardr/launchpadlib/oauth-in-restfulclient
Merge into: lp:launchpadlib
Diff against target: 333 lines
6 files modified
setup.py (+1/-1)
src/launchpadlib/NEWS.txt (+6/-0)
src/launchpadlib/__init__.py (+1/-1)
src/launchpadlib/credentials.py (+9/-126)
src/launchpadlib/launchpad.py (+0/-28)
src/launchpadlib/tests/test_credentials.py (+0/-60)
To merge this branch: bzr merge lp:~leonardr/launchpadlib/oauth-in-restfulclient
Reviewer Review Type Date Requested Status
Gavin Panella Approve
Review via email: mp+13790@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

This branch removes a lot of code from launchpadlib that now lives in lazr.restfulclient. There should be no changes visible from outside. I've run the launchpadlib tests under a Launchpad installation and test-driven launchpadlib using bin/py to go through the token request process.

61. By Leonard Richardson

Prep for release.

Revision history for this message
Gavin Panella (allenap) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'setup.py'
2--- setup.py 2009-07-08 18:48:28 +0000
3+++ setup.py 2009-10-22 16:05:21 +0000
4@@ -60,7 +60,7 @@
5 license='LGPL v3',
6 install_requires=[
7 'httplib2',
8- 'lazr.restfulclient',
9+ 'lazr.restfulclient>=0.9.9',
10 'lazr.uri',
11 'oauth',
12 'setuptools',
13
14=== modified file 'src/launchpadlib/NEWS.txt'
15--- src/launchpadlib/NEWS.txt 2009-10-01 19:15:41 +0000
16+++ src/launchpadlib/NEWS.txt 2009-10-22 16:05:21 +0000
17@@ -2,6 +2,12 @@
18 NEWS for launchpadlib
19 =====================
20
21+1.5.3 (2009-10-22)
22+==================
23+
24+- Moved some more code from launchpadlib into the more generic
25+ lazr.restfulclient.
26+
27 1.5.2 (2009-10-01)
28 ==================
29
30
31=== modified file 'src/launchpadlib/__init__.py'
32--- src/launchpadlib/__init__.py 2009-10-01 19:15:41 +0000
33+++ src/launchpadlib/__init__.py 2009-10-22 16:05:21 +0000
34@@ -14,4 +14,4 @@
35 # You should have received a copy of the GNU Lesser General Public License
36 # along with launchpadlib. If not, see <http://www.gnu.org/licenses/>.
37
38-__version__ = '1.5.2'
39+__version__ = '1.5.3'
40
41=== modified file 'src/launchpadlib/credentials.py'
42--- src/launchpadlib/credentials.py 2009-03-26 21:07:35 +0000
43+++ src/launchpadlib/credentials.py 2009-10-22 16:05:21 +0000
44@@ -23,23 +23,22 @@
45 'Credentials',
46 ]
47
48-from ConfigParser import SafeConfigParser
49 import cgi
50 import httplib2
51-from oauth.oauth import OAuthConsumer, OAuthToken
52 from urllib import urlencode
53
54-from lazr.restfulclient.errors import CredentialsFileError, HTTPError
55-
56-
57-CREDENTIALS_FILE_VERSION = '1'
58+from lazr.restfulclient.errors import HTTPError
59+from lazr.restfulclient.authorize.oauth import (
60+ AccessToken as _AccessToken, Consumer, OAuthAuthorizer)
61+
62+
63 STAGING_WEB_ROOT = 'https://staging.launchpad.net/'
64 request_token_page = '+request-token'
65 access_token_page = '+access-token'
66 authorize_token_page = '+authorize-token'
67
68
69-class Credentials:
70+class Credentials(OAuthAuthorizer):
71 """Standard credentials storage and usage class.
72
73 :ivar consumer: The consumer (application)
74@@ -49,107 +48,6 @@
75 """
76 _request_token = None
77
78- def __init__(self, consumer_name=None, consumer_secret='',
79- access_token=None):
80- """The user's Launchpad API credentials.
81-
82- :param consumer_name: The name of the consumer (application)
83- :param consumer_secret: The secret of the consumer
84- :param access_token: The authenticated user access token
85- :type access_token: `AccessToken`
86- """
87- self.consumer = None
88- if consumer_name is not None:
89- self.consumer = Consumer(consumer_name, consumer_secret)
90- self.access_token = access_token
91-
92- def load(self, readable_file):
93- """Load credentials from a file-like object.
94-
95- This overrides the consumer and access token given in the constructor
96- and replaces them with the values read from the file.
97-
98- :param readable_file: A file-like object to read the credentials from
99- :type readable_file: Any object supporting the file-like `read()`
100- method
101- """
102- # Attempt to load the access token from the file.
103- parser = SafeConfigParser()
104- parser.readfp(readable_file)
105- # Check the version number and extract the access token and
106- # secret. Then convert these to the appropriate instances.
107- if not parser.has_section(CREDENTIALS_FILE_VERSION):
108- raise CredentialsFileError('No configuration for version %s' %
109- CREDENTIALS_FILE_VERSION)
110- consumer_key = parser.get(
111- CREDENTIALS_FILE_VERSION, 'consumer_key')
112- consumer_secret = parser.get(
113- CREDENTIALS_FILE_VERSION, 'consumer_secret')
114- self.consumer = Consumer(consumer_key, consumer_secret)
115- access_token = parser.get(
116- CREDENTIALS_FILE_VERSION, 'access_token')
117- access_secret = parser.get(
118- CREDENTIALS_FILE_VERSION, 'access_secret')
119- self.access_token = AccessToken(access_token, access_secret)
120-
121- @classmethod
122- def load_from_path(cls, path):
123- """Convenience method for loading credentials from a file.
124-
125- Open the file, create the Credentials and load from the file,
126- and finally close the file and return the newly created
127- Credentials instance.
128-
129- :param path: In which file the credential file should be saved.
130- :type path: string
131- :return: The loaded Credentials instance.
132- :rtype: `Credentials`
133- """
134- credentials = cls()
135- credentials_file = open(path, 'r')
136- credentials.load(credentials_file)
137- credentials_file.close()
138- return credentials
139-
140- def save(self, writable_file):
141- """Write the credentials to the file-like object.
142-
143- :param writable_file: A file-like object to write the credentials to
144- :type writable_file: Any object supporting the file-like `write()`
145- method
146- :raise CredentialsFileError: when there is either no consumer or no
147- access token
148- """
149- if self.consumer is None:
150- raise CredentialsFileError('No consumer')
151- if self.access_token is None:
152- raise CredentialsFileError('No access token')
153-
154- parser = SafeConfigParser()
155- parser.add_section(CREDENTIALS_FILE_VERSION)
156- parser.set(CREDENTIALS_FILE_VERSION,
157- 'consumer_key', self.consumer.key)
158- parser.set(CREDENTIALS_FILE_VERSION,
159- 'consumer_secret', self.consumer.secret)
160- parser.set(CREDENTIALS_FILE_VERSION,
161- 'access_token', self.access_token.key)
162- parser.set(CREDENTIALS_FILE_VERSION,
163- 'access_secret', self.access_token.secret)
164- parser.write(writable_file)
165-
166- def save_to_path(self, path):
167- """Convenience method for saving credentials to a file.
168-
169- Create the file, call self.save(), and close the file. Existing
170- files are overwritten.
171-
172- :param path: In which file the credential file should be saved.
173- :type path: string
174- """
175- credentials_file = open(path, 'w')
176- self.save(credentials_file)
177- credentials_file.close()
178-
179 def get_request_token(self, context=None, web_root=STAGING_WEB_ROOT):
180 """Request an OAuth token to Launchpad.
181
182@@ -162,7 +60,7 @@
183 validity within Launchpad.
184 :param web_root: The URL of the website on which the token
185 should be requested.
186- :return: The URL for the user to authorize the `OAuthToken` provided
187+ :return: The URL for the user to authorize the `AccessToken` provided
188 by Launchpad.
189 """
190 assert self.consumer is not None, "Consumer not specified."
191@@ -176,7 +74,7 @@
192 url, method='POST', body=urlencode(params))
193 if response.status != 200:
194 raise HTTPError(response, content)
195- self._request_token = OAuthToken.from_string(content)
196+ self._request_token = AccessToken.from_string(content)
197 url = '%s%s?oauth_token=%s' % (web_root, authorize_token_page,
198 self._request_token.key)
199 if context is not None:
200@@ -210,24 +108,9 @@
201 self.access_token = AccessToken.from_string(content)
202
203
204-# These two classes are provided for convenience (so applications don't need
205-# to import from launchpadlib._oauth.oauth), and to provide a default argument
206-# for secret.
207-
208-class Consumer(OAuthConsumer):
209- """An OAuth consumer (application)."""
210-
211- def __init__(self, key, secret=''):
212- super(Consumer, self).__init__(key, secret)
213-
214-
215-class AccessToken(OAuthToken):
216+class AccessToken(_AccessToken):
217 """An OAuth access token."""
218
219- def __init__(self, key, secret='', context=None):
220- super(AccessToken, self).__init__(key, secret)
221- self.context = context
222-
223 @classmethod
224 def from_string(cls, query_string):
225 """Create and return a new `AccessToken` from the given string."""
226
227=== modified file 'src/launchpadlib/launchpad.py'
228--- src/launchpadlib/launchpad.py 2009-09-29 14:54:55 +0000
229+++ src/launchpadlib/launchpad.py 2009-10-22 16:05:21 +0000
230@@ -107,9 +107,6 @@
231 super(Launchpad, self).__init__(
232 credentials, service_root, cache, timeout, proxy_info)
233
234- def httpFactory(self, credentials, cache, timeout, proxy_info):
235- return OAuthSigningHttp(credentials, cache, timeout, proxy_info)
236-
237 @classmethod
238 def login(cls, consumer_name, token_string, access_secret,
239 service_root=STAGING_SERVICE_ROOT,
240@@ -252,28 +249,3 @@
241 os.path.join(credentials_path, consumer_name),
242 stat.S_IREAD | stat.S_IWRITE)
243 return launchpad
244-
245-
246-class OAuthSigningHttp(RestfulHttp):
247- """A client that signs every outgoing request with OAuth credentials."""
248-
249- def _request(self, conn, host, absolute_uri, request_uri, method, body,
250- headers, redirections, cachekey):
251- """Sign a request with OAuth credentials before sending it."""
252- oauth_request = OAuthRequest.from_consumer_and_token(
253- self.restful_credentials.consumer,
254- self.restful_credentials.access_token,
255- http_url=absolute_uri)
256- oauth_request.sign_request(
257- OAuthSignatureMethod_PLAINTEXT(),
258- self.restful_credentials.consumer,
259- self.restful_credentials.access_token)
260- if headers.has_key('authorization'):
261- # There's an authorization header left over from a
262- # previous request that resulted in a redirect. Remove it
263- # and start again.
264- del headers['authorization']
265- headers.update(oauth_request.to_header(OAUTH_REALM))
266- return super(OAuthSigningHttp, self)._request(
267- conn, host, absolute_uri, request_uri, method, body, headers,
268- redirections, cachekey)
269
270=== removed file 'src/launchpadlib/tests/test_credentials.py'
271--- src/launchpadlib/tests/test_credentials.py 2009-07-09 12:54:19 +0000
272+++ src/launchpadlib/tests/test_credentials.py 1970-01-01 00:00:00 +0000
273@@ -1,60 +0,0 @@
274-# Copyright 2009 Canonical Ltd.
275-
276-# This file is part of launchpadlib.
277-#
278-# launchpadlib is free software: you can redistribute it and/or modify it
279-# under the terms of the GNU Lesser General Public License as published by the
280-# Free Software Foundation, version 3 of the License.
281-#
282-# launchpadlib is distributed in the hope that it will be useful, but WITHOUT
283-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
284-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
285-# for more details.
286-#
287-# You should have received a copy of the GNU Lesser General Public License
288-# along with launchpadlib. If not, see <http://www.gnu.org/licenses/>.
289-
290-"""Tests for the Credentials class."""
291-
292-__metaclass__ = type
293-
294-
295-import os
296-import os.path
297-import shutil
298-import tempfile
299-import unittest
300-
301-from launchpadlib.credentials import AccessToken, Credentials
302-
303-
304-class TestCredentialsSaveAndLoad(unittest.TestCase):
305- """Test for saving and loading credentials."""
306-
307- def setUp(self):
308- self.temp_dir = tempfile.mkdtemp()
309-
310- def tearDown(self):
311- shutil.rmtree(self.temp_dir)
312-
313- def test_save_to_and_load_from__path(self):
314- # Credentials can be saved to and loaded from a file using
315- # save_to_path() and load_from_path().
316- credentials_path = os.path.join(self.temp_dir, 'credentials')
317- credentials = Credentials(
318- 'consumer.key', consumer_secret='consumer.secret',
319- access_token=AccessToken('access.key', 'access.secret'))
320- credentials.save_to_path(credentials_path)
321- self.assertTrue(os.path.exists(credentials_path))
322-
323- loaded_credentials = Credentials.load_from_path(credentials_path)
324- self.assertEqual(loaded_credentials.consumer.key, 'consumer.key')
325- self.assertEqual(
326- loaded_credentials.consumer.secret, 'consumer.secret')
327- self.assertEqual(
328- loaded_credentials.access_token.key, 'access.key')
329- self.assertEqual(
330- loaded_credentials.access_token.secret, 'access.secret')
331-
332-def test_suite():
333- return unittest.TestLoader().loadTestsFromName(__name__)

Subscribers

People subscribed via source and target branches