Merge ~jugmac00/lazr.restfulclient:apply-black into lazr.restfulclient:main
- Git
- lp:~jugmac00/lazr.restfulclient
- apply-black
- Merge into main
Proposed by
Jürgen Gmach
Status: | Merged |
---|---|
Merged at revision: | c7c665bb9c0d8ac232e05f333f6c5229eee08ace |
Proposed branch: | ~jugmac00/lazr.restfulclient:apply-black |
Merge into: | lazr.restfulclient:main |
Diff against target: |
2926 lines (+812/-550) 24 files modified
.git-blame-ignore-revs (+2/-0) .pre-commit-config.yaml (+18/-1) NEWS.rst (+1/-0) pyproject.toml (+8/-0) setup.cfg (+8/-0) setup.py (+44/-41) src/lazr/__init__.py (+5/-3) src/lazr/restfulclient/_browser.py (+140/-81) src/lazr/restfulclient/_json.py (+2/-1) src/lazr/restfulclient/authorize/__init__.py (+10/-5) src/lazr/restfulclient/authorize/oauth.py (+72/-56) src/lazr/restfulclient/docs/Makefile (+1/-1) src/lazr/restfulclient/docs/authorizer.standalone.rst (+0/-1) src/lazr/restfulclient/docs/conf.py (+37/-27) src/lazr/restfulclient/docs/toplevel.rst (+0/-1) src/lazr/restfulclient/errors.py (+31/-28) src/lazr/restfulclient/resource.py (+290/-192) src/lazr/restfulclient/tests/__init__.py (+3/-3) src/lazr/restfulclient/tests/example.py (+24/-14) src/lazr/restfulclient/tests/test_atomicfilecache.py (+31/-33) src/lazr/restfulclient/tests/test_docs.py (+39/-21) src/lazr/restfulclient/tests/test_error.py (+12/-4) src/lazr/restfulclient/tests/test_oauth.py (+33/-36) tox.ini (+1/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+411676@code.launchpad.net |
Commit message
Apply black code formatter
Description of the change
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs |
2 | new file mode 100644 |
3 | index 0000000..135d4d2 |
4 | --- /dev/null |
5 | +++ b/.git-blame-ignore-revs |
6 | @@ -0,0 +1,2 @@ |
7 | +# apply black |
8 | +2417072998741bd627c9906b9d4cbdff915e36a5 |
9 | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml |
10 | index a9e24ce..ac3063e 100644 |
11 | --- a/.pre-commit-config.yaml |
12 | +++ b/.pre-commit-config.yaml |
13 | @@ -2,10 +2,27 @@ |
14 | # See https://pre-commit.com/hooks.html for more hooks |
15 | repos: |
16 | - repo: https://github.com/pre-commit/pre-commit-hooks |
17 | - rev: v3.2.0 |
18 | + rev: v4.0.1 |
19 | hooks: |
20 | - id: check-added-large-files |
21 | - id: check-ast |
22 | + - id: check-json |
23 | - id: check-merge-conflict |
24 | + - id: check-toml |
25 | + - id: check-xml |
26 | - id: check-yaml |
27 | - id: debug-statements |
28 | + - id: end-of-file-fixer |
29 | + - id: trailing-whitespace |
30 | +- repo: https://github.com/PyCQA/isort |
31 | + rev: 5.10.1 |
32 | + hooks: |
33 | + - id: isort |
34 | +- repo: https://github.com/psf/black |
35 | + rev: 21.10b0 |
36 | + hooks: |
37 | + - id: black |
38 | +- repo: https://github.com/PyCQA/flake8 |
39 | + rev: 4.0.1 |
40 | + hooks: |
41 | + - id: flake8 |
42 | diff --git a/NEWS.rst b/NEWS.rst |
43 | index 3559a22..c87b692 100644 |
44 | --- a/NEWS.rst |
45 | +++ b/NEWS.rst |
46 | @@ -7,6 +7,7 @@ NEWS for lazr.restfulclient |
47 | |
48 | - Add ``pre-commit`` configuration. |
49 | - Publish documentation on Read the Docs. |
50 | + - Apply black code formatter. |
51 | |
52 | 0.14.4 (2021-09-13) |
53 | =================== |
54 | diff --git a/pyproject.toml b/pyproject.toml |
55 | new file mode 100644 |
56 | index 0000000..15e7219 |
57 | --- /dev/null |
58 | +++ b/pyproject.toml |
59 | @@ -0,0 +1,8 @@ |
60 | +[tool.black] |
61 | +line-length = 79 |
62 | +target-version = ['py27'] |
63 | + |
64 | +[tool.isort] |
65 | +profile = "black" |
66 | +multi_line_output = 3 |
67 | +line_length = 79 |
68 | diff --git a/setup.cfg b/setup.cfg |
69 | index 2a9acf1..1d922c2 100644 |
70 | --- a/setup.cfg |
71 | +++ b/setup.cfg |
72 | @@ -1,2 +1,10 @@ |
73 | [bdist_wheel] |
74 | universal = 1 |
75 | + |
76 | +[flake8] |
77 | +ignore = |
78 | + # incompatible with black |
79 | + E203 |
80 | + # W503 and W504 are mutally exclusive |
81 | + W503 |
82 | + W504 |
83 | diff --git a/setup.py b/setup.py |
84 | index 2ddeb5b..764dd14 100644 |
85 | --- a/setup.py |
86 | +++ b/setup.py |
87 | @@ -8,90 +8,93 @@ |
88 | # under the terms of the GNU Lesser General Public License as published by |
89 | # the Free Software Foundation, version 3 of the License. |
90 | # |
91 | -# lazr.restfulclient is distributed in the hope that it will be useful, but WITHOUT |
92 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
93 | -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
94 | +# lazr.restfulclient is distributed in the hope that it will be useful, but |
95 | +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
96 | +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
97 | # License for more details. |
98 | # |
99 | # You should have received a copy of the GNU Lesser General Public License |
100 | # along with lazr.restfulclient. If not, see <http://www.gnu.org/licenses/>. |
101 | |
102 | -from setuptools import setup, find_packages |
103 | +from setuptools import find_packages, setup |
104 | + |
105 | |
106 | # generic helpers primarily for the long_description |
107 | def generate(*docname_or_string): |
108 | - marker = '.. pypi description ends here' |
109 | + marker = ".. pypi description ends here" |
110 | res = [] |
111 | for value in docname_or_string: |
112 | - if value.endswith('.rst'): |
113 | + if value.endswith(".rst"): |
114 | with open(value) as f: |
115 | value = f.read() |
116 | idx = value.find(marker) |
117 | if idx >= 0: |
118 | value = value[:idx] |
119 | res.append(value) |
120 | - if not value.endswith('\n'): |
121 | - res.append('') |
122 | - return '\n'.join(res) |
123 | + if not value.endswith("\n"): |
124 | + res.append("") |
125 | + return "\n".join(res) |
126 | + |
127 | + |
128 | # end generic helpers |
129 | |
130 | |
131 | tests_require = [ |
132 | - 'fixtures>=1.3.0', |
133 | - 'lazr.authentication', |
134 | - 'lazr.restful>=0.11.0', |
135 | + "fixtures>=1.3.0", |
136 | + "lazr.authentication", |
137 | + "lazr.restful>=0.11.0", |
138 | 'mock; python_version < "3"', |
139 | - 'oauth', |
140 | - 'testtools', |
141 | - 'wsgi_intercept', |
142 | - 'zope.testrunner', |
143 | - ] |
144 | + "oauth", |
145 | + "testtools", |
146 | + "wsgi_intercept", |
147 | + "zope.testrunner", |
148 | +] |
149 | |
150 | setup( |
151 | - name='lazr.restfulclient', |
152 | - version='0.14.4', |
153 | - namespace_packages=['lazr'], |
154 | - packages=find_packages('src'), |
155 | - package_dir={'':'src'}, |
156 | + name="lazr.restfulclient", |
157 | + version="0.14.4", |
158 | + namespace_packages=["lazr"], |
159 | + packages=find_packages("src"), |
160 | + package_dir={"": "src"}, |
161 | include_package_data=True, |
162 | zip_safe=False, |
163 | - maintainer='LAZR Developers', |
164 | - maintainer_email='lazr-developers@lists.launchpad.net', |
165 | - description=open('README.rst').readline().strip(), |
166 | + maintainer="LAZR Developers", |
167 | + maintainer_email="lazr-developers@lists.launchpad.net", |
168 | + description=open("README.rst").readline().strip(), |
169 | long_description=generate( |
170 | - 'src/lazr/restfulclient/docs/index.rst', |
171 | - 'NEWS.rst'), |
172 | - license='LGPL v3', |
173 | + "src/lazr/restfulclient/docs/index.rst", "NEWS.rst" |
174 | + ), |
175 | + license="LGPL v3", |
176 | install_requires=[ |
177 | - 'distro', |
178 | + "distro", |
179 | 'httplib2; python_version < "3"', |
180 | 'httplib2>=0.7.7; python_version >= "3"', |
181 | 'importlib-metadata; python_version < "3.8"', |
182 | - 'oauthlib', |
183 | - 'setuptools', |
184 | - 'six', |
185 | - 'wadllib>=1.1.4', |
186 | - ], |
187 | - url='https://launchpad.net/lazr.restfulclient', |
188 | + "oauthlib", |
189 | + "setuptools", |
190 | + "six", |
191 | + "wadllib>=1.1.4", |
192 | + ], |
193 | + url="https://launchpad.net/lazr.restfulclient", |
194 | project_urls={ |
195 | "Source": "https://code.launchpad.net/lazr.restfulclient", |
196 | "Issue Tracker": "https://bugs.launchpad.net/lazr.restfulclient", |
197 | "Documentation": "https://lazrrestfulclient.readthedocs.io/en/latest/", |
198 | }, |
199 | - download_url= 'https://launchpad.net/lazr.restfulclient/+download', |
200 | + download_url="https://launchpad.net/lazr.restfulclient/+download", |
201 | classifiers=[ |
202 | "Development Status :: 5 - Production/Stable", |
203 | "Intended Audience :: Developers", |
204 | - "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", |
205 | + "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", # noqa: E501 |
206 | "Operating System :: OS Independent", |
207 | "Programming Language :: Python", |
208 | "Programming Language :: Python :: 2", |
209 | "Programming Language :: Python :: 3", |
210 | - ], |
211 | + ], |
212 | tests_require=tests_require, |
213 | extras_require=dict( |
214 | - docs=['Sphinx'], |
215 | + docs=["Sphinx"], |
216 | test=tests_require, |
217 | ), |
218 | - test_suite='lazr.restfulclient.tests', |
219 | - ) |
220 | + test_suite="lazr.restfulclient.tests", |
221 | +) |
222 | diff --git a/src/lazr/__init__.py b/src/lazr/__init__.py |
223 | index b2362fd..fcc94e2 100644 |
224 | --- a/src/lazr/__init__.py |
225 | +++ b/src/lazr/__init__.py |
226 | @@ -6,9 +6,9 @@ |
227 | # under the terms of the GNU Lesser General Public License as published by |
228 | # the Free Software Foundation, version 3 of the License. |
229 | # |
230 | -# lazr.restfulclient is distributed in the hope that it will be useful, but WITHOUT |
231 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
232 | -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
233 | +# lazr.restfulclient is distributed in the hope that it will be useful, but |
234 | +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
235 | +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
236 | # License for more details. |
237 | # |
238 | # You should have received a copy of the GNU Lesser General Public License |
239 | @@ -17,7 +17,9 @@ |
240 | # this is a namespace package |
241 | try: |
242 | import pkg_resources |
243 | + |
244 | pkg_resources.declare_namespace(__name__) |
245 | except ImportError: |
246 | import pkgutil |
247 | + |
248 | __path__ = pkgutil.extend_path(__path__, __name__) |
249 | diff --git a/src/lazr/restfulclient/_browser.py b/src/lazr/restfulclient/_browser.py |
250 | index cc23467..d0b4211 100644 |
251 | --- a/src/lazr/restfulclient/_browser.py |
252 | +++ b/src/lazr/restfulclient/_browser.py |
253 | @@ -25,32 +25,33 @@ lazr.restfulclient API. (But maybe it should be?) |
254 | |
255 | __metaclass__ = type |
256 | __all__ = [ |
257 | - 'Browser', |
258 | - 'RestfulHttp', |
259 | - 'ssl_certificate_validation_disabled', |
260 | - ] |
261 | + "Browser", |
262 | + "RestfulHttp", |
263 | + "ssl_certificate_validation_disabled", |
264 | +] |
265 | |
266 | import atexit |
267 | import errno |
268 | -from hashlib import md5 |
269 | -from io import BytesIO |
270 | -from json import dumps |
271 | import os |
272 | import re |
273 | import shutil |
274 | import sys |
275 | import tempfile |
276 | +from hashlib import md5 |
277 | +from io import BytesIO |
278 | +from json import dumps |
279 | + |
280 | # Import sleep directly into the module so we can monkey-patch it |
281 | # during a test. |
282 | from time import sleep |
283 | -from httplib2 import ( |
284 | - Http, |
285 | - urlnorm, |
286 | - ) |
287 | + |
288 | +from httplib2 import Http, urlnorm |
289 | + |
290 | try: |
291 | from httplib2 import proxy_info_from_environment |
292 | except ImportError: |
293 | from httplib2 import ProxyInfo |
294 | + |
295 | proxy_info_from_environment = ProxyInfo.from_environment |
296 | |
297 | try: |
298 | @@ -60,14 +61,15 @@ except ImportError: |
299 | from urllib import urlencode |
300 | |
301 | from wadllib.application import Application |
302 | -from lazr.uri import URI |
303 | -from lazr.restfulclient.errors import error_for, HTTPError |
304 | + |
305 | from lazr.restfulclient._json import DatetimeJSONEncoder |
306 | +from lazr.restfulclient.errors import HTTPError, error_for |
307 | +from lazr.uri import URI |
308 | |
309 | if bytes is str: |
310 | # Python 2 |
311 | - unicode_type = unicode |
312 | - str_types = basestring |
313 | + unicode_type = unicode # noqa: F821 |
314 | + str_types = basestring # noqa: F821 |
315 | else: |
316 | unicode_type = str |
317 | str_types = str |
318 | @@ -77,9 +79,9 @@ else: |
319 | # from httplib2, but its cache name format changed in 0.12.0 and we want to |
320 | # stick with the previous version. |
321 | |
322 | -re_url_scheme = re.compile(br'^\w+://') |
323 | -re_url_scheme_s = re.compile(r'^\w+://') |
324 | -re_slash = re.compile(br'[?/:|]+') |
325 | +re_url_scheme = re.compile(br"^\w+://") |
326 | +re_url_scheme_s = re.compile(r"^\w+://") |
327 | +re_slash = re.compile(br"[?/:|]+") |
328 | |
329 | |
330 | def safename(filename): |
331 | @@ -90,20 +92,20 @@ def safename(filename): |
332 | """ |
333 | try: |
334 | if isinstance(filename, bytes): |
335 | - filename_match = filename.decode('utf-8') |
336 | + filename_match = filename.decode("utf-8") |
337 | else: |
338 | filename_match = filename |
339 | |
340 | if re_url_scheme_s.match(filename_match): |
341 | if isinstance(filename, bytes): |
342 | - filename = filename.decode('utf-8') |
343 | - filename = filename.encode('idna') |
344 | + filename = filename.decode("utf-8") |
345 | + filename = filename.encode("idna") |
346 | else: |
347 | - filename = filename.encode('idna') |
348 | + filename = filename.encode("idna") |
349 | except UnicodeError: |
350 | pass |
351 | if isinstance(filename, unicode_type): |
352 | - filename = filename.encode('utf-8') |
353 | + filename = filename.encode("utf-8") |
354 | filemd5 = md5(filename).hexdigest() |
355 | filename = re_url_scheme.sub(b"", filename) |
356 | filename = re_slash.sub(b",", filename) |
357 | @@ -126,7 +128,7 @@ def safename(filename): |
358 | maximum_length_before_md5_sum = maximum_filename_length - 32 - 1 |
359 | if len(filename) > maximum_length_before_md5_sum: |
360 | filename = filename[:maximum_length_before_md5_sum] |
361 | - return ",".join((filename.decode('utf-8'), filemd5)) |
362 | + return ",".join((filename.decode("utf-8"), filemd5)) |
363 | |
364 | |
365 | def ssl_certificate_validation_disabled(): |
366 | @@ -136,12 +138,11 @@ def ssl_certificate_validation_disabled(): |
367 | error, we allow an environment variable, |
368 | ``LP_DISABLE_SSL_CERTIFICATE_VALIDATION`` to disable the check. |
369 | """ |
370 | - return bool( |
371 | - os.environ.get('LP_DISABLE_SSL_CERTIFICATE_VALIDATION', False)) |
372 | + return bool(os.environ.get("LP_DISABLE_SSL_CERTIFICATE_VALIDATION", False)) |
373 | |
374 | |
375 | -if os.path.exists('/etc/ssl/certs/ca-certificates.crt'): |
376 | - SYSTEM_CA_CERTS = '/etc/ssl/certs/ca-certificates.crt' |
377 | +if os.path.exists("/etc/ssl/certs/ca-certificates.crt"): |
378 | + SYSTEM_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt" |
379 | else: |
380 | from httplib2 import CA_CERTS as SYSTEM_CA_CERTS |
381 | |
382 | @@ -156,34 +157,61 @@ class RestfulHttp(Http): |
383 | |
384 | maximum_cache_filename_length = 143 |
385 | |
386 | - def __init__(self, authorizer=None, cache=None, timeout=None, |
387 | - proxy_info=proxy_info_from_environment): |
388 | + def __init__( |
389 | + self, |
390 | + authorizer=None, |
391 | + cache=None, |
392 | + timeout=None, |
393 | + proxy_info=proxy_info_from_environment, |
394 | + ): |
395 | cert_disabled = ssl_certificate_validation_disabled() |
396 | super(RestfulHttp, self).__init__( |
397 | - cache, timeout, proxy_info, |
398 | + cache, |
399 | + timeout, |
400 | + proxy_info, |
401 | disable_ssl_certificate_validation=cert_disabled, |
402 | - ca_certs=SYSTEM_CA_CERTS) |
403 | + ca_certs=SYSTEM_CA_CERTS, |
404 | + ) |
405 | self.authorizer = authorizer |
406 | if self.authorizer is not None: |
407 | self.authorizer.authorizeSession(self) |
408 | |
409 | - def _request(self, conn, host, absolute_uri, request_uri, method, body, |
410 | - headers, redirections, cachekey): |
411 | + def _request( |
412 | + self, |
413 | + conn, |
414 | + host, |
415 | + absolute_uri, |
416 | + request_uri, |
417 | + method, |
418 | + body, |
419 | + headers, |
420 | + redirections, |
421 | + cachekey, |
422 | + ): |
423 | """Use the authorizer to authorize an outgoing request.""" |
424 | - if 'authorization' in headers: |
425 | + if "authorization" in headers: |
426 | # There's an authorization header left over from a |
427 | # previous request that resulted in a redirect. Resources |
428 | # protected by OAuth or HTTP Digest must send a distinct |
429 | # Authorization header with each request, to prevent |
430 | # playback attacks. Remove the Authorization header and |
431 | # start again. |
432 | - del headers['authorization'] |
433 | + del headers["authorization"] |
434 | if self.authorizer is not None: |
435 | self.authorizer.authorizeRequest( |
436 | - absolute_uri, method, body, headers) |
437 | + absolute_uri, method, body, headers |
438 | + ) |
439 | return super(RestfulHttp, self)._request( |
440 | - conn, host, absolute_uri, request_uri, method, body, headers, |
441 | - redirections, cachekey) |
442 | + conn, |
443 | + host, |
444 | + absolute_uri, |
445 | + request_uri, |
446 | + method, |
447 | + body, |
448 | + headers, |
449 | + redirections, |
450 | + cachekey, |
451 | + ) |
452 | |
453 | def _getCachedHeader(self, uri, header): |
454 | """Retrieve a cached value for an HTTP header.""" |
455 | @@ -213,7 +241,7 @@ class AtomicFileCache(object): |
456 | self._cache_dir = os.path.normpath(cache) |
457 | self._get_safe_name = safe |
458 | try: |
459 | - os.makedirs(self._cache_dir) |
460 | + os.makedirs(self._cache_dir) |
461 | except OSError as e: |
462 | if e.errno != errno.EEXIST: |
463 | raise |
464 | @@ -226,7 +254,8 @@ class AtomicFileCache(object): |
465 | # possible that it will clash with a temporary file that we |
466 | # create. |
467 | raise ValueError( |
468 | - "Cache key cannot start with '%s'" % self.TEMPFILE_PREFIX) |
469 | + "Cache key cannot start with '%s'" % self.TEMPFILE_PREFIX |
470 | + ) |
471 | return os.path.join(self._cache_dir, safe_key) |
472 | |
473 | def get(self, key): |
474 | @@ -242,7 +271,7 @@ class AtomicFileCache(object): |
475 | """ |
476 | cache_full_path = self._get_key_path(key) |
477 | try: |
478 | - f = open(cache_full_path, 'rb') |
479 | + f = open(cache_full_path, "rb") |
480 | try: |
481 | return f.read() |
482 | finally: |
483 | @@ -259,13 +288,14 @@ class AtomicFileCache(object): |
484 | """ |
485 | # Open a temporary file |
486 | handle, path_name = tempfile.mkstemp( |
487 | - prefix=self.TEMPFILE_PREFIX, dir=self._cache_dir) |
488 | - f = os.fdopen(handle, 'wb') |
489 | + prefix=self.TEMPFILE_PREFIX, dir=self._cache_dir |
490 | + ) |
491 | + f = os.fdopen(handle, "wb") |
492 | f.write(value) |
493 | f.close() |
494 | cache_full_path = self._get_key_path(key) |
495 | # And rename atomically (on POSIX at least) |
496 | - if sys.platform == 'win32' and os.path.exists(cache_full_path): |
497 | + if sys.platform == "win32" and os.path.exists(cache_full_path): |
498 | os.unlink(cache_full_path) |
499 | os.rename(path_name, cache_full_path) |
500 | |
501 | @@ -299,10 +329,12 @@ class MultipleRepresentationCache(AtomicFileCache): |
502 | This class is very much not thread-safe, but FileCache isn't |
503 | thread-safe anyway. |
504 | """ |
505 | + |
506 | def __init__(self, cache): |
507 | """Tell FileCache to call append_media_type when generating keys.""" |
508 | super(MultipleRepresentationCache, self).__init__( |
509 | - cache, self.append_media_type) |
510 | + cache, self.append_media_type |
511 | + ) |
512 | self.request_media_type = None |
513 | |
514 | def append_media_type(self, key): |
515 | @@ -313,20 +345,20 @@ class MultipleRepresentationCache(AtomicFileCache): |
516 | media types. |
517 | """ |
518 | if self.request_media_type is not None: |
519 | - key = key + '-' + self.request_media_type |
520 | + key = key + "-" + self.request_media_type |
521 | return safename(key) |
522 | |
523 | def _getCachedHeader(self, uri, header): |
524 | """Retrieve a cached value for an HTTP header.""" |
525 | (scheme, authority, request_uri, cachekey) = urlnorm(uri) |
526 | cached_value = self.get(cachekey) |
527 | - header_start = header + ':' |
528 | + header_start = header + ":" |
529 | if not isinstance(header_start, bytes): |
530 | - header_start = header_start.encode('utf-8') |
531 | + header_start = header_start.encode("utf-8") |
532 | if cached_value is not None: |
533 | for line in BytesIO(cached_value): |
534 | if line.startswith(header_start): |
535 | - return line[len(header_start):].strip() |
536 | + return line[len(header_start) :].strip() |
537 | return None |
538 | |
539 | |
540 | @@ -336,8 +368,16 @@ class Browser: |
541 | NOT_MODIFIED = object() |
542 | MAX_RETRIES = 6 |
543 | |
544 | - def __init__(self, service_root, credentials, cache=None, timeout=None, |
545 | - proxy_info=None, user_agent=None, max_retries=MAX_RETRIES): |
546 | + def __init__( |
547 | + self, |
548 | + service_root, |
549 | + credentials, |
550 | + cache=None, |
551 | + timeout=None, |
552 | + proxy_info=None, |
553 | + user_agent=None, |
554 | + max_retries=MAX_RETRIES, |
555 | + ): |
556 | """Initialize, possibly creating a cache. |
557 | |
558 | If no cache is provided, a temporary directory will be used as |
559 | @@ -350,49 +390,64 @@ class Browser: |
560 | if isinstance(cache, str_types): |
561 | cache = MultipleRepresentationCache(cache) |
562 | self._connection = service_root.httpFactory( |
563 | - credentials, cache, timeout, proxy_info) |
564 | + credentials, cache, timeout, proxy_info |
565 | + ) |
566 | self.user_agent = user_agent |
567 | self.max_retries = max_retries |
568 | |
569 | def _request_and_retry(self, url, method, body, headers): |
570 | - for retry_count in range(0, self.max_retries+1): |
571 | + for retry_count in range(0, self.max_retries + 1): |
572 | response, content = self._connection.request( |
573 | - url, method=method, body=body, headers=headers) |
574 | - if (response.status in [502, 503] |
575 | - and retry_count < self.max_retries): |
576 | + url, method=method, body=body, headers=headers |
577 | + ) |
578 | + if ( |
579 | + response.status in [502, 503] |
580 | + and retry_count < self.max_retries |
581 | + ): |
582 | # The server returned a 502 or 503. Sleep for 0, 1, 2, |
583 | # 4, 8, 16, ... seconds and try again. |
584 | - sleep_for = int(2**(retry_count-1)) |
585 | + sleep_for = int(2 ** (retry_count - 1)) |
586 | sleep(sleep_for) |
587 | else: |
588 | break |
589 | # Either the request succeeded or we gave up. |
590 | return response, content |
591 | |
592 | - def _request(self, url, data=None, method='GET', |
593 | - media_type='application/json', extra_headers=None): |
594 | + def _request( |
595 | + self, |
596 | + url, |
597 | + data=None, |
598 | + method="GET", |
599 | + media_type="application/json", |
600 | + extra_headers=None, |
601 | + ): |
602 | """Create an authenticated request object.""" |
603 | # If the user is trying to get data that has been redacted, |
604 | # give a helpful message. |
605 | if url == "tag:launchpad.net:2008:redacted": |
606 | - raise ValueError("You tried to access a resource that you " |
607 | - "don't have the server-side permission to see.") |
608 | + raise ValueError( |
609 | + "You tried to access a resource that you " |
610 | + "don't have the server-side permission to see." |
611 | + ) |
612 | |
613 | # Add extra headers for the request. |
614 | - headers = {'Accept': media_type} |
615 | + headers = {"Accept": media_type} |
616 | if self.user_agent is not None: |
617 | - headers['User-Agent'] = self.user_agent |
618 | + headers["User-Agent"] = self.user_agent |
619 | if isinstance(self._connection.cache, MultipleRepresentationCache): |
620 | self._connection.cache.request_media_type = media_type |
621 | if extra_headers is not None: |
622 | headers.update(extra_headers) |
623 | response, content = self._request_and_retry( |
624 | - str(url), method=method, body=data, headers=headers) |
625 | + str(url), method=method, body=data, headers=headers |
626 | + ) |
627 | if response.status == 304: |
628 | # The resource didn't change. |
629 | - if content == b'': |
630 | - if ('If-None-Match' in headers |
631 | - or 'If-Modified-Since' in headers): |
632 | + if content == b"": |
633 | + if ( |
634 | + "If-None-Match" in headers |
635 | + or "If-Modified-Since" in headers |
636 | + ): |
637 | # The caller made a conditional request, and the |
638 | # condition failed. Rather than send an empty |
639 | # representation, which might be misinterpreted, |
640 | @@ -434,7 +489,7 @@ class Browser: |
641 | if isinstance(resource_or_uri, (str_types, URI)): |
642 | url = resource_or_uri |
643 | else: |
644 | - method = resource_or_uri.get_method('get') |
645 | + method = resource_or_uri.get_method("get") |
646 | url = method.build_request_url() |
647 | response, content = self._request(url, extra_headers=headers) |
648 | if return_response: |
649 | @@ -443,45 +498,49 @@ class Browser: |
650 | |
651 | def get_wadl_application(self, url): |
652 | """GET a WADL representation of the resource at the requested url.""" |
653 | - wadl_type = 'application/vnd.sun.wadl+xml' |
654 | + wadl_type = "application/vnd.sun.wadl+xml" |
655 | response, content = self._request(url, media_type=wadl_type) |
656 | url = str(url) |
657 | if not isinstance(content, bytes): |
658 | - content = content.encode('utf-8') |
659 | + content = content.encode("utf-8") |
660 | return Application(url, content) |
661 | |
662 | def post(self, url, method_name, **kws): |
663 | """POST a request to the web service.""" |
664 | - kws['ws.op'] = method_name |
665 | + kws["ws.op"] = method_name |
666 | data = urlencode(kws) |
667 | - return self._request(url, data, 'POST') |
668 | + return self._request(url, data, "POST") |
669 | |
670 | def put(self, url, representation, media_type, headers=None): |
671 | """PUT the given representation to the URL.""" |
672 | - extra_headers = {'Content-Type': media_type} |
673 | + extra_headers = {"Content-Type": media_type} |
674 | if headers is not None: |
675 | extra_headers.update(headers) |
676 | return self._request( |
677 | - url, representation, 'PUT', extra_headers=extra_headers) |
678 | + url, representation, "PUT", extra_headers=extra_headers |
679 | + ) |
680 | |
681 | def delete(self, url): |
682 | """DELETE the resource at the given URL.""" |
683 | - self._request(url, method='DELETE') |
684 | + self._request(url, method="DELETE") |
685 | return None |
686 | |
687 | def patch(self, url, representation, headers=None): |
688 | """PATCH the object at url with the updated representation.""" |
689 | - extra_headers = {'Content-Type': 'application/json'} |
690 | + extra_headers = {"Content-Type": "application/json"} |
691 | if headers is not None: |
692 | extra_headers.update(headers) |
693 | # httplib2 doesn't know about the PATCH method, so we need to |
694 | # do some work ourselves. Pull any cached value of "ETag" out |
695 | # and use it as the value for "If-Match". |
696 | - cached_etag = self._connection._getCachedHeader(str(url), 'etag') |
697 | + cached_etag = self._connection._getCachedHeader(str(url), "etag") |
698 | if cached_etag is not None and not self._connection.ignore_etag: |
699 | # http://www.w3.org/1999/04/Editing/ |
700 | - headers['If-Match'] = cached_etag |
701 | + headers["If-Match"] = cached_etag |
702 | |
703 | return self._request( |
704 | - url, dumps(representation, cls=DatetimeJSONEncoder), |
705 | - 'PATCH', extra_headers=extra_headers) |
706 | + url, |
707 | + dumps(representation, cls=DatetimeJSONEncoder), |
708 | + "PATCH", |
709 | + extra_headers=extra_headers, |
710 | + ) |
711 | diff --git a/src/lazr/restfulclient/_json.py b/src/lazr/restfulclient/_json.py |
712 | index 136c564..e3bacf7 100644 |
713 | --- a/src/lazr/restfulclient/_json.py |
714 | +++ b/src/lazr/restfulclient/_json.py |
715 | @@ -19,7 +19,7 @@ |
716 | """Classes for working with JSON.""" |
717 | |
718 | __metaclass__ = type |
719 | -__all__ = ['DatetimeJSONEncoder'] |
720 | +__all__ = ["DatetimeJSONEncoder"] |
721 | |
722 | import datetime |
723 | from json import JSONEncoder |
724 | @@ -30,6 +30,7 @@ class DatetimeJSONEncoder(JSONEncoder): |
725 | |
726 | Datetime objects are formatted according to ISO 1601. |
727 | """ |
728 | + |
729 | def default(self, obj): |
730 | if isinstance(obj, datetime.datetime): |
731 | return obj.isoformat() |
732 | diff --git a/src/lazr/restfulclient/authorize/__init__.py b/src/lazr/restfulclient/authorize/__init__.py |
733 | index 471a3f9..faedd2a 100644 |
734 | --- a/src/lazr/restfulclient/authorize/__init__.py |
735 | +++ b/src/lazr/restfulclient/authorize/__init__.py |
736 | @@ -27,9 +27,9 @@ module. |
737 | |
738 | __metaclass__ = type |
739 | __all__ = [ |
740 | - 'BasicHttpAuthorizer', |
741 | - 'HttpAuthorizer', |
742 | - ] |
743 | + "BasicHttpAuthorizer", |
744 | + "HttpAuthorizer", |
745 | +] |
746 | |
747 | import base64 |
748 | |
749 | @@ -47,6 +47,7 @@ class HttpAuthorizer: |
750 | The base class is a null authorizer which does not perform any |
751 | authentication at all. |
752 | """ |
753 | + |
754 | def authorizeSession(self, client): |
755 | """Set up credentials for the entire session.""" |
756 | pass |
757 | @@ -87,8 +88,12 @@ class BasicHttpAuthorizer(HttpAuthorizer): |
758 | |
759 | This sets the authorization header with the username/password. |
760 | """ |
761 | - headers['authorization'] = 'Basic ' + base64.b64encode( |
762 | - "%s:%s" % (self.username, self.password)).strip() |
763 | + headers["authorization"] = ( |
764 | + "Basic " |
765 | + + base64.b64encode( |
766 | + "%s:%s" % (self.username, self.password) |
767 | + ).strip() |
768 | + ) |
769 | |
770 | def authorizeSession(self, client): |
771 | client.add_credentials(self.username, self.password) |
772 | diff --git a/src/lazr/restfulclient/authorize/oauth.py b/src/lazr/restfulclient/authorize/oauth.py |
773 | index 9296f22..09abf0b 100644 |
774 | --- a/src/lazr/restfulclient/authorize/oauth.py |
775 | +++ b/src/lazr/restfulclient/authorize/oauth.py |
776 | @@ -24,31 +24,29 @@ try: |
777 | from configparser import ConfigParser as SafeConfigParser |
778 | except ImportError: |
779 | from ConfigParser import SafeConfigParser |
780 | + |
781 | import os |
782 | import platform |
783 | -import stat |
784 | import socket |
785 | +import stat |
786 | |
787 | -from oauthlib import oauth1 |
788 | import six |
789 | -from six.moves.urllib.parse import ( |
790 | - parse_qs, |
791 | - urlencode, |
792 | - ) |
793 | +from oauthlib import oauth1 |
794 | +from six.moves.urllib.parse import parse_qs, urlencode |
795 | |
796 | from lazr.restfulclient.authorize import HttpAuthorizer |
797 | from lazr.restfulclient.errors import CredentialsFileError |
798 | |
799 | __metaclass__ = type |
800 | __all__ = [ |
801 | - 'AccessToken', |
802 | - 'Consumer', |
803 | - 'OAuthAuthorizer', |
804 | - 'SystemWideConsumer', |
805 | - ] |
806 | + "AccessToken", |
807 | + "Consumer", |
808 | + "OAuthAuthorizer", |
809 | + "SystemWideConsumer", |
810 | +] |
811 | |
812 | |
813 | -CREDENTIALS_FILE_VERSION = '1' |
814 | +CREDENTIALS_FILE_VERSION = "1" |
815 | |
816 | |
817 | # For compatibility, Consumer and AccessToken are defined using terminology |
818 | @@ -56,7 +54,7 @@ CREDENTIALS_FILE_VERSION = '1' |
819 | class Consumer: |
820 | """An OAuth consumer (application).""" |
821 | |
822 | - def __init__(self, key, secret='', application_name=None): |
823 | + def __init__(self, key, secret="", application_name=None): |
824 | """Initialize |
825 | |
826 | :param key: The OAuth consumer key |
827 | @@ -74,16 +72,18 @@ class Consumer: |
828 | class AccessToken: |
829 | """An OAuth access token.""" |
830 | |
831 | - def __init__(self, key, secret='', context=None): |
832 | + def __init__(self, key, secret="", context=None): |
833 | self.key = key |
834 | self.secret = secret |
835 | self.context = context |
836 | |
837 | def to_string(self): |
838 | - return urlencode([ |
839 | - ("oauth_token_secret", self.secret), |
840 | - ("oauth_token", self.key), |
841 | - ]) |
842 | + return urlencode( |
843 | + [ |
844 | + ("oauth_token_secret", self.secret), |
845 | + ("oauth_token", self.key), |
846 | + ] |
847 | + ) |
848 | |
849 | __str__ = to_string |
850 | |
851 | @@ -111,9 +111,10 @@ class SystemWideConsumer(Consumer): |
852 | desktop applications. The OAuth consumer key will be derived from |
853 | system information (platform and hostname). |
854 | """ |
855 | + |
856 | KEY_FORMAT = "System-wide: %s (%s)" |
857 | |
858 | - def __init__(self, application_name, secret=''): |
859 | + def __init__(self, application_name, secret=""): |
860 | """Constructor. |
861 | |
862 | :param application_name: An application name. This will be |
863 | @@ -122,7 +123,8 @@ class SystemWideConsumer(Consumer): |
864 | a misfeature, and lazr.restful doesn't expect it. |
865 | """ |
866 | super(SystemWideConsumer, self).__init__( |
867 | - self.consumer_key, secret, application_name) |
868 | + self.consumer_key, secret, application_name |
869 | + ) |
870 | |
871 | @property |
872 | def consumer_key(self): |
873 | @@ -133,26 +135,33 @@ class SystemWideConsumer(Consumer): |
874 | """ |
875 | try: |
876 | import distro |
877 | + |
878 | distname = distro.name() |
879 | except Exception: |
880 | # This can happen due to various kinds of failures with the data |
881 | # sources used by the distro module. |
882 | - distname = '' |
883 | - if distname == '': |
884 | - distname = platform.system() # (eg. "Windows") |
885 | + distname = "" |
886 | + if distname == "": |
887 | + distname = platform.system() # (eg. "Windows") |
888 | return self.KEY_FORMAT % (distname, socket.gethostname()) |
889 | |
890 | |
891 | class OAuthAuthorizer(HttpAuthorizer): |
892 | """A client that signs every outgoing request with OAuth credentials.""" |
893 | |
894 | - def __init__(self, consumer_name=None, consumer_secret='', |
895 | - access_token=None, oauth_realm="OAuth", |
896 | - application_name=None): |
897 | + def __init__( |
898 | + self, |
899 | + consumer_name=None, |
900 | + consumer_secret="", |
901 | + access_token=None, |
902 | + oauth_realm="OAuth", |
903 | + application_name=None, |
904 | + ): |
905 | self.consumer = None |
906 | if consumer_name is not None: |
907 | self.consumer = Consumer( |
908 | - consumer_name, consumer_secret, application_name) |
909 | + consumer_name, consumer_secret, application_name |
910 | + ) |
911 | self.access_token = access_token |
912 | self.oauth_realm = oauth_realm |
913 | |
914 | @@ -165,9 +174,9 @@ class OAuthAuthorizer(HttpAuthorizer): |
915 | params = {} |
916 | if self.consumer is None: |
917 | return params |
918 | - params['oauth_consumer'] = self.consumer.key |
919 | + params["oauth_consumer"] = self.consumer.key |
920 | if self.consumer.application_name is not None: |
921 | - params['application'] = self.consumer.application_name |
922 | + params["application"] = self.consumer.application_name |
923 | return params |
924 | |
925 | def load(self, readable_file): |
926 | @@ -182,22 +191,21 @@ class OAuthAuthorizer(HttpAuthorizer): |
927 | """ |
928 | # Attempt to load the access token from the file. |
929 | parser = SafeConfigParser() |
930 | - reader = getattr(parser, 'read_file', parser.readfp) |
931 | + reader = getattr(parser, "read_file", parser.readfp) |
932 | reader(readable_file) |
933 | # Check the version number and extract the access token and |
934 | # secret. Then convert these to the appropriate instances. |
935 | if not parser.has_section(CREDENTIALS_FILE_VERSION): |
936 | - raise CredentialsFileError('No configuration for version %s' % |
937 | - CREDENTIALS_FILE_VERSION) |
938 | - consumer_key = parser.get( |
939 | - CREDENTIALS_FILE_VERSION, 'consumer_key') |
940 | + raise CredentialsFileError( |
941 | + "No configuration for version %s" % CREDENTIALS_FILE_VERSION |
942 | + ) |
943 | + consumer_key = parser.get(CREDENTIALS_FILE_VERSION, "consumer_key") |
944 | consumer_secret = parser.get( |
945 | - CREDENTIALS_FILE_VERSION, 'consumer_secret') |
946 | + CREDENTIALS_FILE_VERSION, "consumer_secret" |
947 | + ) |
948 | self.consumer = Consumer(consumer_key, consumer_secret) |
949 | - access_token = parser.get( |
950 | - CREDENTIALS_FILE_VERSION, 'access_token') |
951 | - access_secret = parser.get( |
952 | - CREDENTIALS_FILE_VERSION, 'access_secret') |
953 | + access_token = parser.get(CREDENTIALS_FILE_VERSION, "access_token") |
954 | + access_secret = parser.get(CREDENTIALS_FILE_VERSION, "access_secret") |
955 | self.access_token = AccessToken(access_token, access_secret) |
956 | |
957 | @classmethod |
958 | @@ -214,7 +222,7 @@ class OAuthAuthorizer(HttpAuthorizer): |
959 | :rtype: `Credentials` |
960 | """ |
961 | credentials = cls() |
962 | - credentials_file = open(path, 'r') |
963 | + credentials_file = open(path, "r") |
964 | credentials.load(credentials_file) |
965 | credentials_file.close() |
966 | return credentials |
967 | @@ -229,20 +237,22 @@ class OAuthAuthorizer(HttpAuthorizer): |
968 | access token |
969 | """ |
970 | if self.consumer is None: |
971 | - raise CredentialsFileError('No consumer') |
972 | + raise CredentialsFileError("No consumer") |
973 | if self.access_token is None: |
974 | - raise CredentialsFileError('No access token') |
975 | + raise CredentialsFileError("No access token") |
976 | |
977 | parser = SafeConfigParser() |
978 | parser.add_section(CREDENTIALS_FILE_VERSION) |
979 | - parser.set(CREDENTIALS_FILE_VERSION, |
980 | - 'consumer_key', self.consumer.key) |
981 | - parser.set(CREDENTIALS_FILE_VERSION, |
982 | - 'consumer_secret', self.consumer.secret) |
983 | - parser.set(CREDENTIALS_FILE_VERSION, |
984 | - 'access_token', self.access_token.key) |
985 | - parser.set(CREDENTIALS_FILE_VERSION, |
986 | - 'access_secret', self.access_token.secret) |
987 | + parser.set(CREDENTIALS_FILE_VERSION, "consumer_key", self.consumer.key) |
988 | + parser.set( |
989 | + CREDENTIALS_FILE_VERSION, "consumer_secret", self.consumer.secret |
990 | + ) |
991 | + parser.set( |
992 | + CREDENTIALS_FILE_VERSION, "access_token", self.access_token.key |
993 | + ) |
994 | + parser.set( |
995 | + CREDENTIALS_FILE_VERSION, "access_secret", self.access_token.secret |
996 | + ) |
997 | parser.write(writable_file) |
998 | |
999 | def save_to_path(self, path): |
1000 | @@ -256,8 +266,13 @@ class OAuthAuthorizer(HttpAuthorizer): |
1001 | :type path: string |
1002 | """ |
1003 | credentials_file = os.fdopen( |
1004 | - os.open(path, (os.O_CREAT | os.O_TRUNC | os.O_WRONLY), |
1005 | - (stat.S_IREAD | stat.S_IWRITE)), 'w') |
1006 | + os.open( |
1007 | + path, |
1008 | + (os.O_CREAT | os.O_TRUNC | os.O_WRONLY), |
1009 | + (stat.S_IREAD | stat.S_IWRITE), |
1010 | + ), |
1011 | + "w", |
1012 | + ) |
1013 | self.save(credentials_file) |
1014 | credentials_file.close() |
1015 | |
1016 | @@ -266,10 +281,11 @@ class OAuthAuthorizer(HttpAuthorizer): |
1017 | client = oauth1.Client( |
1018 | self.consumer.key, |
1019 | client_secret=self.consumer.secret, |
1020 | - resource_owner_key=TruthyString(self.access_token.key or ''), |
1021 | + resource_owner_key=TruthyString(self.access_token.key or ""), |
1022 | resource_owner_secret=self.access_token.secret, |
1023 | signature_method=oauth1.SIGNATURE_PLAINTEXT, |
1024 | - realm=self.oauth_realm) |
1025 | + realm=self.oauth_realm, |
1026 | + ) |
1027 | # The older oauth library (which may still be used on the server) |
1028 | # requires the oauth_token parameter to be present and will fail |
1029 | # authentication if it isn't. This hack forces it to be present |
1030 | @@ -280,6 +296,6 @@ class OAuthAuthorizer(HttpAuthorizer): |
1031 | # client.sign returns Unicode headers; convert these to native |
1032 | # strings. |
1033 | if six.PY2: |
1034 | - key = key.encode('UTF-8') |
1035 | - value = value.encode('UTF-8') |
1036 | + key = key.encode("UTF-8") |
1037 | + value = value.encode("UTF-8") |
1038 | headers[key] = value |
1039 | diff --git a/src/lazr/restfulclient/docs/Makefile b/src/lazr/restfulclient/docs/Makefile |
1040 | index 28fde7e..4784813 100644 |
1041 | --- a/src/lazr/restfulclient/docs/Makefile |
1042 | +++ b/src/lazr/restfulclient/docs/Makefile |
1043 | @@ -17,4 +17,4 @@ help: |
1044 | # Catch-all target: route all unknown targets to Sphinx using the new |
1045 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). |
1046 | %: Makefile |
1047 | - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) |
1048 | \ No newline at end of file |
1049 | + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) |
1050 | diff --git a/src/lazr/restfulclient/docs/authorizer.standalone.rst b/src/lazr/restfulclient/docs/authorizer.standalone.rst |
1051 | index 025e3df..f9bca0c 100644 |
1052 | --- a/src/lazr/restfulclient/docs/authorizer.standalone.rst |
1053 | +++ b/src/lazr/restfulclient/docs/authorizer.standalone.rst |
1054 | @@ -266,4 +266,3 @@ they request anonymous access. |
1055 | Teardown. |
1056 | |
1057 | >>> _ = wsgi_intercept.remove_wsgi_intercept("api.launchpad.dev", 80) |
1058 | - |
1059 | diff --git a/src/lazr/restfulclient/docs/conf.py b/src/lazr/restfulclient/docs/conf.py |
1060 | index 5bbb48f..4b8f219 100644 |
1061 | --- a/src/lazr/restfulclient/docs/conf.py |
1062 | +++ b/src/lazr/restfulclient/docs/conf.py |
1063 | @@ -34,21 +34,21 @@ from lazr.restfulclient import __version__ |
1064 | extensions = [] |
1065 | |
1066 | # Add any paths that contain templates here, relative to this directory. |
1067 | -templates_path = ['_templates'] |
1068 | +templates_path = ["_templates"] |
1069 | |
1070 | # The suffix(es) of source filenames. |
1071 | # You can specify multiple suffix as a list of string: |
1072 | # |
1073 | # source_suffix = ['.rst', '.md'] |
1074 | -source_suffix = '.rst' |
1075 | +source_suffix = ".rst" |
1076 | |
1077 | # The master toctree document. |
1078 | -master_doc = 'index' |
1079 | +master_doc = "index" |
1080 | |
1081 | # General information about the project. |
1082 | -project = u'lazr.restfulclient' |
1083 | -copyright = u'2008-2018, Canonical Ltd.' |
1084 | -author = u'LAZR Developers <lazr-developers@lists.launchpad.net>' |
1085 | +project = u"lazr.restfulclient" |
1086 | +copyright = u"2008-2018, Canonical Ltd." |
1087 | +author = u"LAZR Developers <lazr-developers@lists.launchpad.net>" |
1088 | |
1089 | # The version info for the project you're documenting, acts as replacement for |
1090 | # |version| and |release|, also used in various other places throughout the |
1091 | @@ -69,10 +69,10 @@ language = None |
1092 | # List of patterns, relative to source directory, that match files and |
1093 | # directories to ignore when looking for source files. |
1094 | # This patterns also effect to html_static_path and html_extra_path |
1095 | -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] |
1096 | +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] |
1097 | |
1098 | # The name of the Pygments (syntax highlighting) style to use. |
1099 | -pygments_style = 'sphinx' |
1100 | +pygments_style = "sphinx" |
1101 | |
1102 | # If true, `todo` and `todoList` produce output, else they produce nothing. |
1103 | todo_include_todos = False |
1104 | @@ -83,7 +83,7 @@ todo_include_todos = False |
1105 | # The theme to use for HTML and HTML Help pages. See the documentation for |
1106 | # a list of builtin themes. |
1107 | # |
1108 | -html_theme = 'alabaster' |
1109 | +html_theme = "alabaster" |
1110 | |
1111 | # Theme options are theme-specific and customize the look and feel of a theme |
1112 | # further. For a list of options available for each theme, see the |
1113 | @@ -94,7 +94,7 @@ html_theme = 'alabaster' |
1114 | # Add any paths that contain custom static files (such as style sheets) here, |
1115 | # relative to this directory. They are copied after the builtin static files, |
1116 | # so a file named "default.css" will overwrite the builtin "default.css". |
1117 | -#html_static_path = ['_static'] |
1118 | +# html_static_path = ['_static'] |
1119 | |
1120 | # Custom sidebar templates, must be a dictionary that maps document names |
1121 | # to template names. |
1122 | @@ -102,9 +102,9 @@ html_theme = 'alabaster' |
1123 | # This is required for the alabaster theme |
1124 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars |
1125 | html_sidebars = { |
1126 | - '**': [ |
1127 | - 'relations.html', # needs 'show_related': True theme option to display |
1128 | - 'searchbox.html', |
1129 | + "**": [ |
1130 | + "relations.html", # needs 'show_related': True theme option to display |
1131 | + "searchbox.html", |
1132 | ] |
1133 | } |
1134 | |
1135 | @@ -112,7 +112,7 @@ html_sidebars = { |
1136 | # -- Options for HTMLHelp output ------------------------------------------ |
1137 | |
1138 | # Output file base name for HTML help builder. |
1139 | -htmlhelp_basename = 'lazrrestfulclientdoc' |
1140 | +htmlhelp_basename = "lazrrestfulclientdoc" |
1141 | |
1142 | |
1143 | # -- Options for LaTeX output --------------------------------------------- |
1144 | @@ -121,15 +121,12 @@ latex_elements = { |
1145 | # The paper size ('letterpaper' or 'a4paper'). |
1146 | # |
1147 | # 'papersize': 'letterpaper', |
1148 | - |
1149 | # The font size ('10pt', '11pt' or '12pt'). |
1150 | # |
1151 | # 'pointsize': '10pt', |
1152 | - |
1153 | # Additional stuff for the LaTeX preamble. |
1154 | # |
1155 | # 'preamble': '', |
1156 | - |
1157 | # Latex figure (float) alignment |
1158 | # |
1159 | # 'figure_align': 'htbp', |
1160 | @@ -139,8 +136,13 @@ latex_elements = { |
1161 | # (source start file, target name, title, |
1162 | # author, documentclass [howto, manual, or own class]). |
1163 | latex_documents = [ |
1164 | - (master_doc, 'lazrrestfulclient.tex', u'lazr.restfulclient Documentation', |
1165 | - u'LAZR Developers', 'manual'), |
1166 | + ( |
1167 | + master_doc, |
1168 | + "lazrrestfulclient.tex", |
1169 | + u"lazr.restfulclient Documentation", |
1170 | + u"LAZR Developers", |
1171 | + "manual", |
1172 | + ), |
1173 | ] |
1174 | |
1175 | |
1176 | @@ -149,8 +151,13 @@ latex_documents = [ |
1177 | # One entry per manual page. List of tuples |
1178 | # (source start file, name, description, authors, manual section). |
1179 | man_pages = [ |
1180 | - (master_doc, 'lazrrestfulclient', u'lazr.restfulclient Documentation', |
1181 | - [author], 1) |
1182 | + ( |
1183 | + master_doc, |
1184 | + "lazrrestfulclient", |
1185 | + u"lazr.restfulclient Documentation", |
1186 | + [author], |
1187 | + 1, |
1188 | + ) |
1189 | ] |
1190 | |
1191 | |
1192 | @@ -160,10 +167,13 @@ man_pages = [ |
1193 | # (source start file, target name, title, author, |
1194 | # dir menu entry, description, category) |
1195 | texinfo_documents = [ |
1196 | - (master_doc, 'lazrrestfulclient', u'lazr.restfulclient Documentation', |
1197 | - author, 'lazrrestfulclient', 'One line description of project.', |
1198 | - 'Miscellaneous'), |
1199 | + ( |
1200 | + master_doc, |
1201 | + "lazrrestfulclient", |
1202 | + u"lazr.restfulclient Documentation", |
1203 | + author, |
1204 | + "lazrrestfulclient", |
1205 | + "One line description of project.", |
1206 | + "Miscellaneous", |
1207 | + ), |
1208 | ] |
1209 | - |
1210 | - |
1211 | - |
1212 | diff --git a/src/lazr/restfulclient/docs/toplevel.rst b/src/lazr/restfulclient/docs/toplevel.rst |
1213 | index 469b326..eb796f6 100644 |
1214 | --- a/src/lazr/restfulclient/docs/toplevel.rst |
1215 | +++ b/src/lazr/restfulclient/docs/toplevel.rst |
1216 | @@ -207,4 +207,3 @@ different HTTPError subclasses, see tests/test_error.py. |
1217 | |
1218 | >>> print isinstance(e, HTTPError) |
1219 | True |
1220 | - |
1221 | diff --git a/src/lazr/restfulclient/errors.py b/src/lazr/restfulclient/errors.py |
1222 | index 6b702b7..3859d17 100644 |
1223 | --- a/src/lazr/restfulclient/errors.py |
1224 | +++ b/src/lazr/restfulclient/errors.py |
1225 | @@ -20,21 +20,21 @@ |
1226 | |
1227 | __metaclass__ = type |
1228 | __all__ = [ |
1229 | - 'BadRequest', |
1230 | - 'Conflict', |
1231 | - 'ClientError', |
1232 | - 'CredentialsError', |
1233 | - 'CredentialsFileError', |
1234 | - 'HTTPError', |
1235 | - 'MethodNotAllowed', |
1236 | - 'NotFound', |
1237 | - 'PreconditionFailed', |
1238 | - 'RestfulError', |
1239 | - 'ResponseError', |
1240 | - 'ServerError', |
1241 | - 'Unauthorized', |
1242 | - 'UnexpectedResponseError', |
1243 | - ] |
1244 | + "BadRequest", |
1245 | + "Conflict", |
1246 | + "ClientError", |
1247 | + "CredentialsError", |
1248 | + "CredentialsFileError", |
1249 | + "HTTPError", |
1250 | + "MethodNotAllowed", |
1251 | + "NotFound", |
1252 | + "PreconditionFailed", |
1253 | + "RestfulError", |
1254 | + "ResponseError", |
1255 | + "ServerError", |
1256 | + "Unauthorized", |
1257 | + "UnexpectedResponseError", |
1258 | +] |
1259 | |
1260 | |
1261 | class RestfulError(Exception): |
1262 | @@ -62,7 +62,7 @@ class UnexpectedResponseError(ResponseError): |
1263 | """An unexpected response was received.""" |
1264 | |
1265 | def __str__(self): |
1266 | - return '%s: %s' % (self.response.status, self.response.reason) |
1267 | + return "%s: %s" % (self.response.status, self.response.reason) |
1268 | |
1269 | |
1270 | class HTTPError(ResponseError): |
1271 | @@ -70,12 +70,14 @@ class HTTPError(ResponseError): |
1272 | |
1273 | def __str__(self): |
1274 | """Show the error code, response headers, and response body.""" |
1275 | - headers = "\n".join(["%s: %s" % pair |
1276 | - for pair in sorted(self.response.items())]) |
1277 | - return ("HTTP Error %s: %s\n" |
1278 | - "Response headers:\n---\n%s\n---\n" |
1279 | - "Response body:\n---\n%s\n---\n") % ( |
1280 | - self.response.status, self.response.reason, headers, self.content) |
1281 | + headers = "\n".join( |
1282 | + ["%s: %s" % pair for pair in sorted(self.response.items())] |
1283 | + ) |
1284 | + return ( |
1285 | + "HTTP Error %s: %s\n" |
1286 | + "Response headers:\n---\n%s\n---\n" |
1287 | + "Response body:\n---\n%s\n---\n" |
1288 | + ) % (self.response.status, self.response.reason, headers, self.content) |
1289 | |
1290 | |
1291 | class ClientError(HTTPError): |
1292 | @@ -114,6 +116,7 @@ class PreconditionFailed(ClientError): |
1293 | object is now out of date. |
1294 | """ |
1295 | |
1296 | + |
1297 | class ServerError(HTTPError): |
1298 | """An exception representing a server-side error.""" |
1299 | |
1300 | @@ -126,12 +129,12 @@ def error_for(response, content): |
1301 | if nothing else is appropriate. |
1302 | """ |
1303 | http_errors_by_status_code = { |
1304 | - 400 : BadRequest, |
1305 | - 401 : Unauthorized, |
1306 | - 404 : NotFound, |
1307 | - 405 : MethodNotAllowed, |
1308 | - 409 : Conflict, |
1309 | - 412 : PreconditionFailed, |
1310 | + 400: BadRequest, |
1311 | + 401: Unauthorized, |
1312 | + 404: NotFound, |
1313 | + 405: MethodNotAllowed, |
1314 | + 409: Conflict, |
1315 | + 412: PreconditionFailed, |
1316 | } |
1317 | |
1318 | if response.status // 100 <= 3: |
1319 | diff --git a/src/lazr/restfulclient/resource.py b/src/lazr/restfulclient/resource.py |
1320 | index 70e3c05..a330676 100644 |
1321 | --- a/src/lazr/restfulclient/resource.py |
1322 | +++ b/src/lazr/restfulclient/resource.py |
1323 | @@ -20,13 +20,13 @@ |
1324 | |
1325 | __metaclass__ = type |
1326 | __all__ = [ |
1327 | - 'Collection', |
1328 | - 'CollectionWithKeyBasedLookup', |
1329 | - 'Entry', |
1330 | - 'NamedOperation', |
1331 | - 'Resource', |
1332 | - 'ServiceRoot', |
1333 | - ] |
1334 | + "Collection", |
1335 | + "CollectionWithKeyBasedLookup", |
1336 | + "Entry", |
1337 | + "NamedOperation", |
1338 | + "Resource", |
1339 | + "ServiceRoot", |
1340 | +] |
1341 | |
1342 | |
1343 | from email.message import Message |
1344 | @@ -35,26 +35,27 @@ from json import dumps, loads |
1345 | |
1346 | try: |
1347 | # Python 3. |
1348 | - from urllib.parse import urljoin, urlparse, parse_qs, unquote, urlencode |
1349 | + from urllib.parse import parse_qs, unquote, urlencode, urljoin, urlparse |
1350 | except ImportError: |
1351 | from urlparse import urljoin, urlparse, parse_qs |
1352 | from urllib import unquote, urlencode |
1353 | |
1354 | import sys |
1355 | + |
1356 | if sys.version_info[0] >= 3: |
1357 | text_type = str |
1358 | binary_type = bytes |
1359 | else: |
1360 | - text_type = unicode |
1361 | + text_type = unicode # noqa: F821 |
1362 | binary_type = str |
1363 | |
1364 | -from lazr.uri import URI |
1365 | from wadllib.application import Resource as WadlResource |
1366 | |
1367 | from lazr.restfulclient import __version__ |
1368 | from lazr.restfulclient._browser import Browser, RestfulHttp |
1369 | from lazr.restfulclient._json import DatetimeJSONEncoder |
1370 | from lazr.restfulclient.errors import HTTPError |
1371 | +from lazr.uri import URI |
1372 | |
1373 | missing = object() |
1374 | |
1375 | @@ -70,6 +71,7 @@ class HeaderDictionary: |
1376 | the underlying dictionary. That way wadllib can pass in the |
1377 | official header name and httplib2 will get the lowercased name. |
1378 | """ |
1379 | + |
1380 | def __init__(self, wrapped_dictionary): |
1381 | self.wrapped_dictionary = wrapped_dictionary |
1382 | |
1383 | @@ -88,7 +90,7 @@ class HeaderDictionary: |
1384 | class RestfulBase: |
1385 | """Base class for classes that know about lazr.restful services.""" |
1386 | |
1387 | - JSON_MEDIA_TYPE = 'application/json' |
1388 | + JSON_MEDIA_TYPE = "application/json" |
1389 | |
1390 | def _transform_resources_to_links(self, dictionary): |
1391 | new_dictionary = {} |
1392 | @@ -119,8 +121,8 @@ class Resource(RestfulBase): |
1393 | root = self |
1394 | # These values need to be put directly into __dict__ to avoid |
1395 | # calling __setattr__, which would cause an infinite recursion. |
1396 | - self.__dict__['_root'] = root |
1397 | - self.__dict__['_wadl_resource'] = wadl_resource |
1398 | + self.__dict__["_root"] = root |
1399 | + self.__dict__["_wadl_resource"] = wadl_resource |
1400 | |
1401 | FIND_COLLECTIONS = object() |
1402 | FIND_ENTRIES = object() |
1403 | @@ -149,19 +151,22 @@ class Resource(RestfulBase): |
1404 | names = [] |
1405 | for method in self._wadl_resource.method_iter: |
1406 | name = method.name.lower() |
1407 | - if name == 'get': |
1408 | - params = method.request.params(['query', 'plain']) |
1409 | - elif name == 'post': |
1410 | - for media_type in ['application/x-www-form-urlencoded', |
1411 | - 'multipart/form-data']: |
1412 | + if name == "get": |
1413 | + params = method.request.params(["query", "plain"]) |
1414 | + elif name == "post": |
1415 | + for media_type in [ |
1416 | + "application/x-www-form-urlencoded", |
1417 | + "multipart/form-data", |
1418 | + ]: |
1419 | definition = method.request.get_representation_definition( |
1420 | - media_type) |
1421 | + media_type |
1422 | + ) |
1423 | if definition is not None: |
1424 | definition = definition.resolve_definition() |
1425 | break |
1426 | params = definition.params(self._wadl_resource) |
1427 | for param in params: |
1428 | - if param.name == 'ws.op': |
1429 | + if param.name == "ws.op": |
1430 | names.append(param.fixed_value) |
1431 | break |
1432 | return names |
1433 | @@ -170,19 +175,18 @@ class Resource(RestfulBase): |
1434 | def __members__(self): |
1435 | """A hook into dir() that returns web service-derived members.""" |
1436 | return self._get_parameter_names( |
1437 | - self.FIND_COLLECTIONS, self.FIND_ENTRIES, self.FIND_ATTRIBUTES) |
1438 | + self.FIND_COLLECTIONS, self.FIND_ENTRIES, self.FIND_ATTRIBUTES |
1439 | + ) |
1440 | |
1441 | __methods__ = lp_operations |
1442 | |
1443 | def _get_parameter_names(self, *kinds): |
1444 | """Retrieve some subset of the resource's parameters.""" |
1445 | names = [] |
1446 | - for parameter in self._wadl_resource.parameters( |
1447 | - self.JSON_MEDIA_TYPE): |
1448 | + for parameter in self._wadl_resource.parameters(self.JSON_MEDIA_TYPE): |
1449 | name = parameter.name |
1450 | link = parameter.link |
1451 | - if (name != 'self_link' and link is not None |
1452 | - and link.can_follow): |
1453 | + if name != "self_link" and link is not None and link.can_follow: |
1454 | # This is a link to a resource with a WADL |
1455 | # description. Since this is a lazr.restful web |
1456 | # service, we know it's either an entry or a |
1457 | @@ -192,7 +196,7 @@ class Resource(RestfulBase): |
1458 | # self_link is a special case. 'obj.self' will always |
1459 | # work, but it's not useful. 'obj.self_link' is |
1460 | # useful, so we advertise the scalar value instead. |
1461 | - if name.endswith('_collection_link'): |
1462 | + if name.endswith("_collection_link"): |
1463 | # It's a link to a collection. |
1464 | if self.FIND_COLLECTIONS in kinds: |
1465 | names.append(name[:-16]) |
1466 | @@ -222,9 +226,8 @@ class Resource(RestfulBase): |
1467 | representation, if the parameter is a link. |
1468 | """ |
1469 | self._ensure_representation() |
1470 | - for suffix in ['_link', '_collection_link']: |
1471 | - param = self._wadl_resource.get_parameter( |
1472 | - param_name + suffix) |
1473 | + for suffix in ["_link", "_collection_link"]: |
1474 | + param = self._wadl_resource.get_parameter(param_name + suffix) |
1475 | if param is not None: |
1476 | try: |
1477 | param.get_value() |
1478 | @@ -239,7 +242,8 @@ class Resource(RestfulBase): |
1479 | return None |
1480 | linked_resource = param.linked_resource |
1481 | return self._create_bound_resource( |
1482 | - self._root, linked_resource, param_name=param.name) |
1483 | + self._root, linked_resource, param_name=param.name |
1484 | + ) |
1485 | param = self._wadl_resource.get_parameter(param_name) |
1486 | if param is None: |
1487 | raise KeyError("No such parameter: %s" % param_name) |
1488 | @@ -251,21 +255,27 @@ class Resource(RestfulBase): |
1489 | :return: A NamedOperation instance that can be called with |
1490 | appropriate arguments to invoke the operation. |
1491 | """ |
1492 | - params = {'ws.op': operation_name} |
1493 | - method = self._wadl_resource.get_method('get', query_params=params) |
1494 | + params = {"ws.op": operation_name} |
1495 | + method = self._wadl_resource.get_method("get", query_params=params) |
1496 | if method is None: |
1497 | method = self._wadl_resource.get_method( |
1498 | - 'post', representation_params=params) |
1499 | + "post", representation_params=params |
1500 | + ) |
1501 | if method is None: |
1502 | raise KeyError("No operation with name: %s" % operation_name) |
1503 | return NamedOperation(self._root, self, method) |
1504 | |
1505 | @classmethod |
1506 | def _create_bound_resource( |
1507 | - cls, root, resource, representation=None, |
1508 | - representation_media_type='application/json', |
1509 | - representation_needs_processing=True, representation_definition=None, |
1510 | - param_name=None): |
1511 | + cls, |
1512 | + root, |
1513 | + resource, |
1514 | + representation=None, |
1515 | + representation_media_type="application/json", |
1516 | + representation_needs_processing=True, |
1517 | + representation_definition=None, |
1518 | + param_name=None, |
1519 | + ): |
1520 | """Create a lazr.restful Resource subclass from a wadllib Resource. |
1521 | |
1522 | :param resource: The wadllib Resource to wrap. |
1523 | @@ -295,18 +305,20 @@ class Resource(RestfulBase): |
1524 | resource_type = urlparse(type_url)[-1] |
1525 | default = Entry |
1526 | |
1527 | - if (type_url.endswith('-page') |
1528 | - or (param_name is not None |
1529 | - and param_name.endswith('_collection_link'))): |
1530 | + if type_url.endswith("-page") or ( |
1531 | + param_name is not None and param_name.endswith("_collection_link") |
1532 | + ): |
1533 | default = Collection |
1534 | r_class = root.RESOURCE_TYPE_CLASSES.get(resource_type, default) |
1535 | if representation is not None: |
1536 | # We've been given a representation. Bind the resource |
1537 | # immediately. |
1538 | resource = resource.bind( |
1539 | - representation, representation_media_type, |
1540 | + representation, |
1541 | + representation_media_type, |
1542 | representation_needs_processing, |
1543 | - representation_definition=representation_definition) |
1544 | + representation_definition=representation_definition, |
1545 | + ) |
1546 | else: |
1547 | # We'll fetch a representation and bind the resource when |
1548 | # necessary. |
1549 | @@ -319,16 +331,18 @@ class Resource(RestfulBase): |
1550 | self._wadl_resource._url = new_url |
1551 | headers = {} |
1552 | if etag is not None: |
1553 | - headers['If-None-Match'] = etag |
1554 | + headers["If-None-Match"] = etag |
1555 | representation = self._root._browser.get( |
1556 | - self._wadl_resource, headers=headers) |
1557 | + self._wadl_resource, headers=headers |
1558 | + ) |
1559 | if representation == self._root._browser.NOT_MODIFIED: |
1560 | # The entry wasn't modified. No need to do anything. |
1561 | return |
1562 | # __setattr__ assumes we're setting an attribute of the resource, |
1563 | # so we manipulate __dict__ directly. |
1564 | - self.__dict__['_wadl_resource'] = self._wadl_resource.bind( |
1565 | - representation, self.JSON_MEDIA_TYPE) |
1566 | + self.__dict__["_wadl_resource"] = self._wadl_resource.bind( |
1567 | + representation, self.JSON_MEDIA_TYPE |
1568 | + ) |
1569 | |
1570 | def __getattr__(self, attr): |
1571 | """Try to retrive a named operation or parameter of the given name.""" |
1572 | @@ -339,13 +353,15 @@ class Resource(RestfulBase): |
1573 | try: |
1574 | return self.lp_get_parameter(attr) |
1575 | except KeyError: |
1576 | - raise AttributeError("%s object has no attribute '%s'" |
1577 | - % (self, attr)) |
1578 | + raise AttributeError( |
1579 | + "%s object has no attribute '%s'" % (self, attr) |
1580 | + ) |
1581 | |
1582 | def lp_values_for(self, param_name): |
1583 | """Find the set of possible values for a parameter.""" |
1584 | parameter = self._wadl_resource.get_parameter( |
1585 | - param_name, self.JSON_MEDIA_TYPE) |
1586 | + param_name, self.JSON_MEDIA_TYPE |
1587 | + ) |
1588 | options = parameter.options |
1589 | if len(options) > 0: |
1590 | return [option.value for option in options] |
1591 | @@ -353,7 +369,7 @@ class Resource(RestfulBase): |
1592 | |
1593 | def _get_external_param_name(self, param_name): |
1594 | """What's this parameter's name in the underlying representation?""" |
1595 | - for suffix in ['_link', '_collection_link', '']: |
1596 | + for suffix in ["_link", "_collection_link", ""]: |
1597 | name = param_name + suffix |
1598 | if self._wadl_resource.get_parameter(name): |
1599 | return name |
1600 | @@ -365,7 +381,7 @@ class Resource(RestfulBase): |
1601 | # Get a representation of the linked resource. |
1602 | representation = self._root._browser.get(self._wadl_resource) |
1603 | if isinstance(representation, binary_type): |
1604 | - representation = representation.decode('utf-8') |
1605 | + representation = representation.decode("utf-8") |
1606 | representation = loads(representation) |
1607 | |
1608 | # In rare cases, the resource type served by the |
1609 | @@ -377,15 +393,20 @@ class Resource(RestfulBase): |
1610 | # defined by Entry, since it's not relevant to other |
1611 | # resource types. |
1612 | if isinstance(representation, dict): |
1613 | - type_link = representation['resource_type_link'] |
1614 | - if (type_link is not None |
1615 | - and type_link != self._wadl_resource.type_url): |
1616 | + type_link = representation["resource_type_link"] |
1617 | + if ( |
1618 | + type_link is not None |
1619 | + and type_link != self._wadl_resource.type_url |
1620 | + ): |
1621 | resource_type = self._root._wadl.get_resource_type( |
1622 | - type_link) |
1623 | + type_link |
1624 | + ) |
1625 | self._wadl_resource.tag = resource_type.tag |
1626 | - self.__dict__['_wadl_resource'] = self._wadl_resource.bind( |
1627 | - representation, self.JSON_MEDIA_TYPE, |
1628 | - representation_needs_processing=False) |
1629 | + self.__dict__["_wadl_resource"] = self._wadl_resource.bind( |
1630 | + representation, |
1631 | + self.JSON_MEDIA_TYPE, |
1632 | + representation_needs_processing=False, |
1633 | + ) |
1634 | |
1635 | def __ne__(self, other): |
1636 | """Inequality operator.""" |
1637 | @@ -405,9 +426,9 @@ class ScalarValue(Resource): |
1638 | class HostedFile(Resource): |
1639 | """A resource representing a file managed by a lazr.restful service.""" |
1640 | |
1641 | - def open(self, mode='r', content_type=None, filename=None): |
1642 | + def open(self, mode="r", content_type=None, filename=None): |
1643 | """Open the file on the server for read or write access.""" |
1644 | - if mode in ('r', 'w'): |
1645 | + if mode in ("r", "w"): |
1646 | return HostedFileBuffer(self, mode, content_type, filename) |
1647 | else: |
1648 | raise ValueError("Invalid mode. Supported modes are: r, w") |
1649 | @@ -429,8 +450,10 @@ class HostedFile(Resource): |
1650 | retrieve or modify the hosted file contents is to open a |
1651 | filehandle, which goes direct to the server. |
1652 | """ |
1653 | - return (other is not None and |
1654 | - self._wadl_resource.url == other._wadl_resource.url) |
1655 | + return ( |
1656 | + other is not None |
1657 | + and self._wadl_resource.url == other._wadl_resource.url |
1658 | + ) |
1659 | |
1660 | |
1661 | class ServiceRoot(Resource): |
1662 | @@ -441,12 +464,22 @@ class ServiceRoot(Resource): |
1663 | |
1664 | # Custom subclasses of Resource to use when |
1665 | # instantiating resources of a certain WADL type. |
1666 | - RESOURCE_TYPE_CLASSES = {'HostedFile': HostedFile, |
1667 | - 'ScalarValue': ScalarValue} |
1668 | - |
1669 | - def __init__(self, authorizer, service_root, cache=None, |
1670 | - timeout=None, proxy_info=None, version=None, |
1671 | - base_client_name='', max_retries=Browser.MAX_RETRIES): |
1672 | + RESOURCE_TYPE_CLASSES = { |
1673 | + "HostedFile": HostedFile, |
1674 | + "ScalarValue": ScalarValue, |
1675 | + } |
1676 | + |
1677 | + def __init__( |
1678 | + self, |
1679 | + authorizer, |
1680 | + service_root, |
1681 | + cache=None, |
1682 | + timeout=None, |
1683 | + proxy_info=None, |
1684 | + version=None, |
1685 | + base_client_name="", |
1686 | + max_retries=Browser.MAX_RETRIES, |
1687 | + ): |
1688 | """Root access to a lazr.restful API. |
1689 | |
1690 | :param credentials: The credentials used to access the service. |
1691 | @@ -454,11 +487,11 @@ class ServiceRoot(Resource): |
1692 | :type service_root: string |
1693 | """ |
1694 | if version is not None: |
1695 | - if service_root[-1] != '/': |
1696 | - service_root += '/' |
1697 | + if service_root[-1] != "/": |
1698 | + service_root += "/" |
1699 | service_root += str(version) |
1700 | - if service_root[-1] != '/': |
1701 | - service_root += '/' |
1702 | + if service_root[-1] != "/": |
1703 | + service_root += "/" |
1704 | self._root_uri = URI(service_root) |
1705 | |
1706 | # Set up data necessary to calculate the User-Agent header. |
1707 | @@ -467,14 +500,21 @@ class ServiceRoot(Resource): |
1708 | # Get the WADL definition. |
1709 | self.credentials = authorizer |
1710 | self._browser = Browser( |
1711 | - self, authorizer, cache, timeout, proxy_info, self._user_agent, |
1712 | - max_retries) |
1713 | + self, |
1714 | + authorizer, |
1715 | + cache, |
1716 | + timeout, |
1717 | + proxy_info, |
1718 | + self._user_agent, |
1719 | + max_retries, |
1720 | + ) |
1721 | self._wadl = self._browser.get_wadl_application(self._root_uri) |
1722 | |
1723 | # Get the root resource. |
1724 | - root_resource = self._wadl.get_resource_by_path('') |
1725 | + root_resource = self._wadl.get_resource_by_path("") |
1726 | bound_root = root_resource.bind( |
1727 | - self._browser.get(root_resource), 'application/json') |
1728 | + self._browser.get(root_resource), "application/json" |
1729 | + ) |
1730 | super(ServiceRoot, self).__init__(None, bound_root) |
1731 | |
1732 | @property |
1733 | @@ -490,17 +530,17 @@ class ServiceRoot(Resource): |
1734 | user agent (such as the application name). |
1735 | """ |
1736 | base_portion = "lazr.restfulclient %s" % __version__ |
1737 | - if self._base_client_name != '': |
1738 | - base_portion = self._base_client_name + ' (' + base_portion + ')' |
1739 | + if self._base_client_name != "": |
1740 | + base_portion = self._base_client_name + " (" + base_portion + ")" |
1741 | |
1742 | message = Message() |
1743 | - message['User-Agent'] = base_portion |
1744 | + message["User-Agent"] = base_portion |
1745 | if self.credentials is not None: |
1746 | user_agent_params = self.credentials.user_agent_params |
1747 | for key in sorted(user_agent_params): |
1748 | value = user_agent_params[key] |
1749 | - message.set_param(key, value, 'User-Agent') |
1750 | - return message['User-Agent'] |
1751 | + message.set_param(key, value, "User-Agent") |
1752 | + return message["User-Agent"] |
1753 | |
1754 | def httpFactory(self, authorizer, cache, timeout, proxy_info): |
1755 | return RestfulHttp(authorizer, cache, timeout, proxy_info) |
1756 | @@ -508,28 +548,33 @@ class ServiceRoot(Resource): |
1757 | def load(self, url): |
1758 | """Load a resource given its URL.""" |
1759 | parsed = urlparse(url) |
1760 | - if parsed.scheme == '': |
1761 | + if parsed.scheme == "": |
1762 | # This is a relative URL. Make it absolute by joining |
1763 | # it with the service root resource. |
1764 | - if url[:1] == '/': |
1765 | + if url[:1] == "/": |
1766 | url = url[1:] |
1767 | url = str(self._root_uri.append(url)) |
1768 | document = self._browser.get(url) |
1769 | if isinstance(document, binary_type): |
1770 | - document = document.decode('utf-8') |
1771 | + document = document.decode("utf-8") |
1772 | try: |
1773 | representation = loads(document) |
1774 | except ValueError: |
1775 | raise ValueError("%s doesn't serve a JSON document." % url) |
1776 | type_link = representation.get("resource_type_link") |
1777 | if type_link is None: |
1778 | - raise ValueError("Couldn't determine the resource type of %s." |
1779 | - % url) |
1780 | + raise ValueError( |
1781 | + "Couldn't determine the resource type of %s." % url |
1782 | + ) |
1783 | resource_type = self._root._wadl.get_resource_type(type_link) |
1784 | wadl_resource = WadlResource(self._root._wadl, url, resource_type.tag) |
1785 | return self._create_bound_resource( |
1786 | - self._root, wadl_resource, representation, 'application/json', |
1787 | - representation_needs_processing=False) |
1788 | + self._root, |
1789 | + wadl_resource, |
1790 | + representation, |
1791 | + "application/json", |
1792 | + representation_needs_processing=False, |
1793 | + ) |
1794 | |
1795 | |
1796 | class NamedOperation(RestfulBase): |
1797 | @@ -544,27 +589,33 @@ class NamedOperation(RestfulBase): |
1798 | def __call__(self, *args, **kwargs): |
1799 | """Invoke the method and process the result.""" |
1800 | if len(args) > 0: |
1801 | - raise TypeError('Method must be called with keyword args.') |
1802 | + raise TypeError("Method must be called with keyword args.") |
1803 | http_method = self.wadl_method.name |
1804 | args = self._transform_resources_to_links(kwargs) |
1805 | request = self.wadl_method.request |
1806 | |
1807 | - if http_method in ('get', 'head', 'delete'): |
1808 | + if http_method in ("get", "head", "delete"): |
1809 | params = request.query_params |
1810 | else: |
1811 | definition = request.get_representation_definition( |
1812 | - 'multipart/form-data') |
1813 | + "multipart/form-data" |
1814 | + ) |
1815 | if definition is None: |
1816 | definition = request.get_representation_definition( |
1817 | - 'application/x-www-form-urlencoded') |
1818 | + "application/x-www-form-urlencoded" |
1819 | + ) |
1820 | assert definition is not None, ( |
1821 | "A POST named operation must define a multipart or " |
1822 | "form-urlencoded request representation." |
1823 | - ) |
1824 | + ) |
1825 | params = definition.params(self.resource._wadl_resource) |
1826 | - send_as_is_params = set([param.name for param in params |
1827 | - if param.type == 'binary' |
1828 | - or len(param.options) > 0]) |
1829 | + send_as_is_params = set( |
1830 | + [ |
1831 | + param.name |
1832 | + for param in params |
1833 | + if param.type == "binary" or len(param.options) > 0 |
1834 | + ] |
1835 | + ) |
1836 | for key, value in args.items(): |
1837 | # Certain parameter values should not be JSON-encoded: |
1838 | # binary parameters (because they can't be JSON-encoded) |
1839 | @@ -573,16 +624,17 @@ class NamedOperation(RestfulBase): |
1840 | # is a little hacky, but it's the best solution for now. |
1841 | if key not in send_as_is_params: |
1842 | args[key] = dumps(value, cls=DatetimeJSONEncoder) |
1843 | - if http_method in ('get', 'head', 'delete'): |
1844 | + if http_method in ("get", "head", "delete"): |
1845 | url = self.wadl_method.build_request_url(**args) |
1846 | - in_representation = '' |
1847 | + in_representation = "" |
1848 | extra_headers = {} |
1849 | else: |
1850 | url = self.wadl_method.build_request_url() |
1851 | - (media_type, |
1852 | - in_representation) = self.wadl_method.build_representation( |
1853 | - **args) |
1854 | - extra_headers = {'Content-type': media_type} |
1855 | + ( |
1856 | + media_type, |
1857 | + in_representation, |
1858 | + ) = self.wadl_method.build_representation(**args) |
1859 | + extra_headers = {"Content-type": media_type} |
1860 | # Pass uppercase method names to httplib2, as that is what it works |
1861 | # with. If you pass a lowercase method name to httplib then it doesn't |
1862 | # consider it to be a GET, PUT, etc., and so will do things like not |
1863 | @@ -590,18 +642,21 @@ class NamedOperation(RestfulBase): |
1864 | # is compared in this method, but httplib2 expects the opposite, hence |
1865 | # the .upper() call. |
1866 | response, content = self.root._browser._request( |
1867 | - url, in_representation, http_method.upper(), |
1868 | - extra_headers=extra_headers) |
1869 | + url, |
1870 | + in_representation, |
1871 | + http_method.upper(), |
1872 | + extra_headers=extra_headers, |
1873 | + ) |
1874 | |
1875 | if response.status == 201: |
1876 | return self._handle_201_response(url, response, content) |
1877 | else: |
1878 | - if http_method == 'post': |
1879 | + if http_method == "post": |
1880 | # The method call probably modified this resource in |
1881 | # an unknown way. If it moved to a new location, reload it or |
1882 | # else just refresh its representation. |
1883 | if response.status == 301: |
1884 | - url = response['location'] |
1885 | + url = response["location"] |
1886 | response, content = self.root._browser._request(url) |
1887 | else: |
1888 | self.resource.lp_refresh() |
1889 | @@ -610,32 +665,33 @@ class NamedOperation(RestfulBase): |
1890 | def _handle_201_response(self, url, response, content): |
1891 | """Handle the creation of a new resource by fetching it.""" |
1892 | wadl_response = self.wadl_method.response.bind( |
1893 | - HeaderDictionary(response)) |
1894 | - wadl_parameter = wadl_response.get_parameter('Location') |
1895 | + HeaderDictionary(response) |
1896 | + ) |
1897 | + wadl_parameter = wadl_response.get_parameter("Location") |
1898 | wadl_resource = wadl_parameter.linked_resource |
1899 | # Fetch a representation of the new resource. |
1900 | - response, content = self.root._browser._request( |
1901 | - wadl_resource.url) |
1902 | + response, content = self.root._browser._request(wadl_resource.url) |
1903 | # Return an instance of the appropriate lazr.restful |
1904 | # Resource subclass. |
1905 | return Resource._create_bound_resource( |
1906 | - self.root, wadl_resource, content, response['content-type']) |
1907 | + self.root, wadl_resource, content, response["content-type"] |
1908 | + ) |
1909 | |
1910 | def _handle_200_response(self, url, response, content): |
1911 | """Process the return value of an operation.""" |
1912 | - content_type = response['content-type'] |
1913 | + content_type = response["content-type"] |
1914 | # Process the returned content, assuming we know how. |
1915 | response_definition = self.wadl_method.response |
1916 | representation_definition = ( |
1917 | - response_definition.get_representation_definition( |
1918 | - content_type)) |
1919 | + response_definition.get_representation_definition(content_type) |
1920 | + ) |
1921 | |
1922 | if representation_definition is None: |
1923 | # The operation returned a document with nothing |
1924 | # special about it. |
1925 | if content_type == self.JSON_MEDIA_TYPE: |
1926 | if isinstance(content, binary_type): |
1927 | - content = content.decode('utf-8') |
1928 | + content = content.decode("utf-8") |
1929 | return loads(content) |
1930 | # We don't know how to process the content. |
1931 | return content |
1932 | @@ -643,8 +699,8 @@ class NamedOperation(RestfulBase): |
1933 | # The operation returned a representation of some |
1934 | # resource. Instantiate a Resource object for it. |
1935 | if isinstance(content, binary_type): |
1936 | - content = content.decode('utf-8') |
1937 | - |
1938 | + content = content.decode("utf-8") |
1939 | + |
1940 | document = loads(content) |
1941 | if document is None: |
1942 | # The operation returned a null value. |
1943 | @@ -656,9 +712,11 @@ class NamedOperation(RestfulBase): |
1944 | # object will support all of the right named operations. |
1945 | url = document["self_link"] |
1946 | resource_type = self.root._wadl.get_resource_type( |
1947 | - document["resource_type_link"]) |
1948 | - wadl_resource = WadlResource(self.root._wadl, url, |
1949 | - resource_type.tag) |
1950 | + document["resource_type_link"] |
1951 | + ) |
1952 | + wadl_resource = WadlResource( |
1953 | + self.root._wadl, url, resource_type.tag |
1954 | + ) |
1955 | else: |
1956 | # The operation returned a collection. It's probably an ad |
1957 | # hoc collection that doesn't correspond to any resource |
1958 | @@ -666,14 +724,20 @@ class NamedOperation(RestfulBase): |
1959 | # representation type defined in the return value, instead |
1960 | # of a resource type tag. |
1961 | representation_definition = ( |
1962 | - representation_definition.resolve_definition()) |
1963 | + representation_definition.resolve_definition() |
1964 | + ) |
1965 | wadl_resource = WadlResource( |
1966 | - self.root._wadl, url, representation_definition.tag) |
1967 | + self.root._wadl, url, representation_definition.tag |
1968 | + ) |
1969 | |
1970 | return Resource._create_bound_resource( |
1971 | - self.root, wadl_resource, document, content_type, |
1972 | + self.root, |
1973 | + wadl_resource, |
1974 | + document, |
1975 | + content_type, |
1976 | representation_needs_processing=False, |
1977 | - representation_definition=representation_definition) |
1978 | + representation_definition=representation_definition, |
1979 | + ) |
1980 | |
1981 | def _get_external_param_name(self, param_name): |
1982 | """Named operation parameter names are sent as is.""" |
1983 | @@ -695,13 +759,15 @@ class Entry(Resource): |
1984 | # occur. Poking this directly into self.__dict__ means that |
1985 | # the check for self._dirty_attributes won't call __getattr__(), |
1986 | # breaking the cycle. |
1987 | - self.__dict__['_dirty_attributes'] = {} |
1988 | + self.__dict__["_dirty_attributes"] = {} |
1989 | super(Entry, self).__init__(root, wadl_resource) |
1990 | |
1991 | def __repr__(self): |
1992 | """Return the WADL resource type and the URL to the resource.""" |
1993 | - return '<%s at %s>' % ( |
1994 | - URI(self.resource_type_link).fragment, self.self_link) |
1995 | + return "<%s at %s>" % ( |
1996 | + URI(self.resource_type_link).fragment, |
1997 | + self.self_link, |
1998 | + ) |
1999 | |
2000 | def lp_delete(self): |
2001 | """Delete the resource.""" |
2002 | @@ -713,7 +779,7 @@ class Entry(Resource): |
2003 | |
2004 | def __getattr__(self, name): |
2005 | """Try to retrive a parameter of the given name.""" |
2006 | - if name != '_dirty_attributes': |
2007 | + if name != "_dirty_attributes": |
2008 | if name in self._dirty_attributes: |
2009 | return self._dirty_attributes[name] |
2010 | return super(Entry, self).__getattr__(name) |
2011 | @@ -721,8 +787,10 @@ class Entry(Resource): |
2012 | def __setattr__(self, name, value): |
2013 | """Set the parameter of the given name.""" |
2014 | if not self.lp_has_parameter(name): |
2015 | - raise AttributeError("'%s' object has no attribute '%s'" % |
2016 | - (self.__class__.__name__, name)) |
2017 | + raise AttributeError( |
2018 | + "'%s' object has no attribute '%s'" |
2019 | + % (self.__class__.__name__, name) |
2020 | + ) |
2021 | self._dirty_attributes[name] = value |
2022 | |
2023 | def __eq__(self, other): |
2024 | @@ -733,44 +801,47 @@ class Entry(Resource): |
2025 | contain the same values. |
2026 | """ |
2027 | return ( |
2028 | - other is not None and |
2029 | - self.self_link == other.self_link and |
2030 | - self.http_etag == other.http_etag and |
2031 | - self._dirty_attributes == other._dirty_attributes) |
2032 | + other is not None |
2033 | + and self.self_link == other.self_link |
2034 | + and self.http_etag == other.http_etag |
2035 | + and self._dirty_attributes == other._dirty_attributes |
2036 | + ) |
2037 | |
2038 | def lp_refresh(self, new_url=None): |
2039 | """Update this resource's representation.""" |
2040 | - etag = getattr(self, 'http_etag', None) |
2041 | + etag = getattr(self, "http_etag", None) |
2042 | super(Entry, self).lp_refresh(new_url, etag) |
2043 | self._dirty_attributes.clear() |
2044 | |
2045 | def lp_save(self): |
2046 | """Save changes to the entry.""" |
2047 | representation = self._transform_resources_to_links( |
2048 | - self._dirty_attributes) |
2049 | + self._dirty_attributes |
2050 | + ) |
2051 | |
2052 | # If the entry contains an ETag, set the If-Match header |
2053 | # to that value. |
2054 | headers = {} |
2055 | - etag = getattr(self, 'http_etag', None) |
2056 | + etag = getattr(self, "http_etag", None) |
2057 | if etag is not None: |
2058 | - headers['If-Match'] = etag |
2059 | + headers["If-Match"] = etag |
2060 | |
2061 | # PATCH the new representation to the 'self' link. It's possible that |
2062 | # this will cause the object to be permanently moved. Catch that |
2063 | # exception and refresh our representation. |
2064 | response, content = self._root._browser.patch( |
2065 | - URI(self.self_link), representation, headers) |
2066 | + URI(self.self_link), representation, headers |
2067 | + ) |
2068 | if response.status == 301: |
2069 | - self.lp_refresh(response['location']) |
2070 | + self.lp_refresh(response["location"]) |
2071 | self._dirty_attributes.clear() |
2072 | |
2073 | - content_type = response['content-type'] |
2074 | + content_type = response["content-type"] |
2075 | if response.status == 209 and content_type == self.JSON_MEDIA_TYPE: |
2076 | # The server sent back a new representation of the object. |
2077 | # Use it in preference to the existing representation. |
2078 | if isinstance(content, binary_type): |
2079 | - content = content.decode('utf-8') |
2080 | + content = content.decode("utf-8") |
2081 | new_representation = loads(content) |
2082 | self._wadl_resource.representation = new_representation |
2083 | self._wadl_resource.media_type = content_type |
2084 | @@ -799,7 +870,7 @@ class Collection(Resource): |
2085 | # not directly present. |
2086 | return total_size.value |
2087 | else: |
2088 | - raise TypeError('collection size is not available') |
2089 | + raise TypeError("collection size is not available") |
2090 | |
2091 | def __iter__(self): |
2092 | """Iterate over the items in the collection. |
2093 | @@ -811,14 +882,15 @@ class Collection(Resource): |
2094 | current_page = self._wadl_resource.representation |
2095 | while True: |
2096 | for resource in self._convert_dicts_to_entries( |
2097 | - current_page.get('entries', {})): |
2098 | + current_page.get("entries", {}) |
2099 | + ): |
2100 | yield resource |
2101 | - next_link = current_page.get('next_collection_link') |
2102 | + next_link = current_page.get("next_collection_link") |
2103 | if next_link is None: |
2104 | break |
2105 | next_get = self._root._browser.get(URI(next_link)) |
2106 | if isinstance(next_get, binary_type): |
2107 | - next_get = next_get.decode('utf-8') |
2108 | + next_get = next_get.decode("utf-8") |
2109 | current_page = loads(next_get) |
2110 | |
2111 | def __getitem__(self, key): |
2112 | @@ -845,15 +917,19 @@ class Collection(Resource): |
2113 | stop = slice.stop |
2114 | |
2115 | if start < 0: |
2116 | - raise ValueError("Collection slices must have a nonnegative " |
2117 | - "start point.") |
2118 | + raise ValueError( |
2119 | + "Collection slices must have a nonnegative " "start point." |
2120 | + ) |
2121 | if stop < 0: |
2122 | - raise ValueError("Collection slices must have a definite, " |
2123 | - "nonnegative end point.") |
2124 | + raise ValueError( |
2125 | + "Collection slices must have a definite, " |
2126 | + "nonnegative end point." |
2127 | + ) |
2128 | |
2129 | existing_representation = self._wadl_resource.representation |
2130 | - if (existing_representation is not None |
2131 | - and start < len(existing_representation['entries'])): |
2132 | + if existing_representation is not None and start < len( |
2133 | + existing_representation["entries"] |
2134 | + ): |
2135 | # An optimization: the first page of entries has already |
2136 | # been loaded. This can happen if this collection is the |
2137 | # return value of a named operation, or if the client did |
2138 | @@ -870,11 +946,11 @@ class Collection(Resource): |
2139 | # objects to retrieve from the server later. This saves us |
2140 | # time and bandwidth, and it might let us save a whole |
2141 | # HTTP request. |
2142 | - entry_page = existing_representation['entries'] |
2143 | + entry_page = existing_representation["entries"] |
2144 | |
2145 | first_page_size = len(entry_page) |
2146 | entry_dicts = entry_page[start:stop] |
2147 | - page_url = existing_representation.get('next_collection_link') |
2148 | + page_url = existing_representation.get("next_collection_link") |
2149 | else: |
2150 | # No part of this collection has been loaded yet, or the |
2151 | # slice starts beyond the part that has been loaded. We'll |
2152 | @@ -884,7 +960,8 @@ class Collection(Resource): |
2153 | first_page_size = None |
2154 | entry_dicts = [] |
2155 | page_url = self._with_url_query_variable_set( |
2156 | - self._wadl_resource.url, 'ws.start', start) |
2157 | + self._wadl_resource.url, "ws.start", start |
2158 | + ) |
2159 | |
2160 | desired_size = stop - start |
2161 | more_needed = desired_size - len(entry_dicts) |
2162 | @@ -893,13 +970,13 @@ class Collection(Resource): |
2163 | while more_needed > 0 and page_url is not None: |
2164 | page_get = self._root._browser.get(page_url) |
2165 | if isinstance(page_get, binary_type): |
2166 | - page_get = page_get.decode('utf-8') |
2167 | + page_get = page_get.decode("utf-8") |
2168 | representation = loads(page_get) |
2169 | - current_page_entries = representation['entries'] |
2170 | + current_page_entries = representation["entries"] |
2171 | entry_dicts += current_page_entries[:more_needed] |
2172 | more_needed = desired_size - len(entry_dicts) |
2173 | |
2174 | - page_url = representation.get('next_collection_link') |
2175 | + page_url = representation.get("next_collection_link") |
2176 | if page_url is None: |
2177 | # We've gotten the entire collection; there are no |
2178 | # more entries. |
2179 | @@ -915,14 +992,17 @@ class Collection(Resource): |
2180 | # need. If we're wrong, there's no problem; we'll just |
2181 | # keep looping. |
2182 | page_url = self._with_url_query_variable_set( |
2183 | - page_url, 'ws.size', more_needed) |
2184 | + page_url, "ws.size", more_needed |
2185 | + ) |
2186 | |
2187 | if slice.step is not None: |
2188 | - entry_dicts = entry_dicts[::slice.step] |
2189 | + entry_dicts = entry_dicts[:: slice.step] |
2190 | |
2191 | # Convert entry_dicts into a list of Entry objects. |
2192 | - return [resource for resource |
2193 | - in self._convert_dicts_to_entries(entry_dicts)] |
2194 | + return [ |
2195 | + resource |
2196 | + for resource in self._convert_dicts_to_entries(entry_dicts) |
2197 | + ] |
2198 | |
2199 | def _convert_dicts_to_entries(self, entries): |
2200 | """Convert dictionaries describing entries to Entry objects. |
2201 | @@ -937,17 +1017,20 @@ class Collection(Resource): |
2202 | :yield: A sequence of Entry instances. |
2203 | """ |
2204 | for entry_dict in entries: |
2205 | - resource_url = entry_dict['self_link'] |
2206 | - resource_type_link = entry_dict['resource_type_link'] |
2207 | + resource_url = entry_dict["self_link"] |
2208 | + resource_type_link = entry_dict["resource_type_link"] |
2209 | wadl_application = self._wadl_resource.application |
2210 | resource_type = wadl_application.get_resource_type( |
2211 | - resource_type_link) |
2212 | + resource_type_link |
2213 | + ) |
2214 | resource = WadlResource( |
2215 | - self._wadl_resource.application, resource_url, |
2216 | - resource_type.tag) |
2217 | + self._wadl_resource.application, |
2218 | + resource_url, |
2219 | + resource_type.tag, |
2220 | + ) |
2221 | yield Resource._create_bound_resource( |
2222 | - self._root, resource, entry_dict, self.JSON_MEDIA_TYPE, |
2223 | - False) |
2224 | + self._root, resource, entry_dict, self.JSON_MEDIA_TYPE, False |
2225 | + ) |
2226 | |
2227 | def _with_url_query_variable_set(self, url, variable, new_value): |
2228 | """A helper method to set a query variable in a URL.""" |
2229 | @@ -1008,7 +1091,8 @@ class CollectionWithKeyBasedLookup(Collection): |
2230 | # this will save us one round trip. |
2231 | representation = None |
2232 | resource_type_link = urljoin( |
2233 | - self._root._wadl.markup_url, '#' + self.collection_of) |
2234 | + self._root._wadl.markup_url, "#" + self.collection_of |
2235 | + ) |
2236 | else: |
2237 | # We don't know what kind of resource this is. Either the |
2238 | # subclass wasn't programmed with this knowledge, or |
2239 | @@ -1020,7 +1104,7 @@ class CollectionWithKeyBasedLookup(Collection): |
2240 | try: |
2241 | url_get = self._root._browser.get(url) |
2242 | if isinstance(url_get, binary_type): |
2243 | - url_get = url_get.decode('utf-8') |
2244 | + url_get = url_get.decode("utf-8") |
2245 | representation = loads(url_get) |
2246 | except HTTPError as error: |
2247 | # There's no resource corresponding to the given ID. |
2248 | @@ -1029,12 +1113,15 @@ class CollectionWithKeyBasedLookup(Collection): |
2249 | raise |
2250 | # We know that every lazr.restful resource has a |
2251 | # 'resource_type_link' in its representation. |
2252 | - resource_type_link = representation['resource_type_link'] |
2253 | + resource_type_link = representation["resource_type_link"] |
2254 | |
2255 | resource = WadlResource(self._root._wadl, url, resource_type_link) |
2256 | return self._create_bound_resource( |
2257 | - self._root, resource, representation=representation, |
2258 | - representation_needs_processing=False) |
2259 | + self._root, |
2260 | + resource, |
2261 | + representation=representation, |
2262 | + representation_needs_processing=False, |
2263 | + ) |
2264 | |
2265 | # If provided, this should be a string designating the ID of a |
2266 | # resource_type from a specific service's WADL file. |
2267 | @@ -1047,35 +1134,43 @@ class CollectionWithKeyBasedLookup(Collection): |
2268 | |
2269 | class HostedFileBuffer(BytesIO): |
2270 | """The contents of a file hosted by a lazr.restful service.""" |
2271 | + |
2272 | def __init__(self, hosted_file, mode, content_type=None, filename=None): |
2273 | self.url = hosted_file._wadl_resource.url |
2274 | - if mode == 'r': |
2275 | + if mode == "r": |
2276 | if content_type is not None: |
2277 | - raise ValueError("Files opened for read access can't " |
2278 | - "specify content_type.") |
2279 | + raise ValueError( |
2280 | + "Files opened for read access can't " |
2281 | + "specify content_type." |
2282 | + ) |
2283 | if filename is not None: |
2284 | - raise ValueError("Files opened for read access can't " |
2285 | - "specify filename.") |
2286 | + raise ValueError( |
2287 | + "Files opened for read access can't " "specify filename." |
2288 | + ) |
2289 | response, value = hosted_file._root._browser.get( |
2290 | - self.url, return_response=True) |
2291 | - content_type = response['content-type'] |
2292 | - last_modified = response.get('last-modified') |
2293 | + self.url, return_response=True |
2294 | + ) |
2295 | + content_type = response["content-type"] |
2296 | + last_modified = response.get("last-modified") |
2297 | |
2298 | # The Content-Location header contains the URL of the file |
2299 | # hosted by the web service. We happen to know that the |
2300 | # final component of the URL is the name of the uploaded |
2301 | # file. |
2302 | - content_location = response['content-location'] |
2303 | + content_location = response["content-location"] |
2304 | path = urlparse(content_location)[2] |
2305 | filename = unquote(path.split("/")[-1]) |
2306 | - elif mode == 'w': |
2307 | - value = '' |
2308 | + elif mode == "w": |
2309 | + value = "" |
2310 | if content_type is None: |
2311 | - raise ValueError("Files opened for write access must " |
2312 | - "specify content_type.") |
2313 | + raise ValueError( |
2314 | + "Files opened for write access must " |
2315 | + "specify content_type." |
2316 | + ) |
2317 | if filename is None: |
2318 | - raise ValueError("Files opened for write access must " |
2319 | - "specify filename.") |
2320 | + raise ValueError( |
2321 | + "Files opened for write access must " "specify filename." |
2322 | + ) |
2323 | last_modified = None |
2324 | else: |
2325 | raise ValueError("Invalid mode. Supported modes are: r, w") |
2326 | @@ -1088,11 +1183,14 @@ class HostedFileBuffer(BytesIO): |
2327 | BytesIO.__init__(self, value) |
2328 | |
2329 | def close(self): |
2330 | - if self.mode == 'w': |
2331 | + if self.mode == "w": |
2332 | disposition = 'attachment; filename="%s"' % self.filename |
2333 | self.hosted_file._root._browser.put( |
2334 | - self.url, self.getvalue(), |
2335 | - self.content_type, {'Content-Disposition': disposition}) |
2336 | + self.url, |
2337 | + self.getvalue(), |
2338 | + self.content_type, |
2339 | + {"Content-Disposition": disposition}, |
2340 | + ) |
2341 | BytesIO.close(self) |
2342 | |
2343 | def write(self, b): |
2344 | diff --git a/src/lazr/restfulclient/tests/__init__.py b/src/lazr/restfulclient/tests/__init__.py |
2345 | index 1764b70..de07b86 100644 |
2346 | --- a/src/lazr/restfulclient/tests/__init__.py |
2347 | +++ b/src/lazr/restfulclient/tests/__init__.py |
2348 | @@ -6,9 +6,9 @@ |
2349 | # under the terms of the GNU Lesser General Public License as published by |
2350 | # the Free Software Foundation, version 3 of the License. |
2351 | # |
2352 | -# lazr.restfulclient is distributed in the hope that it will be useful, but WITHOUT |
2353 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
2354 | -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
2355 | +# lazr.restfulclient is distributed in the hope that it will be useful, but |
2356 | +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
2357 | +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
2358 | # License for more details. |
2359 | # |
2360 | # You should have received a copy of the GNU Lesser General Public License |
2361 | diff --git a/src/lazr/restfulclient/tests/example.py b/src/lazr/restfulclient/tests/example.py |
2362 | index 0c37c6b..d9c00a6 100644 |
2363 | --- a/src/lazr/restfulclient/tests/example.py |
2364 | +++ b/src/lazr/restfulclient/tests/example.py |
2365 | @@ -6,9 +6,9 @@ |
2366 | # under the terms of the GNU Lesser General Public License as published by |
2367 | # the Free Software Foundation, version 3 of the License. |
2368 | # |
2369 | -# lazr.restfulclient is distributed in the hope that it will be useful, but WITHOUT |
2370 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
2371 | -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
2372 | +# lazr.restfulclient is distributed in the hope that it will be useful, but |
2373 | +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
2374 | +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
2375 | # License for more details. |
2376 | # |
2377 | # You should have received a copy of the GNU Lesser General Public License |
2378 | @@ -17,8 +17,8 @@ |
2379 | |
2380 | __metaclass__ = type |
2381 | __all__ = [ |
2382 | - 'CookbookWebServiceClient', |
2383 | - ] |
2384 | + "CookbookWebServiceClient", |
2385 | +] |
2386 | |
2387 | |
2388 | try: |
2389 | @@ -28,7 +28,9 @@ except ImportError: |
2390 | from urllib import quote |
2391 | |
2392 | from lazr.restfulclient.resource import ( |
2393 | - CollectionWithKeyBasedLookup, ServiceRoot) |
2394 | + CollectionWithKeyBasedLookup, |
2395 | + ServiceRoot, |
2396 | +) |
2397 | |
2398 | |
2399 | class CookbookSet(CollectionWithKeyBasedLookup): |
2400 | @@ -36,8 +38,11 @@ class CookbookSet(CollectionWithKeyBasedLookup): |
2401 | |
2402 | def _get_url_from_id(self, id): |
2403 | """Transform a cookbook name into the URL to a cookbook resource.""" |
2404 | - return (str(self._root._root_uri.ensureSlash()) |
2405 | - + 'cookbooks/' + quote(str(id))) |
2406 | + return ( |
2407 | + str(self._root._root_uri.ensureSlash()) |
2408 | + + "cookbooks/" |
2409 | + + quote(str(id)) |
2410 | + ) |
2411 | |
2412 | collection_of = "cookbook" |
2413 | |
2414 | @@ -47,7 +52,7 @@ class RecipeSet(CollectionWithKeyBasedLookup): |
2415 | |
2416 | def _get_url_from_id(self, id): |
2417 | """Transform a recipe ID into the URL to a recipe resource.""" |
2418 | - return str(self._root._root_uri.ensureSlash()) + 'recipes/' + str(id) |
2419 | + return str(self._root._root_uri.ensureSlash()) + "recipes/" + str(id) |
2420 | |
2421 | collection_of = "recipe" |
2422 | |
2423 | @@ -55,13 +60,18 @@ class RecipeSet(CollectionWithKeyBasedLookup): |
2424 | class CookbookWebServiceClient(ServiceRoot): |
2425 | |
2426 | RESOURCE_TYPE_CLASSES = dict(ServiceRoot.RESOURCE_TYPE_CLASSES) |
2427 | - RESOURCE_TYPE_CLASSES['recipes'] = RecipeSet |
2428 | - RESOURCE_TYPE_CLASSES['cookbooks'] = CookbookSet |
2429 | + RESOURCE_TYPE_CLASSES["recipes"] = RecipeSet |
2430 | + RESOURCE_TYPE_CLASSES["cookbooks"] = CookbookSet |
2431 | |
2432 | DEFAULT_SERVICE_ROOT = "http://cookbooks.dev/" |
2433 | DEFAULT_VERSION = "1.0" |
2434 | |
2435 | - def __init__(self, service_root=DEFAULT_SERVICE_ROOT, |
2436 | - version=DEFAULT_VERSION, cache=None): |
2437 | + def __init__( |
2438 | + self, |
2439 | + service_root=DEFAULT_SERVICE_ROOT, |
2440 | + version=DEFAULT_VERSION, |
2441 | + cache=None, |
2442 | + ): |
2443 | super(CookbookWebServiceClient, self).__init__( |
2444 | - None, service_root, cache=cache, version=version) |
2445 | + None, service_root, cache=cache, version=version |
2446 | + ) |
2447 | diff --git a/src/lazr/restfulclient/tests/test_atomicfilecache.py b/src/lazr/restfulclient/tests/test_atomicfilecache.py |
2448 | index f767b53..b879bd2 100644 |
2449 | --- a/src/lazr/restfulclient/tests/test_atomicfilecache.py |
2450 | +++ b/src/lazr/restfulclient/tests/test_atomicfilecache.py |
2451 | @@ -20,29 +20,28 @@ |
2452 | __metaclass__ = type |
2453 | |
2454 | import shutil |
2455 | +import sys |
2456 | import tempfile |
2457 | import unittest |
2458 | |
2459 | -import sys |
2460 | +import httplib2 |
2461 | + |
2462 | +from lazr.restfulclient._browser import AtomicFileCache, safename |
2463 | + |
2464 | PY3 = sys.version_info[0] >= 3 |
2465 | if PY3: |
2466 | binary_type = bytes |
2467 | else: |
2468 | binary_type = str |
2469 | |
2470 | -import httplib2 |
2471 | - |
2472 | -from lazr.restfulclient._browser import ( |
2473 | - AtomicFileCache, safename) |
2474 | - |
2475 | |
2476 | class TestFileCacheInterface(unittest.TestCase): |
2477 | """Tests for ``AtomicFileCache``.""" |
2478 | |
2479 | file_cache_factory = httplib2.FileCache |
2480 | |
2481 | - unicode_bytes = b'pa\xc9\xaa\xce\xb8\xc9\x99n' |
2482 | - unicode_text = unicode_bytes.decode('utf-8') |
2483 | + unicode_bytes = b"pa\xc9\xaa\xce\xb8\xc9\x99n" |
2484 | + unicode_text = unicode_bytes.decode("utf-8") |
2485 | |
2486 | def setUp(self): |
2487 | super(TestFileCacheInterface, self).setUp() |
2488 | @@ -59,34 +58,34 @@ class TestFileCacheInterface(unittest.TestCase): |
2489 | def test_get_non_existent_key(self): |
2490 | # get() returns None if the key does not exist. |
2491 | cache = self.make_file_cache() |
2492 | - self.assertIs(None, cache.get('nonexistent')) |
2493 | + self.assertIs(None, cache.get("nonexistent")) |
2494 | |
2495 | def test_set_key(self): |
2496 | # A key set with set() can be got by get(). |
2497 | cache = self.make_file_cache() |
2498 | - cache.set('key', b'value') |
2499 | - self.assertEqual(b'value', cache.get('key')) |
2500 | + cache.set("key", b"value") |
2501 | + self.assertEqual(b"value", cache.get("key")) |
2502 | |
2503 | def test_set_twice_overrides(self): |
2504 | # Setting a key again overrides the value. |
2505 | cache = self.make_file_cache() |
2506 | - cache.set('key', b'value') |
2507 | - cache.set('key', b'new-value') |
2508 | - self.assertEqual(b'new-value', cache.get('key')) |
2509 | + cache.set("key", b"value") |
2510 | + cache.set("key", b"new-value") |
2511 | + self.assertEqual(b"new-value", cache.get("key")) |
2512 | |
2513 | def test_delete_absent_key(self): |
2514 | # Deleting a key that's not there does nothing. |
2515 | cache = self.make_file_cache() |
2516 | - cache.delete('nonexistent') |
2517 | - self.assertIs(None, cache.get('nonexistent')) |
2518 | + cache.delete("nonexistent") |
2519 | + self.assertIs(None, cache.get("nonexistent")) |
2520 | |
2521 | def test_delete_key(self): |
2522 | # A key once set can be deleted. Further attempts to get that key |
2523 | # return None. |
2524 | cache = self.make_file_cache() |
2525 | - cache.set('key', b'value') |
2526 | - cache.delete('key') |
2527 | - self.assertIs(None, cache.get('key')) |
2528 | + cache.set("key", b"value") |
2529 | + cache.delete("key") |
2530 | + self.assertIs(None, cache.get("key")) |
2531 | |
2532 | def test_get_non_string_key(self): |
2533 | # get() raises TypeError if asked to get a non-string key. |
2534 | @@ -101,15 +100,15 @@ class TestFileCacheInterface(unittest.TestCase): |
2535 | def test_set_non_string_key(self): |
2536 | # set() raises TypeError if asked to set a non-string key. |
2537 | cache = self.make_file_cache() |
2538 | - self.assertRaises(TypeError, cache.set, 42, 'the answer') |
2539 | + self.assertRaises(TypeError, cache.set, 42, "the answer") |
2540 | |
2541 | def test_set_non_string_value(self): |
2542 | # set() raises TypeError if asked to set a key to a non-string value. |
2543 | # Attempts to retrieve that value return the empty string. This is |
2544 | # probably a bug in httplib2.FileCache. |
2545 | cache = self.make_file_cache() |
2546 | - self.assertRaises(TypeError, cache.set, 'answer', 42) |
2547 | - self.assertEqual(b'', cache.get('answer')) |
2548 | + self.assertRaises(TypeError, cache.set, "answer", 42) |
2549 | + self.assertEqual(b"", cache.get("answer")) |
2550 | |
2551 | def test_get_unicode(self): |
2552 | # get() can retrieve unicode keys. |
2553 | @@ -118,20 +117,19 @@ class TestFileCacheInterface(unittest.TestCase): |
2554 | |
2555 | def test_set_unicode_keys(self): |
2556 | cache = self.make_file_cache() |
2557 | - cache.set(self.unicode_text, b'value') |
2558 | - self.assertEqual(b'value', cache.get(self.unicode_text)) |
2559 | + cache.set(self.unicode_text, b"value") |
2560 | + self.assertEqual(b"value", cache.get(self.unicode_text)) |
2561 | |
2562 | def test_set_unicode_value(self): |
2563 | # set() cannot store unicode values. Values must be bytes. |
2564 | cache = self.make_file_cache() |
2565 | error = TypeError if PY3 else UnicodeEncodeError |
2566 | - self.assertRaises( |
2567 | - error, cache.set, 'key', self.unicode_text) |
2568 | + self.assertRaises(error, cache.set, "key", self.unicode_text) |
2569 | |
2570 | def test_delete_unicode(self): |
2571 | # delete() can remove unicode keys. |
2572 | cache = self.make_file_cache() |
2573 | - cache.set(self.unicode_text, b'value') |
2574 | + cache.set(self.unicode_text, b"value") |
2575 | cache.delete(self.unicode_text) |
2576 | self.assertIs(None, cache.get(self.unicode_text)) |
2577 | |
2578 | @@ -144,7 +142,7 @@ class TestAtomicFileCache(TestFileCacheInterface): |
2579 | @staticmethod |
2580 | def prefix_safename(x): |
2581 | if isinstance(x, binary_type): |
2582 | - x = x.decode('utf-8') |
2583 | + x = x.decode("utf-8") |
2584 | return AtomicFileCache.TEMPFILE_PREFIX + x |
2585 | |
2586 | def test_set_non_string_value(self): |
2587 | @@ -153,22 +151,22 @@ class TestAtomicFileCache(TestFileCacheInterface): |
2588 | # |
2589 | # Note: This behaviour differs from httplib2.FileCache. |
2590 | cache = self.make_file_cache() |
2591 | - self.assertRaises(TypeError, cache.set, 'answer', 42) |
2592 | - self.assertIs(None, cache.get('answer')) |
2593 | + self.assertRaises(TypeError, cache.set, "answer", 42) |
2594 | + self.assertIs(None, cache.get("answer")) |
2595 | |
2596 | # Implementation-specific tests follow. |
2597 | |
2598 | def test_bad_safename_get(self): |
2599 | safename = self.prefix_safename |
2600 | cache = AtomicFileCache(self.cache_dir, safename) |
2601 | - self.assertRaises(ValueError, cache.get, 'key') |
2602 | + self.assertRaises(ValueError, cache.get, "key") |
2603 | |
2604 | def test_bad_safename_set(self): |
2605 | safename = self.prefix_safename |
2606 | cache = AtomicFileCache(self.cache_dir, safename) |
2607 | - self.assertRaises(ValueError, cache.set, 'key', b'value') |
2608 | + self.assertRaises(ValueError, cache.set, "key", b"value") |
2609 | |
2610 | def test_bad_safename_delete(self): |
2611 | safename = self.prefix_safename |
2612 | cache = AtomicFileCache(self.cache_dir, safename) |
2613 | - self.assertRaises(ValueError, cache.delete, 'key') |
2614 | + self.assertRaises(ValueError, cache.delete, "key") |
2615 | diff --git a/src/lazr/restfulclient/tests/test_docs.py b/src/lazr/restfulclient/tests/test_docs.py |
2616 | index a350b40..df3807a 100644 |
2617 | --- a/src/lazr/restfulclient/tests/test_docs.py |
2618 | +++ b/src/lazr/restfulclient/tests/test_docs.py |
2619 | @@ -6,9 +6,9 @@ |
2620 | # under the terms of the GNU Lesser General Public License as published by |
2621 | # the Free Software Foundation, version 3 of the License. |
2622 | # |
2623 | -# lazr.restfulclient is distributed in the hope that it will be useful, but WITHOUT |
2624 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
2625 | -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
2626 | +# lazr.restfulclient is distributed in the hope that it will be useful, but |
2627 | +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
2628 | +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
2629 | # License for more details. |
2630 | # |
2631 | # You should have received a copy of the GNU Lesser General Public License |
2632 | @@ -19,16 +19,20 @@ |
2633 | |
2634 | __metaclass__ = type |
2635 | __all__ = [ |
2636 | - 'load_tests', |
2637 | - ] |
2638 | + "load_tests", |
2639 | +] |
2640 | |
2641 | import atexit |
2642 | import doctest |
2643 | import os |
2644 | |
2645 | -from pkg_resources import ( |
2646 | - resource_filename, resource_exists, resource_listdir, cleanup_resources) |
2647 | import wsgi_intercept |
2648 | +from pkg_resources import ( |
2649 | + cleanup_resources, |
2650 | + resource_exists, |
2651 | + resource_filename, |
2652 | + resource_listdir, |
2653 | +) |
2654 | from wsgi_intercept.httplib2_intercept import install, uninstall |
2655 | |
2656 | # We avoid importing anything from lazr.restful into the module level, |
2657 | @@ -36,21 +40,24 @@ from wsgi_intercept.httplib2_intercept import install, uninstall |
2658 | # lazr.restful. |
2659 | |
2660 | DOCTEST_FLAGS = ( |
2661 | - doctest.ELLIPSIS | |
2662 | - doctest.NORMALIZE_WHITESPACE | |
2663 | - doctest.REPORT_NDIFF) |
2664 | + doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_NDIFF |
2665 | +) |
2666 | |
2667 | |
2668 | def setUp(test): |
2669 | from lazr.restful.example.base.tests.test_integration import WSGILayer |
2670 | + |
2671 | install() |
2672 | wsgi_intercept.add_wsgi_intercept( |
2673 | - 'cookbooks.dev', 80, WSGILayer.make_application) |
2674 | + "cookbooks.dev", 80, WSGILayer.make_application |
2675 | + ) |
2676 | |
2677 | |
2678 | def tearDown(test): |
2679 | - from lazr.restful.example.base.interfaces import IFileManager |
2680 | from zope.component import getUtility |
2681 | + |
2682 | + from lazr.restful.example.base.interfaces import IFileManager |
2683 | + |
2684 | uninstall() |
2685 | file_manager = getUtility(IFileManager) |
2686 | file_manager.files = {} |
2687 | @@ -61,29 +68,40 @@ def find_doctests(suffix, ignore_suffix=None): |
2688 | """Find doctests matching a certain suffix.""" |
2689 | doctest_files = [] |
2690 | # Match doctests against the suffix. |
2691 | - if resource_exists('lazr.restfulclient', 'docs'): |
2692 | - for name in resource_listdir('lazr.restfulclient', 'docs'): |
2693 | + if resource_exists("lazr.restfulclient", "docs"): |
2694 | + for name in resource_listdir("lazr.restfulclient", "docs"): |
2695 | if ignore_suffix is not None and name.endswith(ignore_suffix): |
2696 | continue |
2697 | if name.endswith(suffix): |
2698 | doctest_files.append( |
2699 | os.path.abspath( |
2700 | resource_filename( |
2701 | - 'lazr.restfulclient', 'docs/%s' % name))) |
2702 | + "lazr.restfulclient", "docs/%s" % name |
2703 | + ) |
2704 | + ) |
2705 | + ) |
2706 | return doctest_files |
2707 | |
2708 | |
2709 | def load_tests(loader, tests, pattern): |
2710 | """Load all the doctests.""" |
2711 | from lazr.restful.example.base.tests.test_integration import WSGILayer |
2712 | + |
2713 | atexit.register(cleanup_resources) |
2714 | restful_suite = doctest.DocFileSuite( |
2715 | - *find_doctests('.rst', ignore_suffix='.standalone.rst'), |
2716 | - module_relative=False, optionflags=DOCTEST_FLAGS, |
2717 | - setUp=setUp, tearDown=tearDown) |
2718 | + *find_doctests(".rst", ignore_suffix=".standalone.rst"), |
2719 | + module_relative=False, |
2720 | + optionflags=DOCTEST_FLAGS, |
2721 | + setUp=setUp, |
2722 | + tearDown=tearDown |
2723 | + ) |
2724 | restful_suite.layer = WSGILayer |
2725 | tests.addTest(restful_suite) |
2726 | - tests.addTest(doctest.DocFileSuite( |
2727 | - *find_doctests('.standalone.rst'), |
2728 | - module_relative=False, optionflags=DOCTEST_FLAGS)) |
2729 | + tests.addTest( |
2730 | + doctest.DocFileSuite( |
2731 | + *find_doctests(".standalone.rst"), |
2732 | + module_relative=False, |
2733 | + optionflags=DOCTEST_FLAGS |
2734 | + ) |
2735 | + ) |
2736 | return tests |
2737 | diff --git a/src/lazr/restfulclient/tests/test_error.py b/src/lazr/restfulclient/tests/test_error.py |
2738 | index 7a43d4a..30c1090 100644 |
2739 | --- a/src/lazr/restfulclient/tests/test_error.py |
2740 | +++ b/src/lazr/restfulclient/tests/test_error.py |
2741 | @@ -22,19 +22,27 @@ __metaclass__ = type |
2742 | import unittest |
2743 | |
2744 | from lazr.restfulclient.errors import ( |
2745 | - ClientError, Conflict, MethodNotAllowed, NotFound, |
2746 | - PreconditionFailed, ResponseError, ServerError, Unauthorized, error_for) |
2747 | + ClientError, |
2748 | + Conflict, |
2749 | + MethodNotAllowed, |
2750 | + NotFound, |
2751 | + PreconditionFailed, |
2752 | + ResponseError, |
2753 | + ServerError, |
2754 | + Unauthorized, |
2755 | + error_for, |
2756 | +) |
2757 | |
2758 | |
2759 | class DummyRequest(object): |
2760 | """Just enough of a request to fool error_for().""" |
2761 | + |
2762 | def __init__(self, status): |
2763 | self.status = status |
2764 | |
2765 | |
2766 | class TestErrorFor(unittest.TestCase): |
2767 | - |
2768 | - def error_for_status(self, status, expected_error, content=''): |
2769 | + def error_for_status(self, status, expected_error, content=""): |
2770 | """Make sure error_for returns the right HTTPError subclass.""" |
2771 | request = DummyRequest(status) |
2772 | error = error_for(request, content) |
2773 | diff --git a/src/lazr/restfulclient/tests/test_oauth.py b/src/lazr/restfulclient/tests/test_oauth.py |
2774 | index be0d87d..e87914c 100644 |
2775 | --- a/src/lazr/restfulclient/tests/test_oauth.py |
2776 | +++ b/src/lazr/restfulclient/tests/test_oauth.py |
2777 | @@ -25,10 +25,7 @@ import os.path |
2778 | import stat |
2779 | import unittest |
2780 | |
2781 | -from fixtures import ( |
2782 | - MockPatch, |
2783 | - TempDir, |
2784 | - ) |
2785 | +from fixtures import MockPatch, TempDir |
2786 | from testtools import TestCase |
2787 | |
2788 | from lazr.restfulclient.authorize.oauth import ( |
2789 | @@ -36,11 +33,10 @@ from lazr.restfulclient.authorize.oauth import ( |
2790 | Consumer, |
2791 | OAuthAuthorizer, |
2792 | SystemWideConsumer, |
2793 | - ) |
2794 | +) |
2795 | |
2796 | |
2797 | class TestConsumer(TestCase): |
2798 | - |
2799 | def test_data_fields(self): |
2800 | consumer = Consumer("key", "secret", "application") |
2801 | self.assertEqual(consumer.key, "key") |
2802 | @@ -54,7 +50,6 @@ class TestConsumer(TestCase): |
2803 | |
2804 | |
2805 | class TestAccessToken(TestCase): |
2806 | - |
2807 | def test_data_fields(self): |
2808 | access_token = AccessToken("key", "secret", "context") |
2809 | self.assertEqual(access_token.key, "key") |
2810 | @@ -70,47 +65,46 @@ class TestAccessToken(TestCase): |
2811 | access_token = AccessToken("lock&key", "secret=password") |
2812 | self.assertEqual( |
2813 | "oauth_token_secret=secret%3Dpassword&oauth_token=lock%26key", |
2814 | - str(access_token)) |
2815 | + str(access_token), |
2816 | + ) |
2817 | |
2818 | def test_from_string(self): |
2819 | access_token = AccessToken.from_string( |
2820 | - "oauth_token_secret=secret%3Dpassword&oauth_token=lock%26key") |
2821 | + "oauth_token_secret=secret%3Dpassword&oauth_token=lock%26key" |
2822 | + ) |
2823 | self.assertEqual(access_token.key, "lock&key") |
2824 | self.assertEqual(access_token.secret, "secret=password") |
2825 | |
2826 | |
2827 | class TestSystemWideConsumer(TestCase): |
2828 | - |
2829 | def test_useful_distro_name(self): |
2830 | # If distro.name returns a useful string, as it does on Ubuntu, |
2831 | # we'll use the first string for the system type. |
2832 | - self.useFixture(MockPatch('distro.name', return_value='Fooix')) |
2833 | - self.useFixture(MockPatch('platform.system', return_value='FooOS')) |
2834 | - self.useFixture(MockPatch('socket.gethostname', return_value='foo')) |
2835 | + self.useFixture(MockPatch("distro.name", return_value="Fooix")) |
2836 | + self.useFixture(MockPatch("platform.system", return_value="FooOS")) |
2837 | + self.useFixture(MockPatch("socket.gethostname", return_value="foo")) |
2838 | consumer = SystemWideConsumer("app name") |
2839 | - self.assertEqual( |
2840 | - consumer.key, 'System-wide: Fooix (foo)') |
2841 | + self.assertEqual(consumer.key, "System-wide: Fooix (foo)") |
2842 | |
2843 | def test_empty_distro_name(self): |
2844 | # If distro.name returns an empty string, as it does on Windows and |
2845 | # Mac OS X, we fall back to the result of platform.system(). |
2846 | - self.useFixture(MockPatch('distro.name', return_value='')) |
2847 | - self.useFixture(MockPatch('platform.system', return_value='BarOS')) |
2848 | - self.useFixture(MockPatch('socket.gethostname', return_value='bar')) |
2849 | + self.useFixture(MockPatch("distro.name", return_value="")) |
2850 | + self.useFixture(MockPatch("platform.system", return_value="BarOS")) |
2851 | + self.useFixture(MockPatch("socket.gethostname", return_value="bar")) |
2852 | consumer = SystemWideConsumer("app name") |
2853 | - self.assertEqual( |
2854 | - consumer.key, 'System-wide: BarOS (bar)') |
2855 | + self.assertEqual(consumer.key, "System-wide: BarOS (bar)") |
2856 | |
2857 | def test_broken_distro_name(self): |
2858 | # If distro.name raises an exception, we fall back to the result of |
2859 | # platform.system(). |
2860 | self.useFixture( |
2861 | - MockPatch('distro.name', side_effect=Exception('Oh noes!'))) |
2862 | - self.useFixture(MockPatch('platform.system', return_value='BazOS')) |
2863 | - self.useFixture(MockPatch('socket.gethostname', return_value='baz')) |
2864 | + MockPatch("distro.name", side_effect=Exception("Oh noes!")) |
2865 | + ) |
2866 | + self.useFixture(MockPatch("platform.system", return_value="BazOS")) |
2867 | + self.useFixture(MockPatch("socket.gethostname", return_value="baz")) |
2868 | consumer = SystemWideConsumer("app name") |
2869 | - self.assertEqual( |
2870 | - consumer.key, 'System-wide: BazOS (baz)') |
2871 | + self.assertEqual(consumer.key, "System-wide: BazOS (baz)") |
2872 | |
2873 | |
2874 | class TestOAuthAuthorizer(TestCase): |
2875 | @@ -120,26 +114,29 @@ class TestOAuthAuthorizer(TestCase): |
2876 | # Credentials can be saved to and loaded from a file using |
2877 | # save_to_path() and load_from_path(). |
2878 | temp_dir = self.useFixture(TempDir()).path |
2879 | - credentials_path = os.path.join(temp_dir, 'credentials') |
2880 | + credentials_path = os.path.join(temp_dir, "credentials") |
2881 | credentials = OAuthAuthorizer( |
2882 | - 'consumer.key', consumer_secret='consumer.secret', |
2883 | - access_token=AccessToken('access.key', 'access.secret')) |
2884 | + "consumer.key", |
2885 | + consumer_secret="consumer.secret", |
2886 | + access_token=AccessToken("access.key", "access.secret"), |
2887 | + ) |
2888 | credentials.save_to_path(credentials_path) |
2889 | self.assertTrue(os.path.exists(credentials_path)) |
2890 | |
2891 | # Make sure the file is readable and writable by the user, but |
2892 | # not by anyone else. |
2893 | - self.assertEqual(stat.S_IMODE(os.stat(credentials_path).st_mode), |
2894 | - stat.S_IREAD | stat.S_IWRITE) |
2895 | + self.assertEqual( |
2896 | + stat.S_IMODE(os.stat(credentials_path).st_mode), |
2897 | + stat.S_IREAD | stat.S_IWRITE, |
2898 | + ) |
2899 | |
2900 | loaded_credentials = OAuthAuthorizer.load_from_path(credentials_path) |
2901 | - self.assertEqual(loaded_credentials.consumer.key, 'consumer.key') |
2902 | - self.assertEqual( |
2903 | - loaded_credentials.consumer.secret, 'consumer.secret') |
2904 | - self.assertEqual( |
2905 | - loaded_credentials.access_token.key, 'access.key') |
2906 | + self.assertEqual(loaded_credentials.consumer.key, "consumer.key") |
2907 | + self.assertEqual(loaded_credentials.consumer.secret, "consumer.secret") |
2908 | + self.assertEqual(loaded_credentials.access_token.key, "access.key") |
2909 | self.assertEqual( |
2910 | - loaded_credentials.access_token.secret, 'access.secret') |
2911 | + loaded_credentials.access_token.secret, "access.secret" |
2912 | + ) |
2913 | |
2914 | |
2915 | def test_suite(): |
2916 | diff --git a/tox.ini b/tox.ini |
2917 | index 214d128..e1b6eb9 100644 |
2918 | --- a/tox.ini |
2919 | +++ b/tox.ini |
2920 | @@ -1,5 +1,5 @@ |
2921 | [tox] |
2922 | -envlist = |
2923 | +envlist = |
2924 | py27,lint,docs |
2925 | |
2926 | [testenv] |