Merge ~jugmac00/lazr.restfulclient:apply-black into lazr.restfulclient: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)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+411676@code.launchpad.net

Commit message

Apply black code formatter

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
1diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
2new file mode 100644
3index 0000000..135d4d2
4--- /dev/null
5+++ b/.git-blame-ignore-revs
6@@ -0,0 +1,2 @@
7+# apply black
8+2417072998741bd627c9906b9d4cbdff915e36a5
9diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
10index 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
42diff --git a/NEWS.rst b/NEWS.rst
43index 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 ===================
54diff --git a/pyproject.toml b/pyproject.toml
55new file mode 100644
56index 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
68diff --git a/setup.cfg b/setup.cfg
69index 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
83diff --git a/setup.py b/setup.py
84index 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+)
222diff --git a/src/lazr/__init__.py b/src/lazr/__init__.py
223index 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__)
249diff --git a/src/lazr/restfulclient/_browser.py b/src/lazr/restfulclient/_browser.py
250index 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+ )
711diff --git a/src/lazr/restfulclient/_json.py b/src/lazr/restfulclient/_json.py
712index 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()
732diff --git a/src/lazr/restfulclient/authorize/__init__.py b/src/lazr/restfulclient/authorize/__init__.py
733index 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)
772diff --git a/src/lazr/restfulclient/authorize/oauth.py b/src/lazr/restfulclient/authorize/oauth.py
773index 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
1039diff --git a/src/lazr/restfulclient/docs/Makefile b/src/lazr/restfulclient/docs/Makefile
1040index 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)
1050diff --git a/src/lazr/restfulclient/docs/authorizer.standalone.rst b/src/lazr/restfulclient/docs/authorizer.standalone.rst
1051index 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-
1059diff --git a/src/lazr/restfulclient/docs/conf.py b/src/lazr/restfulclient/docs/conf.py
1060index 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-
1212diff --git a/src/lazr/restfulclient/docs/toplevel.rst b/src/lazr/restfulclient/docs/toplevel.rst
1213index 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-
1221diff --git a/src/lazr/restfulclient/errors.py b/src/lazr/restfulclient/errors.py
1222index 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:
1319diff --git a/src/lazr/restfulclient/resource.py b/src/lazr/restfulclient/resource.py
1320index 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):
2344diff --git a/src/lazr/restfulclient/tests/__init__.py b/src/lazr/restfulclient/tests/__init__.py
2345index 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
2361diff --git a/src/lazr/restfulclient/tests/example.py b/src/lazr/restfulclient/tests/example.py
2362index 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+ )
2447diff --git a/src/lazr/restfulclient/tests/test_atomicfilecache.py b/src/lazr/restfulclient/tests/test_atomicfilecache.py
2448index 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")
2615diff --git a/src/lazr/restfulclient/tests/test_docs.py b/src/lazr/restfulclient/tests/test_docs.py
2616index 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
2737diff --git a/src/lazr/restfulclient/tests/test_error.py b/src/lazr/restfulclient/tests/test_error.py
2738index 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)
2773diff --git a/src/lazr/restfulclient/tests/test_oauth.py b/src/lazr/restfulclient/tests/test_oauth.py
2774index 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():
2916diff --git a/tox.ini b/tox.ini
2917index 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]

Subscribers

People subscribed via source and target branches