Merge lp:~alecu/ubuntu-sso-client/restful-client into lp:ubuntu-sso-client

Proposed by Alejandro J. Cura
Status: Merged
Approved by: Alejandro J. Cura
Approved revision: 849
Merged at revision: 833
Proposed branch: lp:~alecu/ubuntu-sso-client/restful-client
Merge into: lp:ubuntu-sso-client
Prerequisite: lp:~nataliabidart/ubuntu-sso-client/unify-signal-broadcaster
Diff against target: 269 lines (+215/-4)
5 files modified
ubuntu_sso/utils/webclient/common.py (+2/-1)
ubuntu_sso/utils/webclient/qtnetwork.py (+5/-3)
ubuntu_sso/utils/webclient/restful.py (+52/-0)
ubuntu_sso/utils/webclient/tests/test_restful.py (+148/-0)
ubuntu_sso/utils/webclient/tests/test_webclient.py (+8/-0)
To merge this branch: bzr merge lp:~alecu/ubuntu-sso-client/restful-client
Reviewer Review Type Date Requested Status
Diego Sarmentero (community) Approve
Natalia Bidart (community) Approve
Review via email: mp+88230@code.launchpad.net

Commit message

An async, proxy-enabled replacement for lazr.restfulclient

Description of the change

An async, proxy-enabled replacement for lazr.restfulclient
An upcoming branch will use this module to remove the threads that use lazr

NOTE! This branch also depends on lp:~alecu/ubuntu-sso-client/proxy-integration-tests

To post a comment you must log in.
847. By Alejandro J. Cura

merged with trunk

Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

== Python Lint Notices ==

ubuntu_sso/utils/webclient/tests/test_webclient.py:
    343: [E0102, BasicProxyTestCase] class already defined line 305
    347: [E1003, BasicProxyTestCase.setUp] Bad first argument 'BasicProxyTestCase' given to super class

review: Needs Fixing
848. By Alejandro J. Cura

remove duplicated testcase

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Could you please put the value of reply.readAll() in a variable and use that in the 3 lines that uses it?

Shall you also assert on RestfulClient.__init__ that the iri is unicode?

Rest looks good! I will mark as approved but please change the readAll() thingy.

review: Approve
849. By Alejandro J. Cura

refactor requested by nessita

Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'ubuntu_sso/utils/webclient/common.py'
--- ubuntu_sso/utils/webclient/common.py 2012-01-04 21:26:27 +0000
+++ ubuntu_sso/utils/webclient/common.py 2012-01-12 17:51:24 +0000
@@ -56,7 +56,8 @@
56 """Get a timestamp synchronized with the server."""56 """Get a timestamp synchronized with the server."""
57 # pylint: disable=W051157 # pylint: disable=W0511
58 # TODO: get the synchronized timestamp58 # TODO: get the synchronized timestamp
59 return defer.succeed(time.time())59 timestamp = time.time()
60 return defer.succeed(int(timestamp))
6061
61 def force_use_proxy(self, settings):62 def force_use_proxy(self, settings):
62 """Setup this webclient to use the given proxy settings."""63 """Setup this webclient to use the given proxy settings."""
6364
=== modified file 'ubuntu_sso/utils/webclient/qtnetwork.py'
--- ubuntu_sso/utils/webclient/qtnetwork.py 2012-01-04 21:26:27 +0000
+++ ubuntu_sso/utils/webclient/qtnetwork.py 2012-01-12 17:51:24 +0000
@@ -102,17 +102,19 @@
102 assert reply in self.replies102 assert reply in self.replies
103 d = self.replies.pop(reply)103 d = self.replies.pop(reply)
104 error = reply.error()104 error = reply.error()
105 content = reply.readAll()
105 if not error:106 if not error:
106 headers = defaultdict(list)107 headers = defaultdict(list)
107 for key, value in reply.rawHeaderPairs():108 for key, value in reply.rawHeaderPairs():
108 headers[str(key)].append(str(value))109 headers[str(key)].append(str(value))
109 response = Response(reply.readAll(), headers)110 response = Response(bytes(content), headers)
110 d.callback(response)111 d.callback(response)
111 else:112 else:
113 error_string = reply.errorString()
112 if error == QNetworkReply.AuthenticationRequiredError:114 if error == QNetworkReply.AuthenticationRequiredError:
113 exception = UnauthorizedError(reply.errorString())115 exception = UnauthorizedError(error_string, content)
114 else:116 else:
115 exception = WebClientError(reply.errorString())117 exception = WebClientError(error_string, content)
116 d.errback(exception)118 d.errback(exception)
117119
118 def force_use_proxy(self, settings):120 def force_use_proxy(self, settings):
119121
=== added file 'ubuntu_sso/utils/webclient/restful.py'
--- ubuntu_sso/utils/webclient/restful.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/utils/webclient/restful.py 2012-01-12 17:51:24 +0000
@@ -0,0 +1,52 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2011 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16"""A proxy-enabled restful client."""
17
18import json
19import urllib
20
21from twisted.internet import defer
22
23from ubuntu_sso.utils import webclient
24
25
26class RestfulClient(object):
27 """A proxy-enabled restful client."""
28
29 def __init__(self, service_iri, username=None, password=None,
30 oauth_credentials=None):
31 """Initialize this instance."""
32 assert service_iri.endswith("/")
33 self.service_iri = service_iri
34 self.webclient = webclient.webclient_factory(username=username,
35 password=password)
36 self.oauth_credentials = oauth_credentials
37
38 @defer.inlineCallbacks
39 def restcall(self, method, **kwargs):
40 """Make a restful call."""
41 assert isinstance(method, unicode)
42 for key, value in kwargs.items():
43 if isinstance(value, basestring):
44 assert isinstance(value, unicode)
45 kwargs[key] = value.encode("utf-8")
46 namespace, operation = method.split(".")
47 kwargs["ws.op"] = operation
48 encoded_args = urllib.urlencode(kwargs)
49 iri = self.service_iri + namespace + "?" + encoded_args
50 creds = self.oauth_credentials
51 result = yield self.webclient.request(iri, oauth_credentials=creds)
52 defer.returnValue(json.loads(result.content))
053
=== added file 'ubuntu_sso/utils/webclient/tests/test_restful.py'
--- ubuntu_sso/utils/webclient/tests/test_restful.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/utils/webclient/tests/test_restful.py 2012-01-12 17:51:24 +0000
@@ -0,0 +1,148 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2011 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT AN WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16"""Tests for the proxy-enabled restful client."""
17
18import urlparse
19
20from twisted.internet import defer
21from ubuntuone.devtools.testcases import TestCase
22
23from ubuntu_sso.utils.webclient import restful
24from ubuntu_sso.utils.webclient.common import Response
25
26
27SAMPLE_SERVICE_IRI = u"http://localhost/"
28SAMPLE_NAMESPACE = u"sample_namespace"
29SAMPLE_METHOD = u"sample_method"
30SAMPLE_OPERATION = SAMPLE_NAMESPACE + u"." + SAMPLE_METHOD
31SAMPLE_ARGS = dict(uno=1, dos=u"2", tres=u"ñandú")
32SAMPLE_RESPONSE = restful.json.dumps(SAMPLE_ARGS)
33SAMPLE_USERNAME = "joeuser@example.com"
34SAMPLE_PASSWORD = "clavesecreta"
35SAMPLE_OAUTH_CREDS = dict(token="1234", etc="456")
36
37
38class FakeWebClient(object):
39 """A fake web client."""
40
41 def __init__(self, **kwargs):
42 """Initialize this faker."""
43 self.return_value = SAMPLE_RESPONSE
44 self.called = []
45 self.init_kwargs = kwargs
46
47 def request(self, iri, *args, **kwargs):
48 """Return a deferred that will be fired with a Response object."""
49 self.called.append((iri, args, kwargs))
50 return defer.succeed(Response(self.return_value))
51
52
53class BaseTestCase(TestCase):
54 """The base for the Restful Client testcases."""
55
56 @defer.inlineCallbacks
57 def setUp(self):
58 """Initialize this test case."""
59 yield super(BaseTestCase, self).setUp()
60 self.wc = None
61 self.patch(restful.webclient, "webclient_factory",
62 self.webclient_factory)
63
64 def webclient_factory(self, **kwargs):
65 """A factory that saves the webclient created."""
66 self.wc = FakeWebClient(**kwargs)
67 return self.wc
68
69
70class RestfulClientTestCase(BaseTestCase):
71 """Tests for the proxy-enabled Restful Client."""
72
73 @defer.inlineCallbacks
74 def setUp(self):
75 """Initialize this testcase."""
76 yield super(RestfulClientTestCase, self).setUp()
77 self.rc = restful.RestfulClient(SAMPLE_SERVICE_IRI)
78
79 def test_has_a_webclient(self):
80 """The RC has a webclient."""
81 self.assertEqual(self.rc.webclient, self.wc)
82
83 @defer.inlineCallbacks
84 def test_can_make_calls(self):
85 """The RC can make webcalls."""
86 yield self.rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS)
87 self.assertEqual(len(self.wc.called), 1)
88
89 @defer.inlineCallbacks
90 def get_parsed_url(self):
91 """Call the sample operation, and return the url used."""
92 yield self.rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS)
93 iri, _, _ = self.wc.called[0]
94 uri = iri.encode("ascii")
95 defer.returnValue(urlparse.urlparse(uri))
96
97 @defer.inlineCallbacks
98 def test_restful_namespace_added_to_url(self):
99 """The restful namespace is added to the url."""
100 url = yield self.get_parsed_url()
101 self.assertTrue(url.path.endswith(SAMPLE_NAMESPACE),
102 "The namespace is included in url")
103
104 @defer.inlineCallbacks
105 def test_restful_method_added_to_url(self):
106 """The restful method is added to the url."""
107 url = yield self.get_parsed_url()
108 url_query = urlparse.parse_qs(url.query)
109 self.assertEqual(url_query["ws.op"][0], SAMPLE_METHOD)
110
111 @defer.inlineCallbacks
112 def test_arguments_added_to_call(self):
113 """The keyword arguments are used in the called url."""
114 url = yield self.get_parsed_url()
115 url_query = dict(urlparse.parse_qsl(url.query))
116 del(url_query["ws.op"])
117 expected = {}
118 for key, value in SAMPLE_ARGS.items():
119 if isinstance(value, unicode):
120 expected[key] = value.encode("utf-8")
121 else:
122 expected[key] = str(value)
123 self.assertEqual(url_query, expected)
124
125 @defer.inlineCallbacks
126 def test_return_value_json_parsed(self):
127 """The result is json parsed before being returned."""
128 result = yield self.rc.restcall(SAMPLE_OPERATION)
129 self.assertEqual(result, SAMPLE_ARGS)
130
131
132class AuthenticationOptionsTestCase(BaseTestCase):
133 """Tests for the authentication options."""
134
135 def test_passes_userpass_to_webclient_init(self):
136 """The RestfulClient passes the user and pass to the webclient."""
137 expected = dict(username=SAMPLE_USERNAME, password=SAMPLE_PASSWORD)
138 restful.RestfulClient(SAMPLE_SERVICE_IRI, **expected)
139 self.assertEqual(self.wc.init_kwargs, expected)
140
141 @defer.inlineCallbacks
142 def test_passes_oauth_creds_to_request(self):
143 """The RestfulClient passes the credentials in each request."""
144 kwargs = dict(oauth_credentials=SAMPLE_OAUTH_CREDS)
145 rc = restful.RestfulClient(SAMPLE_SERVICE_IRI, **kwargs)
146 yield rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS)
147 _, _, kwargs = self.wc.called[0]
148 self.assertEqual(kwargs["oauth_credentials"], SAMPLE_OAUTH_CREDS)
0149
=== modified file 'ubuntu_sso/utils/webclient/tests/test_webclient.py'
--- ubuntu_sso/utils/webclient/tests/test_webclient.py 2012-01-11 04:37:08 +0000
+++ ubuntu_sso/utils/webclient/tests/test_webclient.py 2012-01-12 17:51:24 +0000
@@ -293,6 +293,14 @@
293 oauth_credentials=SAMPLE_CREDENTIALS)293 oauth_credentials=SAMPLE_CREDENTIALS)
294 self.assertEqual(SAMPLE_RESOURCE, result.content)294 self.assertEqual(SAMPLE_RESOURCE, result.content)
295295
296 @defer.inlineCallbacks
297 def test_returned_content_are_bytes(self):
298 """The returned content are bytes."""
299 result = yield self.wc.request(self.base_iri + OAUTHRESOURCE,
300 oauth_credentials=SAMPLE_CREDENTIALS)
301 self.assertTrue(isinstance(result.content, bytes),
302 "The type of %r must be bytes" % result.content)
303
296304
297class BasicProxyTestCase(SquidTestCase):305class BasicProxyTestCase(SquidTestCase):
298 """Test that the proxy works at all."""306 """Test that the proxy works at all."""

Subscribers

People subscribed via source and target branches