Merge ~jugmac00/lazr.restful:apply-black into lazr.restful:main

Proposed by Jürgen Gmach
Status: Merged
Merged at revision: 80ebe87e3b55ce133c180942c0e46c4116326faf
Proposed branch: ~jugmac00/lazr.restful:apply-black
Merge into: lazr.restful:main
Diff against target: 13815 lines (+3922/-2681)
57 files modified
.git-blame-ignore-revs (+2/-0)
.pre-commit-config.yaml (+6/-2)
NEWS.rst (+1/-0)
setup.py (+54/-52)
src/lazr/__init__.py (+2/-0)
src/lazr/restful/__init__.py (+1/-0)
src/lazr/restful/_bytestorage.py (+12/-10)
src/lazr/restful/_operation.py (+54/-31)
src/lazr/restful/_resource.py (+440/-321)
src/lazr/restful/debug.py (+9/-10)
src/lazr/restful/declarations.py (+583/-385)
src/lazr/restful/directives/__init__.py (+36/-16)
src/lazr/restful/docs/conf.py (+30/-23)
src/lazr/restful/error.py (+29/-25)
src/lazr/restful/example/base/filemanager.py (+10/-10)
src/lazr/restful/example/base/interfaces.py (+103/-51)
src/lazr/restful/example/base/root.py (+94/-49)
src/lazr/restful/example/base/security.py (+1/-1)
src/lazr/restful/example/base/subscribers.py (+1/-1)
src/lazr/restful/example/base/tests/test_integration.py (+21/-13)
src/lazr/restful/example/base/traversal.py (+14/-11)
src/lazr/restful/example/base_extended/comments.py (+3/-3)
src/lazr/restful/example/base_extended/tests/test_integration.py (+12/-9)
src/lazr/restful/example/multiversion/resources.py (+25/-25)
src/lazr/restful/example/multiversion/root.py (+17/-13)
src/lazr/restful/example/multiversion/tests/test_integration.py (+18/-15)
src/lazr/restful/example/wsgi/resources.py (+3/-6)
src/lazr/restful/example/wsgi/root.py (+15/-11)
src/lazr/restful/example/wsgi/run.py (+4/-2)
src/lazr/restful/example/wsgi/tests/test_integration.py (+16/-13)
src/lazr/restful/fields.py (+11/-7)
src/lazr/restful/frameworks/django.py (+23/-15)
src/lazr/restful/interface.py (+9/-10)
src/lazr/restful/interfaces/__init__.py (+1/-0)
src/lazr/restful/interfaces/_fields.py (+6/-5)
src/lazr/restful/interfaces/_rest.py (+133/-95)
src/lazr/restful/jsoncache.py (+8/-11)
src/lazr/restful/marshallers.py (+157/-106)
src/lazr/restful/metazcml.py (+235/-135)
src/lazr/restful/publisher.py (+53/-37)
src/lazr/restful/security.py (+5/-4)
src/lazr/restful/simple.py (+91/-58)
src/lazr/restful/tales.py (+185/-114)
src/lazr/restful/testing/event.py (+1/-2)
src/lazr/restful/testing/helpers.py (+10/-7)
src/lazr/restful/testing/tales.py (+3/-2)
src/lazr/restful/testing/webservice.py (+240/-178)
src/lazr/restful/tests/test_declarations.py (+471/-291)
src/lazr/restful/tests/test_docs.py (+32/-17)
src/lazr/restful/tests/test_error.py (+49/-40)
src/lazr/restful/tests/test_etag.py (+86/-77)
src/lazr/restful/tests/test_navigation.py (+30/-22)
src/lazr/restful/tests/test_utils.py (+72/-61)
src/lazr/restful/tests/test_webservice.py (+316/-203)
src/lazr/restful/utils.py (+63/-48)
src/lazr/restful/wsgi.py (+15/-9)
tox.ini (+1/-19)
Reviewer Review Type Date Requested Status
Guruprasad Approve
LAZR Developers Pending
Review via email: mp+411608@code.launchpad.net

Commit message

Apply black code formatter

To post a comment you must log in.
Revision history for this message
Guruprasad (lgp171188) wrote :

I read the diff, tested the changes locally by running the unit tests on Python 3.9. The tests passed. This looks good to me. 👍

review: Approve
Revision history for this message
Jürgen Gmach (jugmac00) wrote :

Thanks for the review!

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..04ed7f8
4--- /dev/null
5+++ b/.git-blame-ignore-revs
6@@ -0,0 +1,2 @@
7+# apply black
8+2605ef4e4b85988957963224e51f03ce7778405d
9diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
10index d368f14..c8c0353 100644
11--- a/.pre-commit-config.yaml
12+++ b/.pre-commit-config.yaml
13@@ -2,7 +2,7 @@
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@@ -10,7 +10,7 @@ repos:
23 - id: check-yaml
24 - id: debug-statements
25 - repo: https://github.com/PyCQA/flake8
26- rev: 3.9.2
27+ rev: 4.0.1
28 hooks:
29 - id: flake8
30 - repo: https://github.com/get-woke/woke
31@@ -19,3 +19,7 @@ repos:
32 - id: woke-from-source
33 # NEWS.rst mentions API changes
34 exclude: NEWS.rst
35+- repo: https://github.com/psf/black
36+ rev: 21.10b0
37+ hooks:
38+ - id: black
39diff --git a/NEWS.rst b/NEWS.rst
40index 3b8f8f3..74ee44e 100644
41--- a/NEWS.rst
42+++ b/NEWS.rst
43@@ -18,6 +18,7 @@ NEWS for lazr.restful
44 => ``lazr.restful.testing.webservice.StubAbsoluteURL``,
45 ``lazr.restful.testing.webservice.DummyRootResourceURL``
46 => ``lazr.restful.testing.webservice.StubRootResourceURL``
47+- Apply black code formatter via pre-commit.
48
49 1.1.0 (2021-10-07)
50 ==================
51diff --git a/setup.py b/setup.py
52index 4fc49fc..71f4fd8 100755
53--- a/setup.py
54+++ b/setup.py
55@@ -23,69 +23,69 @@ from setuptools import setup, find_packages
56 def generate(*docname_or_string):
57 res = []
58 for value in docname_or_string:
59- if value.endswith('.rst'):
60+ if value.endswith(".rst"):
61 f = open(value)
62- value = f.read().split('..\n end-pypi', 1)[0]
63+ value = f.read().split("..\n end-pypi", 1)[0]
64 f.close()
65 res.append(value)
66- if not value.endswith('\n'):
67- res.append('')
68- return '\n'.join(res)
69+ if not value.endswith("\n"):
70+ res.append("")
71+ return "\n".join(res)
72+
73+
74 # end generic helpers
75
76
77 setup(
78- name='lazr.restful',
79- version='1.1.0',
80- namespace_packages=['lazr'],
81- packages=find_packages('src'),
82- package_dir={'':'src'},
83+ name="lazr.restful",
84+ version="1.1.0",
85+ namespace_packages=["lazr"],
86+ packages=find_packages("src"),
87+ package_dir={"": "src"},
88 include_package_data=True,
89 zip_safe=False,
90- maintainer='LAZR Developers',
91- maintainer_email='lazr-developers@lists.launchpad.net',
92- description=open('README.rst').readline().strip(),
93- long_description=generate(
94- 'src/lazr/restful/docs/index.rst',
95- 'NEWS.rst'),
96- license='LGPL v3',
97+ maintainer="LAZR Developers",
98+ maintainer_email="lazr-developers@lists.launchpad.net",
99+ description=open("README.rst").readline().strip(),
100+ long_description=generate("src/lazr/restful/docs/index.rst", "NEWS.rst"),
101+ license="LGPL v3",
102 install_requires=[
103- 'docutils>=0.3.9',
104- 'grokcore.component>=1.6',
105+ "docutils>=0.3.9",
106+ "grokcore.component>=1.6",
107 'importlib-metadata; python_version < "3.8"',
108- 'lazr.batchnavigator>=1.2.0-dev',
109- 'lazr.delegates>=2.0.3',
110- 'lazr.enum',
111- 'lazr.lifecycle',
112- 'lazr.uri',
113- 'martian>=0.11',
114- 'pytz',
115- 'setuptools',
116- 'simplejson>=2.1.0',
117- 'six>=1.13.0',
118- 'testtools',
119- 'van.testing',
120+ "lazr.batchnavigator>=1.2.0-dev",
121+ "lazr.delegates>=2.0.3",
122+ "lazr.enum",
123+ "lazr.lifecycle",
124+ "lazr.uri",
125+ "martian>=0.11",
126+ "pytz",
127+ "setuptools",
128+ "simplejson>=2.1.0",
129+ "six>=1.13.0",
130+ "testtools",
131+ "van.testing",
132 'wsgiref; python_version < "3"',
133- 'zope.component [zcml]',
134- 'zope.configuration',
135- 'zope.datetime',
136- 'zope.event',
137- 'zope.interface>=3.8.0',
138- 'zope.pagetemplate',
139- 'zope.processlifetime',
140- 'zope.proxy',
141+ "zope.component [zcml]",
142+ "zope.configuration",
143+ "zope.datetime",
144+ "zope.event",
145+ "zope.interface>=3.8.0",
146+ "zope.pagetemplate",
147+ "zope.processlifetime",
148+ "zope.proxy",
149 'zope.publisher; python_version < "3"',
150 'zope.publisher>=6.0.0; python_version >= "3"',
151- 'zope.schema',
152- 'zope.security',
153- 'zope.traversing',
154- ],
155- url='https://launchpad.net/lazr.restful',
156- download_url= 'https://launchpad.net/lazr.restful/+download',
157+ "zope.schema",
158+ "zope.security",
159+ "zope.traversing",
160+ ],
161+ url="https://launchpad.net/lazr.restful",
162+ download_url="https://launchpad.net/lazr.restful/+download",
163 classifiers=[
164 "Development Status :: 5 - Production/Stable",
165 "Intended Audience :: Developers",
166- "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
167+ "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", # noqa: E501
168 "Operating System :: OS Independent",
169 "Programming Language :: Python",
170 "Programming Language :: Python :: 2",
171@@ -97,11 +97,13 @@ setup(
172 "Programming Language :: Python :: 3.8",
173 "Programming Language :: Python :: 3.9",
174 "Programming Language :: Python :: 3.10",
175- ],
176+ ],
177 extras_require=dict(
178- docs=['Sphinx'],
179- test=['zope.testing>=4.6.0'],
180- xml=['lxml',] # requiring this of normal users is too much
181+ docs=["Sphinx"],
182+ test=["zope.testing>=4.6.0"],
183+ xml=[
184+ "lxml",
185+ ], # requiring this of normal users is too much
186 ),
187- test_suite='lazr.restful.tests',
188- )
189+ test_suite="lazr.restful.tests",
190+)
191diff --git a/src/lazr/__init__.py b/src/lazr/__init__.py
192index 20416d1..465f504 100644
193--- a/src/lazr/__init__.py
194+++ b/src/lazr/__init__.py
195@@ -17,7 +17,9 @@
196 # this is a namespace package
197 try:
198 import pkg_resources
199+
200 pkg_resources.declare_namespace(__name__)
201 except ImportError:
202 import pkgutil
203+
204 __path__ = pkgutil.extend_path(__path__, __name__)
205diff --git a/src/lazr/restful/__init__.py b/src/lazr/restful/__init__.py
206index 54321dc..25235cd 100644
207--- a/src/lazr/restful/__init__.py
208+++ b/src/lazr/restful/__init__.py
209@@ -38,6 +38,7 @@ try:
210 from lazr.restful._operation import __all__ as _operation_all
211 from lazr.restful._resource import * # noqa: F401, F403
212 from lazr.restful._resource import __all__ as _resource_all
213+
214 __all__ = []
215 __all__.extend(_bytestorage_all)
216 __all__.extend(_operation_all)
217diff --git a/src/lazr/restful/_bytestorage.py b/src/lazr/restful/_bytestorage.py
218index 382e2d9..7d6c6a0 100644
219--- a/src/lazr/restful/_bytestorage.py
220+++ b/src/lazr/restful/_bytestorage.py
221@@ -20,8 +20,8 @@ from __future__ import absolute_import, print_function
222
223 __metaclass__ = type
224 __all__ = [
225- 'ByteStorageResource',
226- ]
227+ "ByteStorageResource",
228+]
229
230 from zope.interface import implementer
231 from zope.publisher.interfaces import NotFound
232@@ -44,13 +44,15 @@ class ByteStorageResource(HTTPResource):
233 # Read-only resources only support GET.
234 allow_string = "GET"
235 elif self.request.method == "PUT":
236- type = self.request.headers['Content-Type']
237+ type = self.request.headers["Content-Type"]
238 disposition, params = self._parseContentDispositionHeader(
239- self.request.headers['Content-Disposition'])
240+ self.request.headers["Content-Disposition"]
241+ )
242 cache_stream = self.request.bodyStream.getCacheStream()
243 representation = cache_stream.read()
244- return self.do_PUT(type, representation,
245- params.get('filename'))
246+ return self.do_PUT(
247+ type, representation, params.get("filename")
248+ )
249 elif self.request.method == "DELETE":
250 return self.do_DELETE()
251 else:
252@@ -64,20 +66,20 @@ class ByteStorageResource(HTTPResource):
253 media_type = self.getPreferredSupportedContentType()
254 if media_type in [self.WADL_TYPE, self.DEPRECATED_WADL_TYPE]:
255 result = self.toWADL().encode("utf-8")
256- self.request.response.setHeader('Content-Type', media_type)
257+ self.request.response.setHeader("Content-Type", media_type)
258 return result
259 if not self.context.is_stored:
260 # No stored document exists here yet.
261 raise NotFound(self.context, self.context.filename, self.request)
262- self.request.response.setStatus(303) # See Other
263- self.request.response.setHeader('Location', self.context.alias_url)
264+ self.request.response.setStatus(303) # See Other
265+ self.request.response.setHeader("Location", self.context.alias_url)
266
267 def do_PUT(self, type, representation, filename):
268 """See `IByteStorageResource`."""
269 try:
270 self.context.field.validate(representation)
271 except ValidationError as e:
272- self.request.response.setStatus(400) # Bad Request
273+ self.request.response.setStatus(400) # Bad Request
274 return str(e)
275 self.context.createStored(type, representation, filename)
276
277diff --git a/src/lazr/restful/_operation.py b/src/lazr/restful/_operation.py
278index fc7c037..af48e48 100644
279--- a/src/lazr/restful/_operation.py
280+++ b/src/lazr/restful/_operation.py
281@@ -13,36 +13,48 @@ from zope.interface import Attribute, implementer, providedBy
282 from zope.interface.interfaces import IInterface
283 from zope.schema import Field
284 from zope.schema.interfaces import (
285- IField, RequiredMissing, ValidationError, WrongType)
286+ IField,
287+ RequiredMissing,
288+ ValidationError,
289+ WrongType,
290+)
291 from zope.security.proxy import isinstance as zope_isinstance
292
293 from lazr.lifecycle.event import ObjectModifiedEvent
294 from lazr.lifecycle.snapshot import Snapshot
295
296 from lazr.restful.interfaces import (
297- ICollection, IFieldMarshaller, IResourceDELETEOperation,
298- IResourceGETOperation, IResourcePOSTOperation, IWebServiceConfiguration)
299+ ICollection,
300+ IFieldMarshaller,
301+ IResourceDELETEOperation,
302+ IResourceGETOperation,
303+ IResourcePOSTOperation,
304+ IWebServiceConfiguration,
305+)
306 from lazr.restful.interfaces import ICollectionField, IReference
307 from lazr.restful.utils import is_total_size_link_active
308 from lazr.restful._resource import (
309- BatchingResourceMixin, CollectionResource, ResourceJSONEncoder)
310+ BatchingResourceMixin,
311+ CollectionResource,
312+ ResourceJSONEncoder,
313+)
314
315
316 __metaclass__ = type
317 __all__ = [
318- 'IObjectLink',
319- 'ObjectLink',
320- 'ResourceOperation',
321- 'ResourceGETOperation',
322- 'ResourceDELETEOperation',
323- 'ResourcePOSTOperation'
324+ "IObjectLink",
325+ "ObjectLink",
326+ "ResourceOperation",
327+ "ResourceGETOperation",
328+ "ResourceDELETEOperation",
329+ "ResourcePOSTOperation",
330 ]
331
332
333 class ResourceOperation(BatchingResourceMixin):
334 """A one-off operation associated with a resource."""
335
336- JSON_TYPE = 'application/json'
337+ JSON_TYPE = "application/json"
338 send_modification_event = False
339
340 def __init__(self, context, request):
341@@ -67,20 +79,21 @@ class ResourceOperation(BatchingResourceMixin):
342 return None
343 base = str(self.request.URL)
344 query = navigator.getCleanQueryString()
345- if query != '':
346- query += '&'
347- return base + '?' + query + "ws.show=total_size"
348+ if query != "":
349+ query += "&"
350+ return base + "?" + query + "ws.show=total_size"
351
352 def __call__(self):
353 values, errors = self.validate()
354 if len(errors) > 0:
355 self.request.response.setStatus(400)
356- self.request.response.setHeader('Content-type', 'text/plain')
357+ self.request.response.setHeader("Content-type", "text/plain")
358 return "\n".join(errors)
359
360 if self.send_modification_event:
361 snapshot = Snapshot(
362- self.context, providing=providedBy(self.context))
363+ self.context, providing=providedBy(self.context)
364+ )
365
366 response = self.call(**values)
367
368@@ -88,7 +101,8 @@ class ResourceOperation(BatchingResourceMixin):
369 event = ObjectModifiedEvent(
370 object=self.context,
371 object_before_modification=snapshot,
372- edited_fields=None)
373+ edited_fields=None,
374+ )
375 notify(event)
376 return self.encodeResult(response)
377
378@@ -100,8 +114,10 @@ class ResourceOperation(BatchingResourceMixin):
379 responsible for setting the Content-Type header and the status
380 code.
381 """
382- if (self.request.response.getHeader('Content-Type') is not None
383- or self.request.response.getStatus() != 599):
384+ if (
385+ self.request.response.getHeader("Content-Type") is not None
386+ or self.request.response.getStatus() != 599
387+ ):
388 # The operation took care of everything and just needs
389 # this object served to the client.
390 return result
391@@ -116,23 +132,24 @@ class ResourceOperation(BatchingResourceMixin):
392 if self.total_size_only:
393 result = resource.get_total_size(collection)
394 else:
395- result = resource.batch() + '}'
396+ result = resource.batch() + "}"
397 elif self.should_batch(result):
398 if self.total_size_only:
399 result = self.get_total_size(result)
400 else:
401- result = self.batch(result, self.request) + '}'
402+ result = self.batch(result, self.request) + "}"
403 else:
404 # Serialize the result to JSON. Any embedded entries will be
405 # automatically serialized.
406 try:
407 result = simplejson.dumps(result, cls=ResourceJSONEncoder)
408 except TypeError:
409- raise TypeError("Could not serialize object %s to JSON." %
410- result)
411+ raise TypeError(
412+ "Could not serialize object %s to JSON." % result
413+ )
414
415 self.request.response.setStatus(200)
416- self.request.response.setHeader('Content-Type', self.JSON_TYPE)
417+ self.request.response.setHeader("Content-Type", self.JSON_TYPE)
418 return result
419
420 def should_batch(self, result):
421@@ -147,7 +164,8 @@ class ResourceOperation(BatchingResourceMixin):
422 return True
423
424 if zope_isinstance(
425- result, (bytes, six.text_type, dict, set, list, tuple)):
426+ result, (bytes, six.text_type, dict, set, list, tuple)
427+ ):
428 # Ordinary Python data structures generally are not
429 # batched.
430 return False
431@@ -184,15 +202,19 @@ class ResourceOperation(BatchingResourceMixin):
432 for field in self.params:
433 name = field.__name__
434 field = field.bind(self.context)
435- if (self.request.get(name, missing) is missing
436- and not field.required):
437+ if (
438+ self.request.get(name, missing) is missing
439+ and not field.required
440+ ):
441 value = field.default
442 else:
443 marshaller = getMultiAdapter(
444- (field, self.request), IFieldMarshaller)
445+ (field, self.request), IFieldMarshaller
446+ )
447 try:
448 value = marshaller.marshall_from_request(
449- self.request.form.get(name))
450+ self.request.form.get(name)
451+ )
452 except ValueError as e:
453 errors.append(u"%s: %s" % (name, e))
454 continue
455@@ -229,8 +251,9 @@ class ResourcePOSTOperation(ResourceOperation):
456 class IObjectLink(IField):
457 """Field containing a link to an object."""
458
459- schema = Attribute("schema",
460- u"The Interface of the Object on the other end of the link.")
461+ schema = Attribute(
462+ "schema", u"The Interface of the Object on the other end of the link."
463+ )
464
465
466 @implementer(IObjectLink)
467diff --git a/src/lazr/restful/_resource.py b/src/lazr/restful/_resource.py
468index 0b411e8..f720b30 100644
469--- a/src/lazr/restful/_resource.py
470+++ b/src/lazr/restful/_resource.py
471@@ -7,28 +7,28 @@ from __future__ import absolute_import, division, print_function
472 __metaclass__ = type
473
474 __all__ = [
475- 'BatchingResourceMixin',
476- 'Collection',
477- 'CollectionResource',
478- 'Entry',
479- 'EntryAdapterUtility',
480- 'EntryField',
481- 'EntryFieldResource',
482- 'EntryHTMLView',
483- 'EntryResource',
484- 'HTTPResource',
485- 'JSONDate',
486- 'JSONItem',
487- 'ReadOnlyResource',
488- 'RedirectResource',
489- 'register_versioned_request_utility',
490- 'render_field_to_html',
491- 'ResourceJSONEncoder',
492- 'RESTUtilityBase',
493- 'ScopedCollection',
494- 'ServiceRootResource',
495- 'WADL_SCHEMA_FILE',
496- ]
497+ "BatchingResourceMixin",
498+ "Collection",
499+ "CollectionResource",
500+ "Entry",
501+ "EntryAdapterUtility",
502+ "EntryField",
503+ "EntryFieldResource",
504+ "EntryHTMLView",
505+ "EntryResource",
506+ "HTTPResource",
507+ "JSONDate",
508+ "JSONItem",
509+ "ReadOnlyResource",
510+ "RedirectResource",
511+ "register_versioned_request_utility",
512+ "render_field_to_html",
513+ "ResourceJSONEncoder",
514+ "RESTUtilityBase",
515+ "ScopedCollection",
516+ "ServiceRootResource",
517+ "WADL_SCHEMA_FILE",
518+]
519
520 from collections import OrderedDict
521 from email.utils import formatdate
522@@ -43,9 +43,11 @@ import time
523 # Import SHA in a way compatible with both Python 2.4 and Python 2.6.
524 try:
525 import hashlib
526+
527 sha_constructor = hashlib.sha1
528 except ImportError:
529 import sha
530+
531 sha_constructor = sha.new
532
533 try:
534@@ -63,7 +65,7 @@ from zope.component import (
535 getSiteManager,
536 getUtility,
537 queryMultiAdapter,
538- )
539+)
540 from zope.event import notify
541 from zope.interface import (
542 alsoProvides,
543@@ -71,13 +73,13 @@ from zope.interface import (
544 implementedBy,
545 providedBy,
546 Interface,
547- )
548+)
549 from zope.interface.common.idatetime import IDate
550 from zope.interface.common.sequence import IFiniteSequence
551 from zope.interface.interfaces import (
552 ComponentLookupError,
553 IInterface,
554- )
555+)
556 from zope.location.interfaces import ILocation
557 from zope.pagetemplate.engine import TrustedAppPT
558 from zope.pagetemplate.pagetemplatefile import PageTemplateFile
559@@ -87,7 +89,11 @@ from zope.publisher.interfaces import NotFound
560 from zope.publisher.interfaces.http import IHTTPRequest
561 from zope.schema import ValidationError, getFieldsInOrder
562 from zope.schema.interfaces import (
563- ConstraintNotSatisfied, IBytes, IField, RequiredMissing)
564+ ConstraintNotSatisfied,
565+ IBytes,
566+ IField,
567+ RequiredMissing,
568+)
569 from zope.security.interfaces import Unauthorized
570 from zope.security.proxy import getChecker, removeSecurityProxy
571 from zope.security.management import checkPermission
572@@ -127,19 +133,18 @@ from lazr.restful.interfaces import (
573 IWebServiceLayer,
574 IWebServiceVersion,
575 LAZR_WEBSERVICE_NAME,
576- )
577+)
578 from lazr.restful.marshallers import URLDereferencingMixin
579 from lazr.restful.utils import (
580 extract_write_portion,
581 get_current_web_service_request,
582 parse_accept_style_header,
583 sorted_named_things,
584- )
585+)
586
587
588 # The path to the WADL XML Schema definition.
589-WADL_SCHEMA_FILE = os.path.join(os.path.dirname(__file__),
590- 'wadl20061109.xsd')
591+WADL_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), "wadl20061109.xsd")
592
593 # Constants and levels of detail to use when unmarshalling the data.
594 MISSING = object()
595@@ -149,7 +154,7 @@ CLOSEUP_DETAIL = object()
596 # XXX leonardr 2009-01-29
597 # bug=https://bugs.edge.launchpad.net/zope3/+bug/322486:
598 # Add nonstandard status methods to Zope's status_reasons dictionary.
599-for code, reason in [(209, 'Content Returned')]:
600+for code, reason in [(209, "Content Returned")]:
601 if code not in status_reasons:
602 status_reasons[code] = reason
603 init_status_codes()
604@@ -188,7 +193,8 @@ def register_versioned_request_utility(interface, version):
605 """
606 alsoProvides(interface, IWebServiceVersion)
607 getSiteManager().registerUtility(
608- interface, IWebServiceVersion, name=version)
609+ interface, IWebServiceVersion, name=version
610+ )
611
612
613 class LazrPageTemplateFile(TrustedAppPT, PageTemplateFile):
614@@ -205,7 +211,7 @@ class ResourceJSONEncoder(simplejson.encoder.JSONEncoderForHTML):
615 """
616
617 def __init__(self, *args, **kwargs):
618- self.media_type = kwargs.pop('media_type', HTTPResource.JSON_TYPE)
619+ self.media_type = kwargs.pop("media_type", HTTPResource.JSON_TYPE)
620 super(ResourceJSONEncoder, self).__init__(*args, **kwargs)
621
622 def default(self, obj):
623@@ -280,18 +286,18 @@ class HTTPResource:
624 """See `IHTTPResource`."""
625
626 # Some interesting media types.
627- WADL_TYPE = 'application/vnd.sun.wadl+xml'
628- JSON_TYPE = 'application/json'
629- JSON_PLUS_XHTML_TYPE = 'application/json;include=lp_html'
630- XHTML_TYPE = 'application/xhtml+xml'
631+ WADL_TYPE = "application/vnd.sun.wadl+xml"
632+ JSON_TYPE = "application/json"
633+ JSON_PLUS_XHTML_TYPE = "application/json;include=lp_html"
634+ XHTML_TYPE = "application/xhtml+xml"
635
636 # This misspelling of the WADL media type was used for a while,
637 # and lazr.restful still supports it to avoid breaking clients
638 # that depend on it.
639- DEPRECATED_WADL_TYPE = 'application/vd.sun.wadl+xml'
640+ DEPRECATED_WADL_TYPE = "application/vd.sun.wadl+xml"
641
642 # A preparsed template file for WADL representations of resources.
643- WADL_TEMPLATE = LazrPageTemplateFile('templates/wadl-resource.pt')
644+ WADL_TEMPLATE = LazrPageTemplateFile("templates/wadl-resource.pt")
645
646 # All resources serve WADL and JSON representations. Only entry
647 # resources serve XHTML representations and JSON+XHTML representations.
648@@ -316,8 +322,8 @@ class HTTPResource:
649 """
650 if request is None:
651 request = self.request
652- override = request.headers.get('X-HTTP-Method-Override')
653- if override is not None and request.method == 'POST':
654+ override = request.headers.get("X-HTTP-Method-Override")
655+ if override is not None and request.method == "POST":
656 # POST is the only HTTP method for which we respect
657 # X-HTTP-Method-Override.
658 return override
659@@ -335,12 +341,12 @@ class HTTPResource:
660 incoming ETag matched the generated ETag and there is no
661 need to serve anything else.
662 """
663- incoming_etags = self._parseETags('If-None-Match')
664+ incoming_etags = self._parseETags("If-None-Match")
665
666 media_type = self.getPreferredSupportedContentType()
667 existing_etag = self.getETag(media_type)
668 if existing_etag is not None:
669- self.request.response.setHeader('ETag', existing_etag)
670+ self.request.response.setHeader("ETag", existing_etag)
671 if existing_etag in incoming_etags:
672 # The client already has this representation.
673 # No need to send it again.
674@@ -368,9 +374,9 @@ class HTTPResource:
675 generated ETag and the incoming representation should be
676 ignored.
677 """
678- media_type = self.request.headers.get('X-Content-Type-Override')
679+ media_type = self.request.headers.get("X-Content-Type-Override")
680 if media_type is not None:
681- if self.request.method != 'POST':
682+ if self.request.method != "POST":
683 # X-C-T-O should not be used unless the underlying
684 # method is POST. Set response code 400 ("Bad
685 # Request").
686@@ -378,9 +384,10 @@ class HTTPResource:
687 return None
688 else:
689 media_type = self.request.headers.get(
690- 'Content-Type', self.JSON_TYPE)
691+ "Content-Type", self.JSON_TYPE
692+ )
693
694- incoming_etags = self._parseETags('If-Match')
695+ incoming_etags = self._parseETags("If-Match")
696 if len(incoming_etags) == 0:
697 # This is not a conditional write.
698 return media_type
699@@ -459,16 +466,19 @@ class HTTPResource:
700 custom POST operations.
701 """
702 adapters = list(
703- getAdapters((self.context, self.request), IResourcePOSTOperation))
704+ getAdapters((self.context, self.request), IResourcePOSTOperation)
705+ )
706 return len(adapters) > 0
707
708 def implementsDELETE(self):
709 """Returns True if this resource will respond to DELETE."""
710 adapters = list(
711- getAdapters(
712- (self.context, self.request), IResourceDELETEOperation))
713- assert len(adapters) < 2, ("%s has more than one registered DELETE "
714- "handler." % self.context.__class__)
715+ getAdapters((self.context, self.request), IResourceDELETEOperation)
716+ )
717+ assert len(adapters) < 2, (
718+ "%s has more than one registered DELETE "
719+ "handler." % self.context.__class__
720+ )
721 return len(adapters) > 0
722
723 def toWADL(self):
724@@ -477,7 +487,7 @@ class HTTPResource:
725 The WADL document describes the capabilities of this resource.
726 """
727 namespace = self.WADL_TEMPLATE.pt_getContext()
728- namespace['context'] = self
729+ namespace["context"] = self
730 return self.WADL_TEMPLATE.pt_render(namespace)
731
732 def getPreferredSupportedContentType(self):
733@@ -504,8 +514,9 @@ class HTTPResource:
734
735 def getPreferredContentTypes(self):
736 """Find which content types the client prefers to receive."""
737- accept_header = (self.request.form.pop('ws.accept', None)
738- or self.request.get('HTTP_ACCEPT'))
739+ accept_header = self.request.form.pop(
740+ "ws.accept", None
741+ ) or self.request.get("HTTP_ACCEPT")
742 return parse_accept_style_header(accept_header)
743
744 def _parseETags(self, header_name):
745@@ -522,15 +533,16 @@ class HTTPResource:
746 return []
747 utility = getUtility(IWebServiceConfiguration)
748 strip_gzip_from_etag = (
749- utility.compensate_for_mod_compress_etag_modification)
750+ utility.compensate_for_mod_compress_etag_modification
751+ )
752 etags = []
753 # We're kind of cheating, because entity tags can technically
754 # have commas in them, but none of our tags contain commas, so
755 # this will work.
756- for etag in header.split(','):
757+ for etag in header.split(","):
758 etag = etag.strip()
759 if strip_gzip_from_etag:
760- if etag.endswith('-gzip'):
761+ if etag.endswith("-gzip"):
762 etag = etag[:-5]
763 elif etag.endswith('-gzip"'):
764 etag = etag[:-6] + '"'
765@@ -547,11 +559,11 @@ class HTTPResource:
766 params = {}
767 if value is None:
768 return (disposition, params)
769- pieces = value.split(';')
770+ pieces = value.split(";")
771 if len(pieces) > 1:
772 disposition = pieces[0].strip()
773 for name_value in pieces[1:]:
774- name_and_value = name_value.split('=', 2)
775+ name_and_value = name_value.split("=", 2)
776 if len(name_and_value) == 2:
777 name = name_and_value[0].strip()
778 value = name_and_value[1].strip()
779@@ -560,7 +572,7 @@ class HTTPResource:
780 # very likely that a client will quote even short
781 # filenames, and unlikely that a filename will
782 # actually begin and end with quotes.
783- if (value[0] == '"' and value[-1] == '"'):
784+ if value[0] == '"' and value[-1] == '"':
785 value = value[1:-1]
786 else:
787 name = name_and_value
788@@ -612,7 +624,7 @@ class BatchingResourceMixin:
789
790 :return: a JSON string representing the number of objects in the list
791 """
792- if not hasattr(entries, '__len__'):
793+ if not hasattr(entries, "__len__"):
794 entries = IFiniteSequence(entries)
795
796 return simplejson.dumps(len(entries))
797@@ -639,12 +651,12 @@ class BatchingResourceMixin:
798 keys to the JSON hash. It's the caller's responsibility to add
799 a '}' to the end of the string returned from this method.
800 """
801- if not hasattr(entries, '__len__'):
802+ if not hasattr(entries, "__len__"):
803 entries = IFiniteSequence(entries)
804 navigator = WebServiceBatchNavigator(entries, request)
805
806 view_permission = getUtility(IWebServiceConfiguration).view_permission
807- batch = {'start': navigator.batch.start}
808+ batch = {"start": navigator.batch.start}
809 # If this is the last batch, then total() is easy to
810 # calculate. Let's use it and save the client from having to
811 # ask for the total size of the collection.
812@@ -658,27 +670,33 @@ class BatchingResourceMixin:
813 # lazr.batchnavigator uses -1 for an empty list.
814 # We want to use 0.
815 total_size = 0
816- batch['total_size'] = total_size
817+ batch["total_size"] = total_size
818 else:
819- batch['total_size_link'] = total_size_link
820+ batch["total_size_link"] = total_size_link
821 next_url = navigator.nextBatchURL()
822 if next_url != "":
823- batch['next_collection_link'] = next_url
824+ batch["next_collection_link"] = next_url
825 prev_url = navigator.prevBatchURL()
826 if prev_url != "":
827- batch['prev_collection_link'] = prev_url
828+ batch["prev_collection_link"] = prev_url
829 json_string = simplejson.dumps(batch, cls=ResourceJSONEncoder)
830
831 # String together a bunch of entry representations, possibly
832 # obtained from a representation cache.
833- resources = [EntryResource(entry, request)
834- for entry in navigator.batch
835- if checkPermission(view_permission, entry)]
836+ resources = [
837+ EntryResource(entry, request)
838+ for entry in navigator.batch
839+ if checkPermission(view_permission, entry)
840+ ]
841 entry_strings = [
842 resource._representation(HTTPResource.JSON_TYPE)
843- for resource in resources]
844- json_string = (json_string[:-1] + ', "entries": ['
845- + (", ".join(entry_strings) + ']'))
846+ for resource in resources
847+ ]
848+ json_string = (
849+ json_string[:-1]
850+ + ', "entries": ['
851+ + (", ".join(entry_strings) + "]")
852+ )
853 # The caller is responsible for tacking on the final curly brace.
854 return json_string
855
856@@ -709,15 +727,17 @@ class CustomOperationResourceMixin:
857 return "Expected a single operation: %r" % (operation_name,)
858
859 try:
860- operation = getMultiAdapter((self.context, self.request),
861- IResourceGETOperation,
862- name=operation_name)
863+ operation = getMultiAdapter(
864+ (self.context, self.request),
865+ IResourceGETOperation,
866+ name=operation_name,
867+ )
868 except ComponentLookupError:
869 self.request.response.setStatus(400)
870 return "No such operation: " + operation_name
871
872- show = self.request.form.get('ws.show')
873- if show == 'total_size':
874+ show = self.request.form.get("ws.show")
875+ if show == "total_size":
876 operation.total_size_only = True
877 return operation()
878
879@@ -735,9 +755,11 @@ class CustomOperationResourceMixin:
880 return "Expected a single operation: %r" % (operation_name,)
881
882 try:
883- operation = getMultiAdapter((self.context, self.request),
884- IResourcePOSTOperation,
885- name=operation_name)
886+ operation = getMultiAdapter(
887+ (self.context, self.request),
888+ IResourcePOSTOperation,
889+ name=operation_name,
890+ )
891 except ComponentLookupError:
892 self.request.response.setStatus(400)
893 return "No such operation: " + operation_name
894@@ -752,18 +774,19 @@ class CustomOperationResourceMixin:
895 should eventually go into CollectionResource that implements
896 POST to create a new entry inside the collection.
897 """
898- operation_name = self.request.form.get('ws.op')
899+ operation_name = self.request.form.get("ws.op")
900 if operation_name is None:
901 self.request.response.setStatus(400)
902 return "No operation name given."
903- del self.request.form['ws.op']
904+ del self.request.form["ws.op"]
905 return self.handleCustomPOST(operation_name)
906
907 def do_DELETE(self):
908 """Invoke a destructor operation."""
909 try:
910- operation = getMultiAdapter((self.context, self.request),
911- IResourceDELETEOperation)
912+ operation = getMultiAdapter(
913+ (self.context, self.request), IResourceDELETEOperation
914+ )
915 except ComponentLookupError:
916 # This should never happen because implementsDELETE()
917 # makes sure there is a registered
918@@ -779,7 +802,7 @@ class FieldUnmarshallerMixin:
919
920 # The representation value used when the client doesn't have
921 # authorization to see the real value.
922- REDACTED_VALUE = 'tag:launchpad.net:2008:redacted'
923+ REDACTED_VALUE = "tag:launchpad.net:2008:redacted"
924
925 def __init__(self, context, request):
926 """A basic constructor."""
927@@ -798,7 +821,8 @@ class FieldUnmarshallerMixin:
928 cached_value = MISSING
929 if detail is NORMAL_DETAIL:
930 cached_value = self._unmarshalled_field_cache.get(
931- field_name, MISSING)
932+ field_name, MISSING
933+ )
934 if cached_value is not MISSING:
935 return cached_value[0]
936
937@@ -815,7 +839,8 @@ class FieldUnmarshallerMixin:
938 repr_value = marshaller.unmarshall(self.entry, value)
939 elif detail is CLOSEUP_DETAIL:
940 repr_value = marshaller.unmarshall_to_closeup(
941- self.entry, value)
942+ self.entry, value
943+ )
944 else:
945 raise AssertionError("Invalid level of detail specified")
946 except Unauthorized:
947@@ -832,7 +857,9 @@ class FieldUnmarshallerMixin:
948
949 if detail is NORMAL_DETAIL:
950 self._unmarshalled_field_cache[field_name] = (
951- unmarshalled, (field, value, flag))
952+ unmarshalled,
953+ (field, value, flag),
954+ )
955 return unmarshalled
956
957 def _field_with_html_renderer(self, field_name, field):
958@@ -848,14 +875,16 @@ class FieldUnmarshallerMixin:
959 # Try to get a renderer for this particular field.
960 renderer = getMultiAdapter(
961 (self.entry.context, field, self.request),
962- IFieldHTMLRenderer, name=field.__name__)
963+ IFieldHTMLRenderer,
964+ name=field.__name__,
965+ )
966 except ComponentLookupError:
967 # There's no field-specific renderer. Look up an
968 # IFieldHTMLRenderer for this _type_ of field.
969 field = field.bind(self.entry.context)
970 renderer = getMultiAdapter(
971- (self.entry.context, field, self.request),
972- IFieldHTMLRenderer)
973+ (self.entry.context, field, self.request), IFieldHTMLRenderer
974+ )
975 return name, value, renderer
976
977 def unmarshallFieldToHTML(self, field_name, field):
978@@ -867,7 +896,8 @@ class FieldUnmarshallerMixin:
979 :return: a 2-tuple (representation_name, representation_value).
980 """
981 name, value, renderer = self._field_with_html_renderer(
982- field_name, field)
983+ field_name, field
984+ )
985 return name, renderer(value)
986
987
988@@ -976,7 +1006,7 @@ class EntryHTMLView:
989 """An HTML view of an entry."""
990
991 # A preparsed template file for HTML representations of the resource.
992- HTML_TEMPLATE = LazrPageTemplateFile('templates/html-resource.pt')
993+ HTML_TEMPLATE = LazrPageTemplateFile("templates/html-resource.pt")
994
995 def __init__(self, context, request):
996 """Initialize with respect to a data object and request."""
997@@ -988,10 +1018,13 @@ class EntryHTMLView:
998 """Send the entry data through an HTML template."""
999 namespace = self.HTML_TEMPLATE.pt_getContext()
1000 names_and_values = self.resource.toDataStructure(
1001- HTTPResource.XHTML_TYPE).items()
1002- data = [{'name': name, 'value': decode_value(value)}
1003- for name, value in names_and_values]
1004- namespace['context'] = sorted(data, key=itemgetter('name'))
1005+ HTTPResource.XHTML_TYPE
1006+ ).items()
1007+ data = [
1008+ {"name": name, "value": decode_value(value)}
1009+ for name, value in names_and_values
1010+ ]
1011+ namespace["context"] = sorted(data, key=itemgetter("name"))
1012 return self.HTML_TEMPLATE.pt_render(namespace)
1013
1014
1015@@ -1008,9 +1041,9 @@ class EntryManipulatingResource(ReadWriteResource):
1016 """Process an incoming representation as a JSON document."""
1017 if not media_type.startswith(self.JSON_TYPE):
1018 self.request.response.setStatus(415)
1019- return None, 'Expected a media type of %s.' % self.JSON_TYPE
1020+ return None, "Expected a media type of %s." % self.JSON_TYPE
1021 try:
1022- h = simplejson.loads(representation.decode('utf-8'))
1023+ h = simplejson.loads(representation.decode("utf-8"))
1024 except ValueError:
1025 self.request.response.setStatus(400)
1026 return None, "Entity-body was not a well-formed JSON document."
1027@@ -1032,48 +1065,51 @@ class EntryManipulatingResource(ReadWriteResource):
1028
1029 # Some fields aren't part of the schema, so they're handled
1030 # separately.
1031- modified_read_only_attribute = (u"%s: You tried to modify a "
1032- "read-only attribute.")
1033- if 'self_link' in changeset:
1034- if changeset['self_link'] != absoluteURL(self.entry.context,
1035- self.request):
1036- errors.append(modified_read_only_attribute % 'self_link')
1037- del changeset['self_link']
1038-
1039- if 'web_link' in changeset:
1040- browser_request = IWebBrowserOriginatingRequest(
1041- self.request, None)
1042+ modified_read_only_attribute = (
1043+ u"%s: You tried to modify a " "read-only attribute."
1044+ )
1045+ if "self_link" in changeset:
1046+ if changeset["self_link"] != absoluteURL(
1047+ self.entry.context, self.request
1048+ ):
1049+ errors.append(modified_read_only_attribute % "self_link")
1050+ del changeset["self_link"]
1051+
1052+ if "web_link" in changeset:
1053+ browser_request = IWebBrowserOriginatingRequest(self.request, None)
1054 if browser_request is not None:
1055 existing_web_link = absoluteURL(
1056- self.entry.context, browser_request)
1057- if changeset['web_link'] != existing_web_link:
1058- errors.append(modified_read_only_attribute % 'web_link')
1059- del changeset['web_link']
1060-
1061- if 'resource_type_link' in changeset:
1062- if changeset['resource_type_link'] != self.type_url:
1063- errors.append(modified_read_only_attribute %
1064- 'resource_type_link')
1065- del changeset['resource_type_link']
1066-
1067- if 'http_etag' in changeset:
1068- if changeset['http_etag'] != self.getETag(media_type):
1069- errors.append(modified_read_only_attribute %
1070- 'http_etag')
1071- del changeset['http_etag']
1072-
1073- if 'lp_html' in changeset:
1074+ self.entry.context, browser_request
1075+ )
1076+ if changeset["web_link"] != existing_web_link:
1077+ errors.append(modified_read_only_attribute % "web_link")
1078+ del changeset["web_link"]
1079+
1080+ if "resource_type_link" in changeset:
1081+ if changeset["resource_type_link"] != self.type_url:
1082+ errors.append(
1083+ modified_read_only_attribute % "resource_type_link"
1084+ )
1085+ del changeset["resource_type_link"]
1086+
1087+ if "http_etag" in changeset:
1088+ if changeset["http_etag"] != self.getETag(media_type):
1089+ errors.append(modified_read_only_attribute % "http_etag")
1090+ del changeset["http_etag"]
1091+
1092+ if "lp_html" in changeset:
1093 if media_type == self.JSON_PLUS_XHTML_TYPE:
1094 # The HTML portion of the representation is read-only,
1095 # so ignore it.
1096- del changeset['lp_html']
1097+ del changeset["lp_html"]
1098
1099 # For every field in the schema, see if there's a corresponding
1100 # field in the changeset.
1101 for name, field in get_entry_fields_in_write_order(self.entry):
1102 field = field.bind(self.entry.context)
1103- marshaller = getMultiAdapter((field, self.request),
1104- IFieldMarshaller)
1105+ marshaller = getMultiAdapter(
1106+ (field, self.request), IFieldMarshaller
1107+ )
1108 repr_name = marshaller.representation_name
1109 if repr_name not in changeset:
1110 # The client didn't try to set a value for this field.
1111@@ -1084,7 +1120,8 @@ class EntryManipulatingResource(ReadWriteResource):
1112 # way to see if the client changed the value.
1113 try:
1114 current_value = marshaller.unmarshall(
1115- self.entry, getattr(self.entry, name))
1116+ self.entry, getattr(self.entry, name)
1117+ )
1118 except Unauthorized:
1119 # The client doesn't have permission to see the old
1120 # value. That doesn't necessarily mean they can't set
1121@@ -1114,8 +1151,10 @@ class EntryManipulatingResource(ReadWriteResource):
1122 # error message if the new value is not identical to the
1123 # current one.
1124 if value != current_value:
1125- errors.append(u"%s: You tried to modify a collection "
1126- "attribute." % repr_name)
1127+ errors.append(
1128+ u"%s: You tried to modify a collection "
1129+ "attribute." % repr_name
1130+ )
1131 continue
1132
1133 if IBytes.providedBy(field):
1134@@ -1126,15 +1165,15 @@ class EntryManipulatingResource(ReadWriteResource):
1135 # storage resource as a str, but
1136 # BytesFieldMarshaller.marshall_from_json_data returns
1137 # bytes.
1138- if value != current_value.encode('UTF-8'):
1139+ if value != current_value.encode("UTF-8"):
1140 if field.readonly:
1141- errors.append(modified_read_only_attribute
1142- % repr_name)
1143+ errors.append(modified_read_only_attribute % repr_name)
1144 else:
1145 errors.append(
1146 u"%s: To modify this field you need to send a PUT "
1147- "request to its URI (%s)."
1148- % (repr_name, current_value))
1149+ "request to its URI (%s)."
1150+ % (repr_name, current_value)
1151+ )
1152 continue
1153
1154 # If the new value is an object, make sure it provides the correct
1155@@ -1146,8 +1185,10 @@ class EntryManipulatingResource(ReadWriteResource):
1156 # possible for Vocabulary fields to specify a schema
1157 # class the way IReference fields can.
1158 if value is not None and not field.schema.providedBy(value):
1159- errors.append(u"%s: Your value points to the "
1160- "wrong kind of object" % repr_name)
1161+ errors.append(
1162+ u"%s: Your value points to the "
1163+ "wrong kind of object" % repr_name
1164+ )
1165 continue
1166
1167 # Obtain the current value of the field. This gives us an easy
1168@@ -1162,8 +1203,7 @@ class EntryManipulatingResource(ReadWriteResource):
1169 if field.readonly:
1170 change_this_field = False
1171 if value != current_value:
1172- errors.append(modified_read_only_attribute
1173- % repr_name)
1174+ errors.append(modified_read_only_attribute % repr_name)
1175 continue
1176
1177 if change_this_field is True and value != current_value:
1178@@ -1175,8 +1215,9 @@ class EntryManipulatingResource(ReadWriteResource):
1179 # the exception; otherwise use a generic message
1180 # instead of whatever object the raise site
1181 # thought would be a good idea.
1182- if (len(e.args) > 0 and
1183- isinstance(e.args[0], six.string_types)):
1184+ if len(e.args) > 0 and isinstance(
1185+ e.args[0], six.string_types
1186+ ):
1187 error = e.args[0]
1188 else:
1189 error = "Constraint not satisfied."
1190@@ -1196,13 +1237,15 @@ class EntryManipulatingResource(ReadWriteResource):
1191 # fields that don't correspond to some field in the
1192 # schema. They're all errors.
1193 for invalid_field in changeset.keys():
1194- errors.append(u"%s: You tried to modify a nonexistent "
1195- "attribute." % invalid_field)
1196+ errors.append(
1197+ u"%s: You tried to modify a nonexistent "
1198+ "attribute." % invalid_field
1199+ )
1200
1201 # If there were errors, display them and send a status of 400.
1202 if len(errors) > 0:
1203 self.request.response.setStatus(400)
1204- self.request.response.setHeader('Content-type', 'text/plain')
1205+ self.request.response.setHeader("Content-type", "text/plain")
1206 return "\n".join(errors)
1207
1208 # Store the entry's current URL so we can see if it changes.
1209@@ -1213,7 +1256,8 @@ class EntryManipulatingResource(ReadWriteResource):
1210
1211 # Make a snapshot of the entry to use in a notification event.
1212 entry_before_modification = Snapshot(
1213- self.entry.context, providing=providedBy(self.entry.context))
1214+ self.entry.context, providing=providedBy(self.entry.context)
1215+ )
1216
1217 # Make the changes, in the same order obtained from
1218 # get_entry_fields_in_write_order.
1219@@ -1227,8 +1271,8 @@ class EntryManipulatingResource(ReadWriteResource):
1220 event = ObjectModifiedEvent(
1221 object=self.entry.context,
1222 object_before_modification=entry_before_modification,
1223- edited_fields=[
1224- field for field, value in validated_changeset])
1225+ edited_fields=[field for field, value in validated_changeset],
1226+ )
1227 notify(event)
1228
1229 # The changeset contained new values for some of this object's
1230@@ -1246,7 +1290,7 @@ class EntryManipulatingResource(ReadWriteResource):
1231 if flag is not Unauthorized:
1232 force_clear = True
1233 if force_clear or new_value != old_value:
1234- del(self._unmarshalled_field_cache[name])
1235+ del self._unmarshalled_field_cache[name]
1236
1237 self._applyChangesPostHook()
1238
1239@@ -1257,17 +1301,18 @@ class EntryManipulatingResource(ReadWriteResource):
1240 # the whole entry!
1241 self.request.response.setStatus(209)
1242 media_type = self.getPreferredSupportedContentType()
1243- self.request.response.setHeader('Content-type', media_type)
1244- return self._representation(media_type).encode('UTF-8')
1245+ self.request.response.setHeader("Content-type", media_type)
1246+ return self._representation(media_type).encode("UTF-8")
1247 else:
1248 # The object moved. Serve a redirect to its new location.
1249 # This might not necessarily be the location of the entry!
1250 self.request.response.setStatus(301)
1251 self.request.response.setHeader(
1252- 'Location', absoluteURL(self.context, self.request))
1253+ "Location", absoluteURL(self.context, self.request)
1254+ )
1255 # RFC 2616 says the body of a 301 response, if present,
1256 # SHOULD be a note linking to the new object.
1257- return ''
1258+ return ""
1259
1260 def _applyChangesPostHook(self):
1261 """A hook method called near the end of applyChanges.
1262@@ -1282,8 +1327,7 @@ class EntryManipulatingResource(ReadWriteResource):
1263 class EntryFieldResource(FieldUnmarshallerMixin, EntryManipulatingResource):
1264 """An individual field of an entry."""
1265
1266- SUPPORTED_CONTENT_TYPES = [HTTPResource.JSON_TYPE,
1267- HTTPResource.XHTML_TYPE]
1268+ SUPPORTED_CONTENT_TYPES = [HTTPResource.JSON_TYPE, HTTPResource.XHTML_TYPE]
1269
1270 def __init__(self, context, request):
1271 """Initialize with respect to a context and request."""
1272@@ -1297,8 +1341,8 @@ class EntryFieldResource(FieldUnmarshallerMixin, EntryManipulatingResource):
1273 # The conditional GET succeeded. Serve nothing.
1274 return b""
1275 else:
1276- self.request.response.setHeader('Content-Type', media_type)
1277- return self._representation(media_type).encode('UTF-8')
1278+ self.request.response.setHeader("Content-Type", media_type)
1279+ return self._representation(media_type).encode("UTF-8")
1280
1281 def do_PUT(self, media_type, representation):
1282 """Overwrite the field's existing value with a new value."""
1283@@ -1317,29 +1361,31 @@ class EntryFieldResource(FieldUnmarshallerMixin, EntryManipulatingResource):
1284
1285 :arg cache: is ignored.
1286 """
1287- value = self._unmarshallField(
1288- self.context.name, self.context.field)[1]
1289+ value = self._unmarshallField(self.context.name, self.context.field)[1]
1290
1291 # Append the revision number, because the algorithm for
1292 # generating the representation might itself change across
1293 # versions.
1294 revno = getUtility(IWebServiceConfiguration).code_revision
1295- return [core.encode('utf-8') for core in [revno, six.text_type(value)]]
1296+ return [core.encode("utf-8") for core in [revno, six.text_type(value)]]
1297
1298 def _representation(self, media_type):
1299 """Create a representation of the field value."""
1300 if media_type == self.JSON_TYPE:
1301 name, value = self._unmarshallField(
1302- self.context.name, self.context.field, CLOSEUP_DETAIL)
1303+ self.context.name, self.context.field, CLOSEUP_DETAIL
1304+ )
1305 return simplejson.dumps(value)
1306 elif media_type == self.XHTML_TYPE:
1307 name, value = self.unmarshallFieldToHTML(
1308- self.context.name, self.context.field)
1309+ self.context.name, self.context.field
1310+ )
1311 return decode_value(value)
1312 else:
1313 raise AssertionError(
1314- "No representation implementation for media type %s"
1315- % media_type)
1316+ "No representation implementation for media type %s"
1317+ % media_type
1318+ )
1319
1320
1321 @implementer(IEntryField, ILocation)
1322@@ -1376,14 +1422,14 @@ def make_entry_etag_cores(field_details):
1323 unwritable_values = []
1324 writable_values = []
1325 for name, details in field_details:
1326- if details['writable']:
1327+ if details["writable"]:
1328 # The client can write to this value.
1329 bucket = writable_values
1330 else:
1331 # The client can't write to this value (it might be read-only or
1332 # it might just be non-web-service writable.
1333 bucket = unwritable_values
1334- bucket.append(decode_value(details['value']))
1335+ bucket.append(decode_value(details["value"]))
1336
1337 unwritable = "\0".join(unwritable_values).encode("utf-8")
1338 writable = "\0".join(writable_values).encode("utf-8")
1339@@ -1391,15 +1437,20 @@ def make_entry_etag_cores(field_details):
1340
1341
1342 @implementer(IEntryResource, IJSONPublishable)
1343-class EntryResource(CustomOperationResourceMixin,
1344- FieldUnmarshallerMixin, EntryManipulatingResource):
1345+class EntryResource(
1346+ CustomOperationResourceMixin,
1347+ FieldUnmarshallerMixin,
1348+ EntryManipulatingResource,
1349+):
1350 """An individual object, published to the web."""
1351
1352- SUPPORTED_CONTENT_TYPES = [HTTPResource.WADL_TYPE,
1353- HTTPResource.DEPRECATED_WADL_TYPE,
1354- HTTPResource.XHTML_TYPE,
1355- HTTPResource.JSON_TYPE,
1356- HTTPResource.JSON_PLUS_XHTML_TYPE]
1357+ SUPPORTED_CONTENT_TYPES = [
1358+ HTTPResource.WADL_TYPE,
1359+ HTTPResource.DEPRECATED_WADL_TYPE,
1360+ HTTPResource.XHTML_TYPE,
1361+ HTTPResource.JSON_TYPE,
1362+ HTTPResource.JSON_PLUS_XHTML_TYPE,
1363+ ]
1364
1365 def __init__(self, context, request):
1366 """Associate this resource with a specific object and request."""
1367@@ -1422,8 +1473,9 @@ class EntryResource(CustomOperationResourceMixin,
1368 # This might not necessarily be the location of the entry!
1369 self.request.response.setStatus(301)
1370 self.request.response.setHeader(
1371- 'Location', absoluteURL(self.context, self.request))
1372- return ''
1373+ "Location", absoluteURL(self.context, self.request)
1374+ )
1375+ return ""
1376 return value
1377
1378 def _getETagCores(self, unmarshalled_field_values=None):
1379@@ -1441,11 +1493,12 @@ class EntryResource(CustomOperationResourceMixin,
1380 details = {}
1381 # Add in any values provided by the caller.
1382 # The value of the field is either passed in, or extracted.
1383- details['value'] = unmarshalled_field_values.get(
1384- name, self._unmarshallField(name, field)[1])
1385+ details["value"] = unmarshalled_field_values.get(
1386+ name, self._unmarshallField(name, field)[1]
1387+ )
1388
1389 # The client can write to this field.
1390- details['writable'] = self.isModifiableField(field, True)
1391+ details["writable"] = self.isModifiableField(field, True)
1392
1393 field_details.append((name, details))
1394
1395@@ -1478,21 +1531,26 @@ class EntryResource(CustomOperationResourceMixin,
1396 of media_type.
1397 """
1398 data = OrderedDict()
1399- data['self_link'] = absoluteURL(self.context, self.request)
1400+ data["self_link"] = absoluteURL(self.context, self.request)
1401 if self.adapter_utility.publish_web_link:
1402 # Objects in the web service correspond to pages on some website.
1403 # Provide the link to the corresponding page on the website.
1404 browser_request = IWebBrowserOriginatingRequest(self.request)
1405- data['web_link'] = absoluteURL(self.context, browser_request)
1406- data['resource_type_link'] = self.type_url
1407+ data["web_link"] = absoluteURL(self.context, browser_request)
1408+ data["resource_type_link"] = self.type_url
1409 unmarshalled_field_values = {}
1410 html_renderings = {}
1411 for name, field in getFieldsInOrder(self.entry.schema):
1412 if media_type.startswith(self.JSON_TYPE):
1413- repr_name, repr_value, html_renderer = (
1414- self._field_with_html_renderer(name, field))
1415- if (media_type == self.JSON_PLUS_XHTML_TYPE
1416- and html_renderer != _default_html_renderer):
1417+ (
1418+ repr_name,
1419+ repr_value,
1420+ html_renderer,
1421+ ) = self._field_with_html_renderer(name, field)
1422+ if (
1423+ media_type == self.JSON_PLUS_XHTML_TYPE
1424+ and html_renderer != _default_html_renderer
1425+ ):
1426 # This field has an unusual HTML renderer.
1427 if repr_value == self.REDACTED_VALUE:
1428 # The data is redacted, so the HTML
1429@@ -1503,31 +1561,31 @@ class EntryResource(CustomOperationResourceMixin,
1430 html_value = html_renderer(repr_value)
1431 html_renderings[repr_name] = html_value
1432 elif media_type == self.XHTML_TYPE:
1433- repr_name, repr_value = self.unmarshallFieldToHTML(
1434- name, field)
1435+ repr_name, repr_value = self.unmarshallFieldToHTML(name, field)
1436 else:
1437 raise AssertionError(
1438- "Cannot create data structure for media type %s"
1439- % media_type)
1440+ "Cannot create data structure for media type %s"
1441+ % media_type
1442+ )
1443 data[repr_name] = repr_value
1444 unmarshalled_field_values[name] = repr_value
1445
1446 if len(html_renderings) > 0:
1447- data['lp_html'] = html_renderings
1448+ data["lp_html"] = html_renderings
1449 elif media_type == self.JSON_PLUS_XHTML_TYPE:
1450 # The client requested application/json;include=lp_html,
1451 # but there's no HTML to deliver. Set the Content-Type to
1452 # regular application/json.
1453- self.request.response.setHeader('Content-Type', self.JSON_TYPE)
1454+ self.request.response.setHeader("Content-Type", self.JSON_TYPE)
1455 etag = self.getETag(media_type, unmarshalled_field_values)
1456- data['http_etag'] = etag
1457+ data["http_etag"] = etag
1458 return data
1459
1460 def toXHTML(self):
1461 """Represent this resource as an XHTML document."""
1462 view = getMultiAdapter(
1463- (self.context, self.request),
1464- name="lazr.restful.EntryResource")
1465+ (self.context, self.request), name="lazr.restful.EntryResource"
1466+ )
1467 return view()
1468
1469 def processAsJSONHash(self, media_type, representation):
1470@@ -1550,13 +1608,13 @@ class EntryResource(CustomOperationResourceMixin,
1471 return value, error
1472 if not isinstance(value, dict):
1473 self.request.response.setStatus(400)
1474- return None, 'Expected a JSON hash.'
1475+ return None, "Expected a JSON hash."
1476 return value, None
1477
1478 def do_GET(self):
1479 """Render an appropriate representation of the entry."""
1480 # Handle a custom operation, probably a search.
1481- operation_name = self.request.form.pop('ws.op', None)
1482+ operation_name = self.request.form.pop("ws.op", None)
1483 if operation_name is not None:
1484 result = self.handleCustomGET(operation_name)
1485 if isinstance(result, (bytes, six.text_type)):
1486@@ -1572,8 +1630,8 @@ class EntryResource(CustomOperationResourceMixin,
1487 # The conditional GET succeeded. Serve nothing.
1488 return b""
1489 else:
1490- self.request.response.setHeader('Content-Type', media_type)
1491- return self._representation(media_type).encode('UTF-8')
1492+ self.request.response.setHeader("Content-Type", media_type)
1493+ return self._representation(media_type).encode("UTF-8")
1494
1495 def do_PUT(self, media_type, representation):
1496 """Modify the entry's state to match the given representation.
1497@@ -1595,18 +1653,23 @@ class EntryResource(CustomOperationResourceMixin,
1498 if not self.isModifiableField(field, True):
1499 continue
1500 field = field.bind(self.context)
1501- marshaller = getMultiAdapter((field, self.request),
1502- IFieldMarshaller)
1503+ marshaller = getMultiAdapter(
1504+ (field, self.request), IFieldMarshaller
1505+ )
1506 repr_name = marshaller.representation_name
1507- if (changeset.get(repr_name) is None
1508- and getattr(self.entry, name) is not None):
1509+ if (
1510+ changeset.get(repr_name) is None
1511+ and getattr(self.entry, name) is not None
1512+ ):
1513 # This entry has a value for the attribute, but the
1514 # entity-body of the PUT request didn't make any assertion
1515 # about the attribute. The resource's behavior under HTTP
1516 # is undefined; we choose to send an error.
1517 self.request.response.setStatus(400)
1518- return ("You didn't specify a value for the attribute '%s'."
1519- % repr_name)
1520+ return (
1521+ "You didn't specify a value for the attribute '%s'."
1522+ % repr_name
1523+ )
1524
1525 return self.applyChanges(changeset, media_type)
1526
1527@@ -1630,9 +1693,12 @@ class EntryResource(CustomOperationResourceMixin,
1528 def type_url(self):
1529 """The URL to the resource type for this resource."""
1530 return "%s#%s" % (
1531- absoluteURL(self.request.publication.getApplication(
1532- self.request), self.request),
1533- self.adapter_utility.singular_type)
1534+ absoluteURL(
1535+ self.request.publication.getApplication(self.request),
1536+ self.request,
1537+ ),
1538+ self.adapter_utility.singular_type,
1539+ )
1540
1541 @property
1542 def adapter_utility(self):
1543@@ -1653,8 +1719,9 @@ class EntryResource(CustomOperationResourceMixin,
1544 # database hits.
1545 try:
1546 tagged_values = field.getTaggedValue(
1547- 'lazr.restful.exported')
1548- original_name = tagged_values['original_name']
1549+ "lazr.restful.exported"
1550+ )
1551+ original_name = tagged_values["original_name"]
1552 except KeyError:
1553 # This field has no tagged values, or is missing
1554 # the 'original_name' value. Its entry class was
1555@@ -1700,8 +1767,9 @@ class EntryResource(CustomOperationResourceMixin,
1556 directly modified from external clients, but they might change
1557 as side effects of other changes.
1558 """
1559- if (ICollectionField.providedBy(field)
1560- or field.__name__.startswith('_')):
1561+ if ICollectionField.providedBy(field) or field.__name__.startswith(
1562+ "_"
1563+ ):
1564 return False
1565 if field.readonly:
1566 return not is_external_client
1567@@ -1718,20 +1786,26 @@ class EntryResource(CustomOperationResourceMixin,
1568 representation = None
1569 else:
1570 representation = cache.get(
1571- self.context, media_type, self.request.version)
1572+ self.context, media_type, self.request.version
1573+ )
1574
1575 redacted_fields = self.redacted_fields
1576 if representation is None:
1577 # Either there is no active cache, or the representation
1578 # wasn't in the cache.
1579 representation = simplejson.dumps(
1580- self, cls=ResourceJSONEncoder, media_type=media_type)
1581+ self, cls=ResourceJSONEncoder, media_type=media_type
1582+ )
1583 # If there's an active cache, and this representation
1584 # doesn't contain any redactions, store it in the
1585 # cache.
1586 if cache is not None and len(redacted_fields) == 0:
1587- cache.set(self.context, self.JSON_TYPE,
1588- self.request.version, representation)
1589+ cache.set(
1590+ self.context,
1591+ self.JSON_TYPE,
1592+ self.request.version,
1593+ representation,
1594+ )
1595 else:
1596 # We have a representation, but we might not be able
1597 # to use it as-is.
1598@@ -1760,8 +1834,9 @@ class EntryResource(CustomOperationResourceMixin,
1599 return self.toXHTML()
1600 else:
1601 raise AssertionError(
1602- "No representation implementation for media type %s"
1603- % media_type)
1604+ "No representation implementation for media type %s"
1605+ % media_type
1606+ )
1607
1608 @property
1609 def _representation_cache(self):
1610@@ -1778,9 +1853,9 @@ class EntryResource(CustomOperationResourceMixin,
1611
1612
1613 @implementer(ICollectionResource)
1614-class CollectionResource(BatchingResourceMixin,
1615- CustomOperationResourceMixin,
1616- ReadOnlyResource):
1617+class CollectionResource(
1618+ BatchingResourceMixin, CustomOperationResourceMixin, ReadOnlyResource
1619+):
1620 """A resource that serves a list of entry resources."""
1621
1622 def __init__(self, context, request):
1623@@ -1794,7 +1869,7 @@ class CollectionResource(BatchingResourceMixin,
1624 def do_GET(self):
1625 """Fetch a collection and render it as JSON."""
1626 # Handle a custom operation, probably a search.
1627- operation_name = self.request.form.pop('ws.op', None)
1628+ operation_name = self.request.form.pop("ws.op", None)
1629 if operation_name is not None:
1630 result = self.handleCustomGET(operation_name)
1631 if isinstance(result, (bytes, six.text_type)):
1632@@ -1812,12 +1887,12 @@ class CollectionResource(BatchingResourceMixin,
1633 media_type = self.getPreferredSupportedContentType()
1634 if media_type in [self.DEPRECATED_WADL_TYPE, self.WADL_TYPE]:
1635 result = self.toWADL().encode("utf-8")
1636- self.request.response.setHeader('Content-Type', media_type)
1637+ self.request.response.setHeader("Content-Type", media_type)
1638 return result
1639
1640- result = self.batch(entries) + '}'
1641+ result = self.batch(entries) + "}"
1642
1643- self.request.response.setHeader('Content-type', self.JSON_TYPE)
1644+ self.request.response.setHeader("Content-type", self.JSON_TYPE)
1645 return result
1646
1647 def batch(self, entries=None, request=None):
1648@@ -1831,8 +1906,7 @@ class CollectionResource(BatchingResourceMixin,
1649 if request is None:
1650 request = self.request
1651 result = super(CollectionResource, self).batch(entries, request)
1652- result += (
1653- ', "resource_type_link" : ' + simplejson.dumps(self.type_url))
1654+ result += ', "resource_type_link" : ' + simplejson.dumps(self.type_url)
1655 return result
1656
1657 @property
1658@@ -1844,13 +1918,15 @@ class CollectionResource(BatchingResourceMixin,
1659 # entry the collection holds.
1660 schema = self.context.relationship.value_type.schema
1661 adapter = EntryAdapterUtility.forSchemaInterface(
1662- schema, self.request)
1663+ schema, self.request
1664+ )
1665 return adapter.entry_page_type_link
1666 else:
1667 # Top-level collection.
1668 schema = self.collection.entry_schema
1669 adapter = EntryAdapterUtility.forEntryInterface(
1670- schema, self.request)
1671+ schema, self.request
1672+ )
1673 return adapter.collection_type_link
1674
1675
1676@@ -1859,7 +1935,7 @@ class ServiceRootResource(HTTPResource):
1677 """A resource that responds to GET by describing the service."""
1678
1679 # A preparsed template file for WADL representations of the root.
1680- WADL_TEMPLATE = LazrPageTemplateFile('templates/wadl-root.pt')
1681+ WADL_TEMPLATE = LazrPageTemplateFile("templates/wadl-root.pt")
1682
1683 def __init__(self):
1684 """Initialize the resource.
1685@@ -1887,7 +1963,7 @@ class ServiceRootResource(HTTPResource):
1686 itself changes.
1687 """
1688 revno = getUtility(IWebServiceConfiguration).code_revision
1689- return [revno.encode('utf-8')]
1690+ return [revno.encode("utf-8")]
1691
1692 def __call__(self, REQUEST=None):
1693 """Handle a GET request."""
1694@@ -1902,8 +1978,8 @@ class ServiceRootResource(HTTPResource):
1695
1696 def setCachingHeaders(self):
1697 "How long should the client cache this service root?"
1698- user_agent = self.request.getHeader('User-Agent', '')
1699- if user_agent.startswith('Python-httplib2'):
1700+ user_agent = self.request.getHeader("User-Agent", "")
1701+ if user_agent.startswith("Python-httplib2"):
1702 # XXX leonardr 20100412
1703 # bug=http://code.google.com/p/httplib2/issues/detail?id=97
1704 #
1705@@ -1921,10 +1997,11 @@ class ServiceRootResource(HTTPResource):
1706 max_age = caching_policy[0]
1707 if max_age > 0:
1708 self.request.response.setHeader(
1709- 'Cache-Control', 'max-age=%d' % max_age)
1710+ "Cache-Control", "max-age=%d" % max_age
1711+ )
1712 # Also set the Date header so that client-side caches will
1713 # have something to work from.
1714- self.request.response.setHeader('Date', formatdate(time.time()))
1715+ self.request.response.setHeader("Date", formatdate(time.time()))
1716
1717 def do_GET(self):
1718 """Describe the capabilities of the web service."""
1719@@ -1940,7 +2017,7 @@ class ServiceRootResource(HTTPResource):
1720 # resources.
1721 result = simplejson.dumps(self, cls=ResourceJSONEncoder)
1722
1723- self.request.response.setHeader('Content-Type', media_type)
1724+ self.request.response.setHeader("Content-Type", media_type)
1725 return result.encode("utf-8")
1726
1727 def toWADL(self):
1728@@ -1951,15 +2028,17 @@ class ServiceRootResource(HTTPResource):
1729 singular_names = {}
1730 plural_names = {}
1731
1732- # Determine the name of the earliest version. We'll be using this later.
1733+ # Determine the name of the earliest version.
1734+ # We'll be using this later.
1735 config = getUtility(IWebServiceConfiguration)
1736 earliest_version = config.active_versions[0]
1737
1738 for registration in sorted(site_manager.registeredAdapters()):
1739 provided = registration.provided
1740 if IInterface.providedBy(provided):
1741- if (provided.isOrExtends(IEntry)
1742- and IEntry.implementedBy(registration.factory)):
1743+ if provided.isOrExtends(IEntry) and IEntry.implementedBy(
1744+ registration.factory
1745+ ):
1746 # The implementedBy check is necessary because
1747 # some IEntry adapters aren't classes with
1748 # schemas; they're functions. We can ignore these
1749@@ -1971,8 +2050,10 @@ class ServiceRootResource(HTTPResource):
1750 # registration for every web service version.
1751 schema, version_marker = registration.required
1752
1753- if (version_marker is IWebServiceClientRequest
1754- and self.request.version != earliest_version):
1755+ if (
1756+ version_marker is IWebServiceClientRequest
1757+ and self.request.version != earliest_version
1758+ ):
1759 # We are generating WADL for some version
1760 # other than the earliest version, and this is
1761 # a registration for the earliest version. We
1762@@ -1992,37 +2073,47 @@ class ServiceRootResource(HTTPResource):
1763 # Make sure that no other entry class is using this
1764 # class's singular or plural names.
1765 adapter = EntryAdapterUtility.forSchemaInterface(
1766- schema, self.request)
1767+ schema, self.request
1768+ )
1769
1770 singular = adapter.singular_type
1771- assert singular not in singular_names, (
1772- "Both %s and %s expose the singular name '%s'."
1773- % (singular_names[singular].__name__,
1774- schema.__name__, singular))
1775+ assert (
1776+ singular not in singular_names
1777+ ), "Both %s and %s expose the singular name '%s'." % (
1778+ singular_names[singular].__name__,
1779+ schema.__name__,
1780+ singular,
1781+ )
1782 singular_names[singular] = schema
1783
1784 plural = adapter.plural_type
1785- assert plural not in plural_names, (
1786- "Both %s and %s expose the plural name '%s'."
1787- % (plural_names[plural].__name__,
1788- schema.__name__, plural))
1789+ assert (
1790+ plural not in plural_names
1791+ ), "Both %s and %s expose the plural name '%s'." % (
1792+ plural_names[plural].__name__,
1793+ schema.__name__,
1794+ plural,
1795+ )
1796 plural_names[plural] = schema
1797
1798 entry_classes.append(registration.factory)
1799- elif (provided.isOrExtends(ICollection)
1800- and ICollection.implementedBy(registration.factory)
1801- and not IScopedCollection.implementedBy(
1802- registration.factory)):
1803+ elif (
1804+ provided.isOrExtends(ICollection)
1805+ and ICollection.implementedBy(registration.factory)
1806+ and not IScopedCollection.implementedBy(
1807+ registration.factory
1808+ )
1809+ ):
1810 # See comment above re: implementedBy check.
1811 # We omit IScopedCollection because those are handled
1812 # by the entry classes.
1813 collection_classes.append(registration.factory)
1814
1815 namespace = self.WADL_TEMPLATE.pt_getContext()
1816- namespace['service'] = self
1817- namespace['request'] = self.request
1818- namespace['entries'] = sorted_named_things(entry_classes)
1819- namespace['collections'] = sorted_named_things(collection_classes)
1820+ namespace["service"] = self
1821+ namespace["request"] = self.request
1822+ namespace["entries"] = sorted_named_things(entry_classes)
1823+ namespace["collections"] = sorted_named_things(collection_classes)
1824 return self.WADL_TEMPLATE.pt_render(namespace)
1825
1826 def toDataForJSON(self, media_type):
1827@@ -2035,13 +2126,14 @@ class ServiceRootResource(HTTPResource):
1828 type_url = "%s#%s" % (
1829 absoluteURL(
1830 self.request.publication.getApplication(self.request),
1831- self.request),
1832- "service-root")
1833- data_for_json = {'resource_type_link': type_url}
1834+ self.request,
1835+ ),
1836+ "service-root",
1837+ )
1838+ data_for_json = {"resource_type_link": type_url}
1839 publications = self.getTopLevelPublications()
1840 for link_name, publication in publications.items():
1841- data_for_json[link_name] = absoluteURL(publication,
1842- self.request)
1843+ data_for_json[link_name] = absoluteURL(publication, self.request)
1844 return data_for_json
1845
1846 def getTopLevelPublications(self):
1847@@ -2055,8 +2147,9 @@ class ServiceRootResource(HTTPResource):
1848 # XXX sinzui 2008-09-29 bug=276079:
1849 # Top-level collections need a marker interface
1850 # so that so top-level utilities are explicit.
1851- if (provided.isOrExtends(ICollection)
1852- and ICollection.implementedBy(registration.factory)):
1853+ if provided.isOrExtends(
1854+ ICollection
1855+ ) and ICollection.implementedBy(registration.factory):
1856 try:
1857 utility = getUtility(registration.required[0])
1858 except ComponentLookupError:
1859@@ -2067,12 +2160,13 @@ class ServiceRootResource(HTTPResource):
1860 # It's not a top-level resource.
1861 continue
1862 adapter = EntryAdapterUtility.forEntryInterface(
1863- entry_schema, self.request)
1864- link_name = ("%s_collection_link" % adapter.plural_type)
1865+ entry_schema, self.request
1866+ )
1867+ link_name = "%s_collection_link" % adapter.plural_type
1868 top_level_resources[link_name] = utility
1869 # Now, collect the top-level entries.
1870 for utility in getAllUtilitiesRegisteredFor(ITopLevelEntryLink):
1871- link_name = ("%s_link" % utility.link_name)
1872+ link_name = "%s_link" % utility.link_name
1873 top_level_resources[link_name] = utility
1874
1875 return top_level_resources
1876@@ -2083,9 +2177,12 @@ class ServiceRootResource(HTTPResource):
1877 adapter = self.adapter_utility
1878
1879 return "%s#%s" % (
1880- absoluteURL(self.request.publication.getApplication(
1881- self.request), self.request),
1882- adapter.singular_type)
1883+ absoluteURL(
1884+ self.request.publication.getApplication(self.request),
1885+ self.request,
1886+ ),
1887+ adapter.singular_type,
1888+ )
1889
1890
1891 @implementer(IEntry)
1892@@ -2132,10 +2229,13 @@ class ScopedCollection:
1893 # corresponding entry schema (IFooEntry).
1894 model_schema = self.relationship.value_type.schema
1895 request_interface = getUtility(
1896- IWebServiceVersion,
1897- name=self.request.version)
1898- return getGlobalSiteManager().adapters.lookup(
1899- (model_schema, request_interface), IEntry).schema
1900+ IWebServiceVersion, name=self.request.version
1901+ )
1902+ return (
1903+ getGlobalSiteManager()
1904+ .adapters.lookup((model_schema, request_interface), IEntry)
1905+ .schema
1906+ )
1907
1908 def find(self):
1909 """See `ICollection`."""
1910@@ -2143,12 +2243,12 @@ class ScopedCollection:
1911
1912
1913 class RESTUtilityBase:
1914-
1915 def _service_root_url(self):
1916 """Return the URL to the service root."""
1917 request = get_current_web_service_request()
1918- return absoluteURL(request.publication.getApplication(request),
1919- request)
1920+ return absoluteURL(
1921+ request.publication.getApplication(request), request
1922+ )
1923
1924
1925 class UnknownEntryAdapter(Exception):
1926@@ -2157,13 +2257,15 @@ class UnknownEntryAdapter(Exception):
1927 def __init__(self, adapted_interface_name, version):
1928 self.adapted_interface_name = adapted_interface_name
1929 self.version = version
1930- self.whence = ''
1931+ self.whence = ""
1932
1933 def __str__(self):
1934- message = ("No IEntry adapter found for %s (web service version: %s)."
1935- % (self.adapted_interface_name, self.version))
1936+ message = (
1937+ "No IEntry adapter found for %s (web service version: %s)."
1938+ % (self.adapted_interface_name, self.version)
1939+ )
1940 if self.whence:
1941- message += ' ' + self.whence
1942+ message += " " + self.whence
1943 return message
1944
1945
1946@@ -2182,11 +2284,15 @@ class EntryAdapterUtility(RESTUtilityBase):
1947 subclass of IEntry.
1948 """
1949 request_interface = getUtility(
1950- IWebServiceVersion, name=request.version)
1951+ IWebServiceVersion, name=request.version
1952+ )
1953 entry_class = getGlobalSiteManager().adapters.lookup(
1954- (entry_interface, request_interface), IEntry)
1955+ (entry_interface, request_interface), IEntry
1956+ )
1957 if entry_class is None:
1958- raise UnknownEntryAdapter(entry_interface.__name__, request.version)
1959+ raise UnknownEntryAdapter(
1960+ entry_interface.__name__, request.version
1961+ )
1962 return EntryAdapterUtility(entry_class)
1963
1964 @classmethod
1965@@ -2199,17 +2305,27 @@ class EntryAdapterUtility(RESTUtilityBase):
1966 # same IWebServiceVersion interface we find on the 'request'
1967 # object.
1968 entry_classes = [
1969- registration.factory for registration in registrations
1970- if (IInterface.providedBy(registration.provided)
1971+ registration.factory
1972+ for registration in registrations
1973+ if (
1974+ IInterface.providedBy(registration.provided)
1975 and registration.provided.isOrExtends(IEntry)
1976 and entry_interface.implementedBy(registration.factory)
1977- and registration.required[1].providedBy(request))]
1978- assert not len(entry_classes) > 1, (
1979- "%s provides more than one IEntry subclass for version %s." %
1980- (entry_interface.__name__, request.version))
1981- assert not len(entry_classes) < 1, (
1982- "%s does not provide any IEntry subclass for version %s." %
1983- (entry_interface.__name__, request.version))
1984+ and registration.required[1].providedBy(request)
1985+ )
1986+ ]
1987+ assert (
1988+ not len(entry_classes) > 1
1989+ ), "%s provides more than one IEntry subclass for version %s." % (
1990+ entry_interface.__name__,
1991+ request.version,
1992+ )
1993+ assert (
1994+ not len(entry_classes) < 1
1995+ ), "%s does not provide any IEntry subclass for version %s." % (
1996+ entry_interface.__name__,
1997+ request.version,
1998+ )
1999 return EntryAdapterUtility(entry_classes[0])
2000
2001 def __init__(self, entry_class):
2002@@ -2220,13 +2336,15 @@ class EntryAdapterUtility(RESTUtilityBase):
2003 def entry_interface(self):
2004 """The IEntry subclass implemented by this entry type."""
2005 interfaces = implementedBy(self.entry_class)
2006- entry_ifaces = [interface for interface in interfaces
2007- if interface.extends(IEntry)]
2008- assert len(entry_ifaces) == 1, (
2009- "There must be one and only one IEntry implementation "
2010- "for %s, found %s" % (
2011- self.entry_class,
2012- ", ".join(interface.__name__ for interface in entry_ifaces)))
2013+ entry_ifaces = [
2014+ interface for interface in interfaces if interface.extends(IEntry)
2015+ ]
2016+ assert (
2017+ len(entry_ifaces) == 1
2018+ ), "There must be one and only one IEntry implementation " "for %s, found %s" % (
2019+ self.entry_class,
2020+ ", ".join(interface.__name__ for interface in entry_ifaces),
2021+ )
2022 return entry_ifaces[0]
2023
2024 def _get_tagged_value(self, tag):
2025@@ -2240,31 +2358,31 @@ class EntryAdapterUtility(RESTUtilityBase):
2026 # request, we shouldn't publish a web_link for *any* entry.
2027 web_service_request = get_current_web_service_request()
2028 website_request = IWebBrowserOriginatingRequest(
2029- web_service_request, None)
2030+ web_service_request, None
2031+ )
2032 return website_request is not None and self._get_tagged_value(
2033- 'publish_web_link')
2034+ "publish_web_link"
2035+ )
2036
2037 @property
2038 def singular_type(self):
2039 """Return the singular name for this object type."""
2040- return self._get_tagged_value('singular')
2041+ return self._get_tagged_value("singular")
2042
2043 @property
2044 def plural_type(self):
2045 """Return the plural name for this object type."""
2046- return self._get_tagged_value('plural')
2047+ return self._get_tagged_value("plural")
2048
2049 @property
2050 def type_link(self):
2051 """The URL to the type definition for this kind of entry."""
2052- return "%s#%s" % (
2053- self._service_root_url(), self.singular_type)
2054+ return "%s#%s" % (self._service_root_url(), self.singular_type)
2055
2056 @property
2057 def collection_type_link(self):
2058 """The definition of a top-level collection of this kind of object."""
2059- return "%s#%s" % (
2060- self._service_root_url(), self.plural_type)
2061+ return "%s#%s" % (self._service_root_url(), self.plural_type)
2062
2063 @property
2064 def entry_page_type(self):
2065@@ -2274,8 +2392,7 @@ class EntryAdapterUtility(RESTUtilityBase):
2066 @property
2067 def entry_page_type_link(self):
2068 "The URL to the definition of a collection of this kind of object."
2069- return "%s#%s" % (
2070- self._service_root_url(), self.entry_page_type)
2071+ return "%s#%s" % (self._service_root_url(), self.entry_page_type)
2072
2073 @property
2074 def entry_page_representation_id(self):
2075@@ -2287,13 +2404,13 @@ class EntryAdapterUtility(RESTUtilityBase):
2076 "The URL to the description of a collection of this kind of object."
2077 return "%s#%s" % (
2078 self._service_root_url(),
2079- self.entry_page_representation_id)
2080+ self.entry_page_representation_id,
2081+ )
2082
2083 @property
2084 def full_representation_link(self):
2085 """The URL to the description of the object's full representation."""
2086- return "%s#%s-full" % (
2087- self._service_root_url(), self.singular_type)
2088+ return "%s#%s-full" % (self._service_root_url(), self.singular_type)
2089
2090
2091 def get_entry_fields_in_write_order(entry):
2092@@ -2312,7 +2429,7 @@ def get_entry_fields_in_write_order(entry):
2093 mutator_fields = []
2094 field_implementations = entry.__class__.__dict__
2095 for name, field in getFieldsInOrder(entry.schema):
2096- if name.startswith('_'):
2097+ if name.startswith("_"):
2098 # This field is not part of the web service interface.
2099 continue
2100
2101@@ -2320,8 +2437,10 @@ def get_entry_fields_in_write_order(entry):
2102 # Passthrough (but not a direct instance of Passthrough), put
2103 # it at the end -- it's probably controlled by a mutator.
2104 implementation = field_implementations[name]
2105- if (issubclass(implementation.__class__, Passthrough)
2106- and implementation.__class__ is not Passthrough):
2107+ if (
2108+ issubclass(implementation.__class__, Passthrough)
2109+ and implementation.__class__ is not Passthrough
2110+ ):
2111 mutator_fields.append((name, field))
2112 else:
2113 non_mutator_fields.append((name, field))
2114diff --git a/src/lazr/restful/debug.py b/src/lazr/restful/debug.py
2115index 5b2205a..0567455 100644
2116--- a/src/lazr/restful/debug.py
2117+++ b/src/lazr/restful/debug.py
2118@@ -5,10 +5,7 @@
2119 from __future__ import absolute_import, print_function
2120
2121 __metaclass__ = type
2122-__all__ = [
2123- "debug_proxy",
2124- "typename"
2125- ]
2126+__all__ = ["debug_proxy", "typename"]
2127
2128
2129 from six import StringIO
2130@@ -20,7 +17,7 @@ from zope.security.checker import getChecker, Checker, CheckerPublic, Proxy
2131 def typename(obj):
2132 """Return the typename of an object."""
2133 t = type(obj)
2134- if t.__module__ in {'__builtin__', 'builtins'}:
2135+ if t.__module__ in {"__builtin__", "builtins"}:
2136 return t.__name__
2137 else:
2138 return "%s.%s" % (t.__module__, t.__name__)
2139@@ -39,15 +36,17 @@ def get_permission_mapping(checker):
2140 permission_to_names = {}
2141 for name, permission in checker.get_permissions.items():
2142 if permission is CheckerPublic:
2143- permission = 'public'
2144+ permission = "public"
2145 permission_to_names.setdefault(permission, []).append(name)
2146 for name, permission in checker.set_permissions.items():
2147 if permission is CheckerPublic:
2148- permission = 'public'
2149+ permission = "public"
2150 set_permission = "%s (set)" % permission
2151 permission_to_names.setdefault(set_permission, []).append(name)
2152- return sorted((permission, sorted(names))
2153- for permission, names in permission_to_names.items())
2154+ return sorted(
2155+ (permission, sorted(names))
2156+ for permission, names in permission_to_names.items()
2157+ )
2158
2159
2160 def security_proxy_formatter(proxy):
2161@@ -56,7 +55,7 @@ def security_proxy_formatter(proxy):
2162 output = ["%s (using %s)" % (typename(proxy), typename(checker))]
2163 if type(checker) is Checker:
2164 for permission, names in get_permission_mapping(checker):
2165- output.append('%s: %s' % (permission, ", ".join(sorted(names))))
2166+ output.append("%s: %s" % (permission, ", ".join(sorted(names))))
2167 return "\n ".join(output)
2168
2169
2170diff --git a/src/lazr/restful/declarations.py b/src/lazr/restful/declarations.py
2171index f1529de..74eb8c5 100644
2172--- a/src/lazr/restful/declarations.py
2173+++ b/src/lazr/restful/declarations.py
2174@@ -6,43 +6,43 @@ from __future__ import absolute_import, print_function
2175
2176 __metaclass__ = type
2177 __all__ = [
2178- 'COLLECTION_TYPE',
2179- 'ENTRY_TYPE',
2180- 'FIELD_TYPE',
2181- 'LAZR_WEBSERVICE_ACCESSORS',
2182- 'LAZR_WEBSERVICE_EXPORTED',
2183- 'LAZR_WEBSERVICE_MUTATORS',
2184- 'OPERATION_TYPES',
2185- 'REQUEST_USER',
2186- 'accessor_for',
2187- 'cache_for',
2188- 'call_with',
2189- 'collection_default_content',
2190- 'error_status',
2191- 'export_as_webservice_collection',
2192- 'export_as_webservice_entry',
2193- 'export_destructor_operation',
2194- 'export_factory_operation',
2195- 'export_operation_as',
2196- 'export_read_operation',
2197- 'export_write_operation',
2198- 'exported',
2199- 'exported_as_webservice_collection',
2200- 'exported_as_webservice_entry',
2201- 'generate_collection_adapter',
2202- 'generate_entry_adapters',
2203- 'generate_entry_interfaces',
2204- 'generate_operation_adapter',
2205- 'mutator_for',
2206- 'operation_for_version',
2207- 'operation_parameters',
2208- 'operation_removed_in_version',
2209- 'operation_returns_entry',
2210- 'operation_returns_collection_of',
2211- 'rename_parameters_as',
2212- 'scoped',
2213- 'webservice_error',
2214- ]
2215+ "COLLECTION_TYPE",
2216+ "ENTRY_TYPE",
2217+ "FIELD_TYPE",
2218+ "LAZR_WEBSERVICE_ACCESSORS",
2219+ "LAZR_WEBSERVICE_EXPORTED",
2220+ "LAZR_WEBSERVICE_MUTATORS",
2221+ "OPERATION_TYPES",
2222+ "REQUEST_USER",
2223+ "accessor_for",
2224+ "cache_for",
2225+ "call_with",
2226+ "collection_default_content",
2227+ "error_status",
2228+ "export_as_webservice_collection",
2229+ "export_as_webservice_entry",
2230+ "export_destructor_operation",
2231+ "export_factory_operation",
2232+ "export_operation_as",
2233+ "export_read_operation",
2234+ "export_write_operation",
2235+ "exported",
2236+ "exported_as_webservice_collection",
2237+ "exported_as_webservice_entry",
2238+ "generate_collection_adapter",
2239+ "generate_entry_adapters",
2240+ "generate_entry_interfaces",
2241+ "generate_operation_adapter",
2242+ "mutator_for",
2243+ "operation_for_version",
2244+ "operation_parameters",
2245+ "operation_removed_in_version",
2246+ "operation_returns_entry",
2247+ "operation_returns_collection_of",
2248+ "rename_parameters_as",
2249+ "scoped",
2250+ "webservice_error",
2251+]
2252
2253 from collections import OrderedDict
2254 import copy
2255@@ -58,12 +58,12 @@ from zope.interface.interfaces import IInterface, IMethod
2256 from zope.schema import (
2257 getFields,
2258 getFieldsInOrder,
2259- )
2260+)
2261 from zope.schema.interfaces import (
2262 IField,
2263 IObject,
2264 IText,
2265- )
2266+)
2267 from zope.security.checker import CheckerPublic
2268 from zope.traversing.browser import absoluteURL
2269
2270@@ -72,7 +72,7 @@ from lazr.delegates import Passthrough
2271 from lazr.restful.fields import (
2272 CollectionField,
2273 Reference,
2274- )
2275+)
2276 from lazr.restful.interface import copy_field
2277 from lazr.restful.interfaces import (
2278 ICollection,
2279@@ -85,14 +85,14 @@ from lazr.restful.interfaces import (
2280 IWebServiceVersion,
2281 LAZR_WEBSERVICE_NAME,
2282 LAZR_WEBSERVICE_NS,
2283- )
2284+)
2285 from lazr.restful import (
2286 Collection,
2287 Entry,
2288 EntryAdapterUtility,
2289 ResourceOperation,
2290 ObjectLink,
2291- )
2292+)
2293 from lazr.restful.security import protect_schema
2294 from lazr.restful.utils import (
2295 camelcase_to_underscore_separated,
2296@@ -100,28 +100,34 @@ from lazr.restful.utils import (
2297 make_identifier_safe,
2298 VersionedDict,
2299 VersionedObject,
2300- )
2301-
2302-LAZR_WEBSERVICE_ACCESSORS = '%s.exported.accessors' % LAZR_WEBSERVICE_NS
2303-LAZR_WEBSERVICE_EXPORTED = '%s.exported' % LAZR_WEBSERVICE_NS
2304-LAZR_WEBSERVICE_MUTATORS = '%s.exported.mutators' % LAZR_WEBSERVICE_NS
2305-COLLECTION_TYPE = 'collection'
2306-ENTRY_TYPE = 'entry'
2307-FIELD_TYPE = 'field'
2308-REMOVED_OPERATION_TYPE = 'removed_operation'
2309+)
2310+
2311+LAZR_WEBSERVICE_ACCESSORS = "%s.exported.accessors" % LAZR_WEBSERVICE_NS
2312+LAZR_WEBSERVICE_EXPORTED = "%s.exported" % LAZR_WEBSERVICE_NS
2313+LAZR_WEBSERVICE_MUTATORS = "%s.exported.mutators" % LAZR_WEBSERVICE_NS
2314+COLLECTION_TYPE = "collection"
2315+ENTRY_TYPE = "entry"
2316+FIELD_TYPE = "field"
2317+REMOVED_OPERATION_TYPE = "removed_operation"
2318 OPERATION_TYPES = (
2319- 'destructor', 'factory', 'read_operation', 'write_operation',
2320- REMOVED_OPERATION_TYPE)
2321+ "destructor",
2322+ "factory",
2323+ "read_operation",
2324+ "write_operation",
2325+ REMOVED_OPERATION_TYPE,
2326+)
2327
2328 # These are the only valid keys to be found in an entry's
2329 # version-specific annotation dictionary.
2330-ENTRY_ANNOTATION_KEYS = set([
2331- 'contributes_to',
2332- 'exported',
2333- 'plural_name',
2334- 'publish_web_link',
2335- 'singular_name',
2336- ])
2337+ENTRY_ANNOTATION_KEYS = set(
2338+ [
2339+ "contributes_to",
2340+ "exported",
2341+ "plural_name",
2342+ "publish_web_link",
2343+ "singular_name",
2344+ ]
2345+)
2346
2347
2348 class REQUEST_USER:
2349@@ -132,20 +138,23 @@ class REQUEST_USER:
2350 copy.deepcopy, and we want 'is REQUEST_USER' to succeed on the
2351 copy.
2352 """
2353+
2354 pass
2355
2356
2357 def _check_called_from_interface_def(name):
2358- """Make sure that the declaration was used from within a class definition.
2359+ """
2360+ Make sure that the declaration was used from within a class definition.
2361 """
2362 # 2 is our caller's caller.
2363 frame = sys._getframe(2)
2364 f_locals = frame.f_locals
2365
2366 # Try to make sure we were called from a class def.
2367- if (f_locals is frame.f_globals) or ('__module__' not in f_locals):
2368+ if (f_locals is frame.f_globals) or ("__module__" not in f_locals):
2369 raise TypeError(
2370- "%s can only be used from within an interface definition." % name)
2371+ "%s can only be used from within an interface definition." % name
2372+ )
2373
2374
2375 def _check_interface(name, interface):
2376@@ -164,10 +173,15 @@ def _get_interface_tags():
2377 return f_locals.setdefault(TAGGED_DATA, {})
2378
2379
2380-def _export_as_webservice_entry(interface,
2381- singular_name=None, plural_name=None,
2382- contributes_to=None, publish_web_link=True,
2383- as_of=None, versioned_annotations=None):
2384+def _export_as_webservice_entry(
2385+ interface,
2386+ singular_name=None,
2387+ plural_name=None,
2388+ contributes_to=None,
2389+ publish_web_link=True,
2390+ as_of=None,
2391+ versioned_annotations=None,
2392+):
2393 """Tag an interface as exported on the web service as an entry.
2394
2395 This is the core of export_as_webservice_entry and
2396@@ -180,21 +194,27 @@ def _export_as_webservice_entry(interface,
2397 # behavior assumes this convention and yields a singular name of
2398 # "word1_word2".
2399 my_singular_name = camelcase_to_underscore_separated(
2400- interface.__name__[1:])
2401+ interface.__name__[1:]
2402+ )
2403 else:
2404 my_singular_name = singular_name
2405
2406 # Turn the named arguments into a dictionary for the first exported
2407 # version.
2408 initial_version = dict(
2409- type=ENTRY_TYPE, singular_name=my_singular_name,
2410- plural_name=plural_name, contributes_to=contributes_to,
2411- publish_web_link=publish_web_link, exported=True,
2412- _as_of_was_used=(as_of is not None))
2413+ type=ENTRY_TYPE,
2414+ singular_name=my_singular_name,
2415+ plural_name=plural_name,
2416+ contributes_to=contributes_to,
2417+ publish_web_link=publish_web_link,
2418+ exported=True,
2419+ _as_of_was_used=(as_of is not None),
2420+ )
2421
2422 afterwards = versioned_annotations or []
2423 for version, annotations in itertools.chain(
2424- [(as_of, initial_version)], afterwards):
2425+ [(as_of, initial_version)], afterwards
2426+ ):
2427 annotation_stack.push(version)
2428
2429 for key, value in annotations.items():
2430@@ -204,14 +224,18 @@ def _export_as_webservice_entry(interface,
2431 if key not in ENTRY_ANNOTATION_KEYS:
2432 raise ValueError(
2433 'Unrecognized annotation for version "%s": '
2434- '"%s"' % (version, key))
2435+ '"%s"' % (version, key)
2436+ )
2437 annotation_stack[key] = value
2438 # If this version provides a singular name but not a plural name,
2439 # apply the default pluralization rule.
2440- if (annotations.get('singular_name') is not None
2441- and annotations.get('plural_name') is None):
2442- annotation_stack['plural_name'] = (
2443- annotations['singular_name'] + 's')
2444+ if (
2445+ annotations.get("singular_name") is not None
2446+ and annotations.get("plural_name") is None
2447+ ):
2448+ annotation_stack["plural_name"] = (
2449+ annotations["singular_name"] + "s"
2450+ )
2451
2452 # When called from the @exported_as_webservice_entry decorator, this
2453 # would overwrite any interface annotations made by method decorators;
2454@@ -227,24 +251,29 @@ def _export_as_webservice_entry(interface,
2455 tag_stack = field.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
2456 if tag_stack is None or tag_stack.is_empty:
2457 continue
2458- if tag_stack['type'] != FIELD_TYPE:
2459+ if tag_stack["type"] != FIELD_TYPE:
2460 continue
2461 for version, tags in tag_stack.stack:
2462 # Set 'as' for every version in which the field is published but
2463 # no 'as' is specified. Also set 'original_name' for every
2464 # version in which the field is published--this will help with
2465 # performance optimizations around permission checks.
2466- if tags.get('exported') is not False:
2467- tags['original_name'] = name
2468- if tags.get('as') is None:
2469- tags['as'] = name
2470+ if tags.get("exported") is not False:
2471+ tags["original_name"] = name
2472+ if tags.get("as") is None:
2473+ tags["as"] = name
2474
2475 annotate_exported_methods(interface)
2476
2477
2478-def export_as_webservice_entry(singular_name=None, plural_name=None,
2479- contributes_to=None, publish_web_link=True,
2480- as_of=None, versioned_annotations=None):
2481+def export_as_webservice_entry(
2482+ singular_name=None,
2483+ plural_name=None,
2484+ contributes_to=None,
2485+ publish_web_link=True,
2486+ as_of=None,
2487+ versioned_annotations=None,
2488+):
2489 """Mark the content interface as exported on the web service as an entry.
2490
2491 If contributes_to is a non-empty sequence of Interfaces, this entry will
2492@@ -278,15 +307,20 @@ def export_as_webservice_entry(singular_name=None, plural_name=None,
2493 'plural_name', 'contributes_to', or 'publish_web_link', which
2494 work just like the corresponding arguments to this method.
2495 """
2496- _check_called_from_interface_def('export_as_webservice_entry()')
2497+ _check_called_from_interface_def("export_as_webservice_entry()")
2498
2499 def mark_entry(interface):
2500 """Class advisor that tags the interface once it is created."""
2501- _check_interface('export_as_webservice_entry()', interface)
2502+ _check_interface("export_as_webservice_entry()", interface)
2503 _export_as_webservice_entry(
2504- interface, singular_name=singular_name, plural_name=plural_name,
2505- contributes_to=contributes_to, publish_web_link=publish_web_link,
2506- as_of=as_of, versioned_annotations=versioned_annotations)
2507+ interface,
2508+ singular_name=singular_name,
2509+ plural_name=plural_name,
2510+ contributes_to=contributes_to,
2511+ publish_web_link=publish_web_link,
2512+ as_of=as_of,
2513+ versioned_annotations=versioned_annotations,
2514+ )
2515 return interface
2516
2517 addClassAdvisor(mark_entry)
2518@@ -323,9 +357,15 @@ class exported_as_webservice_entry:
2519 work just like the corresponding arguments to this method.
2520 """
2521
2522- def __init__(self, singular_name=None, plural_name=None,
2523- contributes_to=None, publish_web_link=True,
2524- as_of=None, versioned_annotations=None):
2525+ def __init__(
2526+ self,
2527+ singular_name=None,
2528+ plural_name=None,
2529+ contributes_to=None,
2530+ publish_web_link=True,
2531+ as_of=None,
2532+ versioned_annotations=None,
2533+ ):
2534 self.singular_name = singular_name
2535 self.plural_name = plural_name
2536 self.contributes_to = contributes_to
2537@@ -334,13 +374,16 @@ class exported_as_webservice_entry:
2538 self.versioned_annotations = versioned_annotations
2539
2540 def __call__(self, interface):
2541- _check_interface('exported_as_webservice_entry()', interface)
2542+ _check_interface("exported_as_webservice_entry()", interface)
2543 _export_as_webservice_entry(
2544 interface,
2545- singular_name=self.singular_name, plural_name=self.plural_name,
2546+ singular_name=self.singular_name,
2547+ plural_name=self.plural_name,
2548 contributes_to=self.contributes_to,
2549 publish_web_link=self.publish_web_link,
2550- as_of=self.as_of, versioned_annotations=self.versioned_annotations)
2551+ as_of=self.as_of,
2552+ versioned_annotations=self.versioned_annotations,
2553+ )
2554 return interface
2555
2556
2557@@ -378,37 +421,47 @@ def exported(field, *versioned_annotations, **kwparams):
2558 # describing the different ways this field is exposed in different
2559 # versions.
2560 annotation_stack = VersionedDict()
2561- first_version_name = kwparams.pop('as_of', None)
2562+ first_version_name = kwparams.pop("as_of", None)
2563 annotation_stack.push(first_version_name)
2564- annotation_stack['type'] = FIELD_TYPE
2565+ annotation_stack["type"] = FIELD_TYPE
2566
2567 if first_version_name is not None:
2568 # The user explicitly said to start publishing this field in a
2569 # particular version.
2570- annotation_stack['_as_of_was_used'] = True
2571- annotation_stack['exported'] = True
2572+ annotation_stack["_as_of_was_used"] = True
2573+ annotation_stack["exported"] = True
2574
2575- annotation_key_for_argument_key = {'exported_as' : 'as',
2576- 'exported' : 'exported',
2577- 'readonly' : 'readonly'}
2578+ annotation_key_for_argument_key = {
2579+ "exported_as": "as",
2580+ "exported": "exported",
2581+ "readonly": "readonly",
2582+ }
2583
2584 # If keyword parameters are present, they define the field's
2585 # behavior for the first exposed version. Incorporate them into
2586 # the VersionedDict.
2587 for (key, annotation_key) in annotation_key_for_argument_key.items():
2588 if key in kwparams:
2589- if (key == "exported" and kwparams[key] is False
2590- and first_version_name is not None):
2591+ if (
2592+ key == "exported"
2593+ and kwparams[key] is False
2594+ and first_version_name is not None
2595+ ):
2596 raise ValueError(
2597- ("as_of=%s says to export %s, but exported=False "
2598- "says not to.") % (
2599- first_version_name, field.__class__.__name__))
2600+ (
2601+ "as_of=%s says to export %s, but exported=False "
2602+ "says not to."
2603+ )
2604+ % (first_version_name, field.__class__.__name__)
2605+ )
2606 annotation_stack[annotation_key] = kwparams.pop(key)
2607
2608 # If any keywords are left over, raise an exception.
2609 if len(kwparams) > 0:
2610- raise TypeError("exported got an unexpected keyword "
2611- "argument '%s'" % list(kwparams)[0])
2612+ raise TypeError(
2613+ "exported got an unexpected keyword "
2614+ "argument '%s'" % list(kwparams)[0]
2615+ )
2616
2617 # Now incorporate the list of named dicts into the VersionedDict.
2618 for version, annotations in reversed(versioned_annotations):
2619@@ -418,10 +471,13 @@ def exported(field, *versioned_annotations, **kwparams):
2620 # recognized annotations.
2621 for key in annotations:
2622 if key not in annotation_key_for_argument_key:
2623- raise ValueError('Unrecognized annotation for version "%s": '
2624- '"%s"' % (version, key))
2625- annotation_stack[annotation_key_for_argument_key[key]] = (
2626- annotations[key])
2627+ raise ValueError(
2628+ 'Unrecognized annotation for version "%s": '
2629+ '"%s"' % (version, key)
2630+ )
2631+ annotation_stack[
2632+ annotation_key_for_argument_key[key]
2633+ ] = annotations[key]
2634
2635 # Now we can annotate the field object with the VersionedDict.
2636 field.setTaggedValue(LAZR_WEBSERVICE_EXPORTED, annotation_stack)
2637@@ -439,10 +495,11 @@ def exported(field, *versioned_annotations, **kwparams):
2638 def _check_collection_default_content(name, interface):
2639 """Check that the interface has a method providing the default content."""
2640 tag = interface.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED, {})
2641- if 'collection_default_content' not in tag:
2642+ if "collection_default_content" not in tag:
2643 raise TypeError(
2644- "%s is missing a method tagged with @collection_default_content." %
2645- name)
2646+ "%s is missing a method tagged with @collection_default_content."
2647+ % name
2648+ )
2649
2650
2651 def export_as_webservice_collection(entry_schema):
2652@@ -455,7 +512,7 @@ def export_as_webservice_collection(entry_schema):
2653 :raises TypeError: if the interface doesn't have a method decorated with
2654 @collection_default_content.
2655 """
2656- _check_called_from_interface_def('export_as_webservice_collection()')
2657+ _check_called_from_interface_def("export_as_webservice_collection()")
2658
2659 if not IInterface.providedBy(entry_schema):
2660 raise TypeError("entry_schema must be an interface.")
2661@@ -464,13 +521,15 @@ def export_as_webservice_collection(entry_schema):
2662 # check it.
2663 tags = _get_interface_tags()
2664 tags[LAZR_WEBSERVICE_EXPORTED] = dict(
2665- type=COLLECTION_TYPE, collection_entry_schema=entry_schema)
2666+ type=COLLECTION_TYPE, collection_entry_schema=entry_schema
2667+ )
2668
2669 def mark_collection(interface):
2670 """Class advisor that tags the interface once it is created."""
2671- _check_interface('export_as_webservice_collection()', interface)
2672+ _check_interface("export_as_webservice_collection()", interface)
2673 _check_collection_default_content(
2674- 'export_as_webservice_collection()', interface)
2675+ "export_as_webservice_collection()", interface
2676+ )
2677 annotate_exported_methods(interface)
2678 return interface
2679
2680@@ -490,15 +549,16 @@ class exported_as_webservice_collection:
2681 self.entry_schema = entry_schema
2682
2683 def __call__(self, interface):
2684- _check_interface('exported_as_webservice_collection()', interface)
2685+ _check_interface("exported_as_webservice_collection()", interface)
2686 _check_collection_default_content(
2687- 'exported_as_webservice_collection()', interface)
2688+ "exported_as_webservice_collection()", interface
2689+ )
2690 tag = interface.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
2691 if tag is None:
2692 tag = {}
2693 interface.setTaggedValue(LAZR_WEBSERVICE_EXPORTED, tag)
2694- tag['type'] = COLLECTION_TYPE
2695- tag['collection_entry_schema'] = self.entry_schema
2696+ tag["type"] = COLLECTION_TYPE
2697+ tag["collection_entry_schema"] = self.entry_schema
2698 annotate_exported_methods(interface)
2699 return interface
2700
2701@@ -520,7 +580,7 @@ class collection_default_content:
2702 method. This is to be used when the method has required
2703 parameters.
2704 """
2705- _check_called_from_interface_def('@collection_default_content')
2706+ _check_called_from_interface_def("@collection_default_content")
2707
2708 # We used to check that this decorator is being used from within an
2709 # interface exported as a collection. However, it isn't possible to
2710@@ -535,31 +595,36 @@ class collection_default_content:
2711 # instead.
2712 tags = _get_interface_tags()
2713 tag = tags.setdefault(LAZR_WEBSERVICE_EXPORTED, {})
2714- if 'type' in tag and tag['type'] != COLLECTION_TYPE:
2715+ if "type" in tag and tag["type"] != COLLECTION_TYPE:
2716 raise TypeError(
2717 "@collection_default_content can only be used from within an "
2718- "interface exported as a collection.")
2719+ "interface exported as a collection."
2720+ )
2721
2722 default_content_methods = tag.setdefault(
2723- 'collection_default_content', {})
2724+ "collection_default_content", {}
2725+ )
2726
2727 if version in default_content_methods:
2728 raise TypeError(
2729 "Only one method can be marked with "
2730- "@collection_default_content for version '%s'." % (
2731- _version_name(version)))
2732+ "@collection_default_content for version '%s'."
2733+ % (_version_name(version))
2734+ )
2735 self.version = version
2736 self.params = params
2737
2738 def __call__(self, f):
2739 """Annotates the collection with the name of the method to call."""
2740 tag = _get_interface_tags()[LAZR_WEBSERVICE_EXPORTED]
2741- tag['collection_default_content'][self.version] = (
2742- f.__name__, self.params)
2743+ tag["collection_default_content"][self.version] = (
2744+ f.__name__,
2745+ self.params,
2746+ )
2747 return f
2748
2749
2750-WEBSERVICE_ERROR = '__lazr_webservice_error__'
2751+WEBSERVICE_ERROR = "__lazr_webservice_error__"
2752
2753
2754 def webservice_error(status):
2755@@ -576,10 +641,11 @@ def webservice_error(status):
2756 f_locals = frame.f_locals
2757
2758 # Try to make sure we were called from a class def.
2759- if (f_locals is frame.f_globals) or ('__module__' not in f_locals):
2760+ if (f_locals is frame.f_globals) or ("__module__" not in f_locals):
2761 raise TypeError(
2762 "webservice_error() can only be used from within an exception "
2763- "definition.")
2764+ "definition."
2765+ )
2766
2767 f_locals[WEBSERVICE_ERROR] = int(status)
2768
2769@@ -608,12 +674,13 @@ def error_status(status):
2770
2771 def func(value):
2772 if not issubclass(value, Exception):
2773- raise TypeError('Annotated value must be an exception class.')
2774+ raise TypeError("Annotated value must be an exception class.")
2775 old = getattr(value, WEBSERVICE_ERROR, None)
2776 if old is not None and old != status:
2777- raise ValueError('Exception already has an error status', old)
2778+ raise ValueError("Exception already has an error status", old)
2779 setattr(value, WEBSERVICE_ERROR, status)
2780 return value
2781+
2782 return func
2783
2784
2785@@ -654,7 +721,7 @@ class _method_annotator:
2786 # @export_*_operation declaration will modify
2787 # annotations['type'] in place to signal that it is in
2788 # fact being published.
2789- annotations['type'] = REMOVED_OPERATION_TYPE
2790+ annotations["type"] = REMOVED_OPERATION_TYPE
2791 method.__dict__[LAZR_WEBSERVICE_EXPORTED] = annotations
2792 self.annotate_method(method, annotations)
2793 return method
2794@@ -682,57 +749,64 @@ def annotate_exported_methods(interface):
2795 annotation_stack = method.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
2796 if annotation_stack is None:
2797 continue
2798- if annotation_stack.get('type') is None:
2799+ if annotation_stack.get("type") is None:
2800 continue
2801
2802 # Make sure that each version of the web service defines
2803 # a self-consistent view of this method.
2804 for version, annotations in annotation_stack.stack:
2805- if annotations['type'] == REMOVED_OPERATION_TYPE:
2806+ if annotations["type"] == REMOVED_OPERATION_TYPE:
2807 # The method is published in other versions of the web
2808 # service, but not in this one. Don't try to validate this
2809 # version's annotations.
2810 continue
2811
2812 # Method is exported under its own name by default.
2813- if 'as' not in annotations:
2814- annotations['as'] = method.__name__
2815+ if "as" not in annotations:
2816+ annotations["as"] = method.__name__
2817
2818 # It's possible that call_with, operation_parameters, and/or
2819 # operation_returns_* weren't used.
2820- annotations.setdefault('call_with', {})
2821- annotations.setdefault('params', {})
2822- annotations.setdefault('return_type', None)
2823+ annotations.setdefault("call_with", {})
2824+ annotations.setdefault("params", {})
2825+ annotations.setdefault("return_type", None)
2826
2827 # Make sure that all parameters exist and that we miss none.
2828 info = method.getSignatureInfo()
2829- defined_params = set(info['optional'])
2830- defined_params.update(info['required'])
2831- exported_params = set(annotations['params'])
2832- exported_params.update(annotations['call_with'])
2833+ defined_params = set(info["optional"])
2834+ defined_params.update(info["required"])
2835+ exported_params = set(annotations["params"])
2836+ exported_params.update(annotations["call_with"])
2837 undefined_params = exported_params.difference(defined_params)
2838- if undefined_params and info['kwargs'] is None:
2839+ if undefined_params and info["kwargs"] is None:
2840 raise TypeError(
2841 'method "%s" doesn\'t have the following exported '
2842- 'parameters in version "%s": %s.' % (
2843- method.__name__, _version_name(version),
2844- ", ".join(sorted(undefined_params))))
2845- missing_params = set(
2846- info['required']).difference(exported_params)
2847+ 'parameters in version "%s": %s.'
2848+ % (
2849+ method.__name__,
2850+ _version_name(version),
2851+ ", ".join(sorted(undefined_params)),
2852+ )
2853+ )
2854+ missing_params = set(info["required"]).difference(exported_params)
2855 if missing_params:
2856 raise TypeError(
2857 'method "%s" is missing definitions for parameter(s) '
2858- 'exported in version "%s": %s' % (
2859- method.__name__, _version_name(version),
2860- ", ".join(sorted(missing_params))))
2861+ 'exported in version "%s": %s'
2862+ % (
2863+ method.__name__,
2864+ _version_name(version),
2865+ ", ".join(sorted(missing_params)),
2866+ )
2867+ )
2868
2869- _update_default_and_required_params(annotations['params'], info)
2870+ _update_default_and_required_params(annotations["params"], info)
2871
2872
2873 def _update_default_and_required_params(params, method_info):
2874 """Set missing default/required based on the method signature."""
2875- optional = method_info['optional']
2876- required = method_info['required']
2877+ optional = method_info["optional"]
2878+ required = method_info["required"]
2879 for name, param_def in params.items():
2880 # If the method parameter is optional and the param didn't have
2881 # a default, set it to the same as the method.
2882@@ -743,8 +817,11 @@ def _update_default_and_required_params(params, method_info):
2883 # zope schema are expected to be unicode, whereas it's
2884 # really possible that the method's default is a simple
2885 # string.
2886- if (six.PY2 and
2887- isinstance(default, str) and IText.providedBy(param_def)):
2888+ if (
2889+ six.PY2
2890+ and isinstance(default, str)
2891+ and IText.providedBy(param_def)
2892+ ):
2893 default = unicode(default) # noqa: F821
2894 param_def.default = default
2895 param_def.required = False
2896@@ -761,12 +838,12 @@ class call_with(_method_annotator):
2897
2898 def __init__(self, **params):
2899 """Specify fixed values for parameters."""
2900- _check_called_from_interface_def('%s()' % self.__class__.__name__)
2901+ _check_called_from_interface_def("%s()" % self.__class__.__name__)
2902 self.params = params
2903
2904 def annotate_method(self, method, annotations):
2905 """See `_method_annotator`."""
2906- annotations['call_with'] = self.params
2907+ annotations["call_with"] = self.params
2908
2909
2910 class mutator_for(_method_annotator):
2911@@ -775,6 +852,7 @@ class mutator_for(_method_annotator):
2912 The method can be invoked through POST, or by setting a value for
2913 the given field as part of a PUT or PATCH request.
2914 """
2915+
2916 def __init__(self, field):
2917 """Specify the field for which this method is a mutator."""
2918 self.field = field
2919@@ -786,16 +864,19 @@ class mutator_for(_method_annotator):
2920 """
2921
2922 if not self.field.readonly:
2923- raise TypeError("Only a read-only field can have a mutator "
2924- "method.")
2925+ raise TypeError(
2926+ "Only a read-only field can have a mutator " "method."
2927+ )
2928
2929 # The mutator method must take only one argument, not counting
2930 # arguments with values fixed by call_with().
2931 free_params = _free_parameters(method, annotations)
2932 if len(free_params) != 1:
2933- raise TypeError("A mutator method must take one and only one "
2934- "non-fixed argument. %s takes %d." %
2935- (method.__name__, len(free_params)))
2936+ raise TypeError(
2937+ "A mutator method must take one and only one "
2938+ "non-fixed argument. %s takes %d."
2939+ % (method.__name__, len(free_params))
2940+ )
2941
2942 # We need to keep mutator annotations in a separate dictionary
2943 # from the field's main annotations because we're not
2944@@ -804,13 +885,15 @@ class mutator_for(_method_annotator):
2945 # version fits into the field's annotations.
2946 version, method_annotations = annotations.stack[-1]
2947 mutator_annotations = self.field.queryTaggedValue(
2948- LAZR_WEBSERVICE_MUTATORS)
2949+ LAZR_WEBSERVICE_MUTATORS
2950+ )
2951 if version in mutator_annotations:
2952 raise TypeError(
2953 "A field can only have one mutator method for version %s; "
2954- "%s makes two." % (_version_name(version), method.__name__ ))
2955+ "%s makes two." % (_version_name(version), method.__name__)
2956+ )
2957 mutator_annotations[version] = (method, dict(method_annotations))
2958- method_annotations['is_mutator'] = True
2959+ method_annotations["is_mutator"] = True
2960
2961
2962 class accessor_for(_method_annotator):
2963@@ -818,6 +901,7 @@ class accessor_for(_method_annotator):
2964
2965 The method can be invoked by accessing the field's URL.
2966 """
2967+
2968 def __init__(self, field):
2969 """Specify the field for which this method is a mutator."""
2970 self.field = field
2971@@ -834,13 +918,15 @@ class accessor_for(_method_annotator):
2972 # field's annotations.
2973 version, method_annotations = annotations.stack[-1]
2974 accessor_annotations = self.field.queryTaggedValue(
2975- LAZR_WEBSERVICE_ACCESSORS)
2976+ LAZR_WEBSERVICE_ACCESSORS
2977+ )
2978 if version in accessor_annotations:
2979 raise TypeError(
2980 "A field can only have one accessor method for version %s; "
2981- "%s makes two." % (_version_name(version), method.__name__ ))
2982+ "%s makes two." % (_version_name(version), method.__name__)
2983+ )
2984 accessor_annotations[version] = (method, dict(method_annotations))
2985- method_annotations['is_accessor'] = True
2986+ method_annotations["is_accessor"] = True
2987
2988
2989 def _free_parameters(method, annotations):
2990@@ -849,8 +935,9 @@ def _free_parameters(method, annotations):
2991 Parameters that have values fixed by call_with() are not free.
2992 """
2993 signature = fromFunction(method).getSignatureInfo()
2994- return (set(signature['required']) -
2995- set(annotations.get('call_with', {}).keys()))
2996+ return set(signature["required"]) - set(
2997+ annotations.get("call_with", {}).keys()
2998+ )
2999
3000
3001 class operation_for_version(_method_annotator):
3002@@ -861,8 +948,9 @@ class operation_for_version(_method_annotator):
3003 their values. Subsequent versions may provide conflicting values,
3004 but those values will not affect this version.
3005 """
3006+
3007 def __init__(self, version):
3008- _check_called_from_interface_def('%s()' % self.__class__.__name__)
3009+ _check_called_from_interface_def("%s()" % self.__class__.__name__)
3010 self.version = version
3011
3012 def annotate_method(self, method, annotations):
3013@@ -881,6 +969,7 @@ class operation_removed_in_version(operation_for_version):
3014 service, or any subsequent version, unless it's re-published with
3015 an export_*_operation method.
3016 """
3017+
3018 def annotate_method(self, method, annotations):
3019 """See `_method_annotator`."""
3020 # The annotations dict is a VersionedDict. Push a new dict
3021@@ -891,19 +980,19 @@ class operation_removed_in_version(operation_for_version):
3022 # We need to set a special 'type' so that lazr.restful can
3023 # easily distinguish a method that's not present in the latest
3024 # version from a method that was incompletely annotated.
3025- annotations['type'] = REMOVED_OPERATION_TYPE
3026+ annotations["type"] = REMOVED_OPERATION_TYPE
3027
3028
3029 class export_operation_as(_method_annotator):
3030 """Decorator specifying the name to export the method as."""
3031
3032 def __init__(self, name):
3033- _check_called_from_interface_def('%s()' % self.__class__.__name__)
3034+ _check_called_from_interface_def("%s()" % self.__class__.__name__)
3035 self.name = name
3036
3037 def annotate_method(self, method, annotations):
3038 """See `_method_annotator`."""
3039- annotations['as'] = self.name
3040+ annotations["as"] = self.name
3041
3042
3043 class rename_parameters_as(_method_annotator):
3044@@ -911,20 +1000,22 @@ class rename_parameters_as(_method_annotator):
3045
3046 def __init__(self, **params):
3047 """params is of the form method_parameter_name=webservice_name."""
3048- _check_called_from_interface_def('%s()' % self.__class__.__name__)
3049+ _check_called_from_interface_def("%s()" % self.__class__.__name__)
3050 self.params = params
3051
3052 def annotate_method(self, method, annotations):
3053 """See `_method_annotator`."""
3054- param_defs = annotations.get('params')
3055+ param_defs = annotations.get("params")
3056 if param_defs is None:
3057 raise TypeError(
3058- '"%s" isn\'t exported on the webservice.' % method.__name__)
3059+ '"%s" isn\'t exported on the webservice.' % method.__name__
3060+ )
3061 for name, export_as in self.params.items():
3062 if name not in param_defs:
3063 raise TypeError(
3064- 'rename_parameters_as(): no "%s" parameter is exported.' %
3065- name)
3066+ 'rename_parameters_as(): no "%s" parameter is exported.'
3067+ % name
3068+ )
3069 param_defs[name].__name__ = export_as
3070
3071
3072@@ -934,24 +1025,25 @@ class operation_parameters(_method_annotator):
3073 The decorator takes a list of `IField` describing the parameters. The name
3074 of the underlying method parameter is taken from the argument name.
3075 """
3076+
3077 def __init__(self, **params):
3078 """params is of the form method_parameter_name=Field()."""
3079- _check_called_from_interface_def('%s()' % self.__class__.__name__)
3080+ _check_called_from_interface_def("%s()" % self.__class__.__name__)
3081 self.params = params
3082
3083 def annotate_method(self, method, annotations):
3084 """See `_method_annotator`."""
3085 # It's possible that another decorator already created the params
3086 # annotation.
3087- params = annotations.setdefault('params', {})
3088+ params = annotations.setdefault("params", {})
3089 for name, param in self.params.items():
3090 if not IField.providedBy(param):
3091 raise TypeError(
3092 'export definition of "%s" in method "%s" must '
3093- 'provide IField: %r' % (name, method.__name__, param))
3094+ "provide IField: %r" % (name, method.__name__, param)
3095+ )
3096 if name in params:
3097- raise TypeError(
3098- "'%s' parameter is already defined." % name)
3099+ raise TypeError("'%s' parameter is already defined." % name)
3100
3101 # By default, parameters are exported under their own name.
3102 param.__name__ = name
3103@@ -967,14 +1059,15 @@ class operation_returns_entry(_method_annotator):
3104 """
3105
3106 def __init__(self, schema):
3107- _check_called_from_interface_def('%s()' % self.__class__.__name__)
3108+ _check_called_from_interface_def("%s()" % self.__class__.__name__)
3109 if not IInterface.providedBy(schema):
3110- raise TypeError('Entry type %s does not provide IInterface.'
3111- % schema)
3112+ raise TypeError(
3113+ "Entry type %s does not provide IInterface." % schema
3114+ )
3115 self.return_type = Reference(schema=schema)
3116
3117 def annotate_method(self, method, annotations):
3118- annotations['return_type'] = self.return_type
3119+ annotations["return_type"] = self.return_type
3120
3121
3122 class operation_returns_collection_of(_method_annotator):
3123@@ -983,16 +1076,18 @@ class operation_returns_collection_of(_method_annotator):
3124 The decorator takes one required argument, "schema", an interface that's
3125 been exported as an entry.
3126 """
3127+
3128 def __init__(self, schema):
3129- _check_called_from_interface_def('%s()' % self.__class__.__name__)
3130+ _check_called_from_interface_def("%s()" % self.__class__.__name__)
3131 if not IInterface.providedBy(schema):
3132- raise TypeError('Collection value type %s does not provide '
3133- 'IInterface.' % schema)
3134- self.return_type = CollectionField(
3135- value_type=Reference(schema=schema))
3136+ raise TypeError(
3137+ "Collection value type %s does not provide "
3138+ "IInterface." % schema
3139+ )
3140+ self.return_type = CollectionField(value_type=Reference(schema=schema))
3141
3142 def annotate_method(self, method, annotations):
3143- annotations['return_type'] = self.return_type
3144+ annotations["return_type"] = self.return_type
3145
3146
3147 class _export_operation(_method_annotator):
3148@@ -1002,16 +1097,17 @@ class _export_operation(_method_annotator):
3149 type = None
3150
3151 def __init__(self):
3152- _check_called_from_interface_def('%s()' % self.__class__.__name__)
3153+ _check_called_from_interface_def("%s()" % self.__class__.__name__)
3154
3155 def annotate_method(self, method, annotations):
3156 """See `_method_annotator`."""
3157- annotations['type'] = self.type
3158+ annotations["type"] = self.type
3159
3160
3161 class export_factory_operation(_export_operation):
3162 """Decorator marking a method as being a factory on the webservice."""
3163- type = 'factory'
3164+
3165+ type = "factory"
3166
3167 def __init__(self, interface, field_names):
3168 """Creates a factory decorator.
3169@@ -1022,26 +1118,30 @@ class export_factory_operation(_export_operation):
3170 are used as parameters by this factory.
3171 """
3172 # pylint: disable-msg=W0231
3173- _check_called_from_interface_def('%s()' % self.__class__.__name__)
3174+ _check_called_from_interface_def("%s()" % self.__class__.__name__)
3175 self.interface = interface
3176 self.params = OrderedDict()
3177 for name in field_names:
3178 field = interface.get(name)
3179 if field is None:
3180- raise TypeError("%s doesn't define '%s'." % (
3181- interface.__name__, name))
3182+ raise TypeError(
3183+ "%s doesn't define '%s'." % (interface.__name__, name)
3184+ )
3185 if not IField.providedBy(field):
3186- raise TypeError("%s.%s doesn't provide IField." % (
3187- interface.__name__, name))
3188+ raise TypeError(
3189+ "%s.%s doesn't provide IField."
3190+ % (interface.__name__, name)
3191+ )
3192 self.params[name] = copy_field(field)
3193
3194 def annotate_method(self, method, annotations):
3195 """See `_method_annotator`."""
3196 super(export_factory_operation, self).annotate_method(
3197- method, annotations)
3198- annotations['creates'] = self.interface
3199- annotations['params'] = self.params
3200- annotations['return_type'] = ObjectLink(schema=self.interface)
3201+ method, annotations
3202+ )
3203+ annotations["creates"] = self.interface
3204+ annotations["params"] = self.params
3205+ annotations["return_type"] = ObjectLink(schema=self.interface)
3206
3207
3208 class cache_for(_method_annotator):
3209@@ -1051,16 +1151,18 @@ class cache_for(_method_annotator):
3210 """Specify the duration, in seconds, of the caching resource."""
3211 if not isinstance(duration, six.integer_types):
3212 raise TypeError(
3213- 'Caching duration should be an integer type, not %s' %
3214- duration.__class__.__name__)
3215+ "Caching duration should be an integer type, not %s"
3216+ % duration.__class__.__name__
3217+ )
3218 if duration <= 0:
3219 raise ValueError(
3220- 'Caching duration should be a positive number: %s' % duration)
3221+ "Caching duration should be a positive number: %s" % duration
3222+ )
3223 self.duration = duration
3224
3225 def annotate_method(self, method, annotations):
3226 """See `_method_annotator`."""
3227- annotations['cache_for'] = self.duration
3228+ annotations["cache_for"] = self.duration
3229
3230
3231 class scoped(_method_annotator):
3232@@ -1076,22 +1178,25 @@ class scoped(_method_annotator):
3233 for scope in scopes:
3234 if not isinstance(scope, six.string_types):
3235 raise TypeError(
3236- 'Scope should be a string type, not %s' %
3237- scope.__class__.__name__)
3238+ "Scope should be a string type, not %s"
3239+ % scope.__class__.__name__
3240+ )
3241 self.scopes = scopes
3242
3243 def annotate_method(self, method, annotations):
3244 """See `_method_annotator`."""
3245- annotations['scopes'] = list(self.scopes)
3246+ annotations["scopes"] = list(self.scopes)
3247
3248
3249 class export_read_operation(_export_operation):
3250 """Decorator marking a method for export as a read operation."""
3251- type = 'read_operation'
3252+
3253+ type = "read_operation"
3254
3255
3256 class export_write_operation(_export_operation):
3257 """Decorator marking a method for export as a write operation."""
3258+
3259 type = "write_operation"
3260
3261
3262@@ -1101,6 +1206,7 @@ class export_destructor_operation(_export_operation):
3263 The method will be invoked when the client sends a DELETE request to
3264 the entry.
3265 """
3266+
3267 type = "destructor"
3268
3269 def annotate_method(self, method, annotation_stack):
3270@@ -1111,37 +1217,44 @@ class export_destructor_operation(_export_operation):
3271 Every version must have a self-consistent set of annotations.
3272 """
3273 super(export_destructor_operation, self).annotate_method(
3274- method, annotation_stack)
3275+ method, annotation_stack
3276+ )
3277 # The mutator method must take no arguments, not counting
3278 # arguments with values fixed by call_with().
3279 for version, annotations in annotation_stack.stack:
3280- if annotations['type'] == REMOVED_OPERATION_TYPE:
3281+ if annotations["type"] == REMOVED_OPERATION_TYPE:
3282 continue
3283 free_params = _free_parameters(method, annotations)
3284 if len(free_params) != 0:
3285 raise TypeError(
3286 "A destructor method must take no non-fixed arguments. "
3287- 'In version %s, the "%s" method takes %d: "%s".' % (
3288- _version_name(version), method.__name__,
3289- len(free_params), '", "'.join(free_params))
3290- )
3291+ 'In version %s, the "%s" method takes %d: "%s".'
3292+ % (
3293+ _version_name(version),
3294+ method.__name__,
3295+ len(free_params),
3296+ '", "'.join(free_params),
3297+ )
3298+ )
3299
3300
3301 def _check_tagged_interface(interface, type):
3302 """Make sure that the interface is exported under the proper type."""
3303 if not isinstance(interface, InterfaceClass):
3304- raise TypeError('not an interface.')
3305+ raise TypeError("not an interface.")
3306
3307 tag = interface.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
3308 if tag is None:
3309 raise TypeError(
3310- "'%s' isn't tagged for webservice export." % interface.__name__)
3311- elif tag['type'] != type:
3312- art = 'a'
3313- if type == 'entry':
3314- art = 'an'
3315+ "'%s' isn't tagged for webservice export." % interface.__name__
3316+ )
3317+ elif tag["type"] != type:
3318+ art = "a"
3319+ if type == "entry":
3320+ art = "an"
3321 raise TypeError(
3322- "'%s' isn't exported as %s %s." % (interface.__name__, art, type))
3323+ "'%s' isn't exported as %s %s." % (interface.__name__, art, type)
3324+ )
3325
3326
3327 def generate_entry_interfaces(interface, contributors=[], *versions):
3328@@ -1155,7 +1268,7 @@ def generate_entry_interfaces(interface, contributors=[], *versions):
3329 as `versions`.
3330 """
3331
3332- _check_tagged_interface(interface, 'entry')
3333+ _check_tagged_interface(interface, "entry")
3334 versions = list(versions)
3335
3336 # Make sure any given version defines only one destructor method.
3337@@ -1163,19 +1276,22 @@ def generate_entry_interfaces(interface, contributors=[], *versions):
3338 for name, method in interface.namesAndDescriptions(True):
3339 if not IMethod.providedBy(method):
3340 continue
3341- method_annotations = method.queryTaggedValue(
3342- LAZR_WEBSERVICE_EXPORTED)
3343+ method_annotations = method.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
3344 if method_annotations is None:
3345 continue
3346 for version, annotations in method_annotations.stack:
3347- if annotations.get('type') == export_destructor_operation.type:
3348+ if annotations.get("type") == export_destructor_operation.type:
3349 destructor = destructor_for_version.get(version)
3350 if destructor is not None:
3351 raise TypeError(
3352- 'An entry can only have one destructor method for '
3353- 'version %s; %s and %s make two.' % (
3354- _version_name(version), method.__name__,
3355- destructor.__name__))
3356+ "An entry can only have one destructor method for "
3357+ "version %s; %s and %s make two."
3358+ % (
3359+ _version_name(version),
3360+ method.__name__,
3361+ destructor.__name__,
3362+ )
3363+ )
3364 destructor_for_version[version] = method
3365
3366 # Build a data set describing this entry, as it appears in each
3367@@ -1191,15 +1307,16 @@ def generate_entry_interfaces(interface, contributors=[], *versions):
3368
3369 # If require_explicit_versions is set, make sure the first version
3370 # to set 'exported' also sets '_as_of_was_used'.
3371- _enforce_explicit_version(
3372- entry_tags, 'Entry "%s": ' % interface.__name__)
3373+ _enforce_explicit_version(entry_tags, 'Entry "%s": ' % interface.__name__)
3374
3375 # Make sure there's one set of entry tags for every version of the
3376 # web service, including versions in which this entry is not
3377 # published.
3378 entry_tags.normalize_for_versions(
3379- versions, {'type': ENTRY_TYPE, 'exported': False},
3380- 'Interface "%s": ' % interface.__name__)
3381+ versions,
3382+ {"type": ENTRY_TYPE, "exported": False},
3383+ 'Interface "%s": ' % interface.__name__,
3384+ )
3385
3386 # Next, we'll normalize each published field. A normalized field
3387 # has a set of annotations for every version in which the entry is
3388@@ -1212,8 +1329,10 @@ def generate_entry_interfaces(interface, contributors=[], *versions):
3389 if tag_stack is None:
3390 # This field is not published at all.
3391 continue
3392- error_message_prefix = (
3393- 'Field "%s" in interface "%s": ' % (name, iface.__name__))
3394+ error_message_prefix = 'Field "%s" in interface "%s": ' % (
3395+ name,
3396+ iface.__name__,
3397+ )
3398 _normalize_field_annotations(field, versions, error_message_prefix)
3399 tags_for_published_fields.append((name, field, tag_stack))
3400
3401@@ -1221,49 +1340,64 @@ def generate_entry_interfaces(interface, contributors=[], *versions):
3402 entry_tags = interface.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
3403 for version in versions:
3404 entry_tags_this_version = entry_tags.dict_for_name(version)
3405- if entry_tags_this_version.get('exported') is False:
3406+ if entry_tags_this_version.get("exported") is False:
3407 # Don't define an entry interface for this version at all.
3408 continue
3409 attrs = {}
3410 for name, field, tag_stack in tags_for_published_fields:
3411 tags = tag_stack.dict_for_name(version)
3412- if tags.get('exported') is False:
3413+ if tags.get("exported") is False:
3414 continue
3415 mutated_by, mutated_by_annotations = tags.get(
3416- 'mutator_annotations', (None, {}))
3417- readonly = (field.readonly and mutated_by is None)
3418- if 'readonly' in tags:
3419- publish_as_readonly = tags.get('readonly')
3420+ "mutator_annotations", (None, {})
3421+ )
3422+ readonly = field.readonly and mutated_by is None
3423+ if "readonly" in tags:
3424+ publish_as_readonly = tags.get("readonly")
3425 if readonly and not publish_as_readonly:
3426 raise TypeError(
3427- ("%s.%s is defined as a read-only field, so you "
3428- "can't just declare it to be read-write in "
3429- "the web service: you must define a mutator.") % (
3430- interface.__name__, field.__name__))
3431+ (
3432+ "%s.%s is defined as a read-only field, so you "
3433+ "can't just declare it to be read-write in "
3434+ "the web service: you must define a mutator."
3435+ )
3436+ % (interface.__name__, field.__name__)
3437+ )
3438 if not readonly and publish_as_readonly:
3439 # The field is read-write internally, but the
3440 # developer wants it to be read-only through the web
3441 # service.
3442 readonly = True
3443- attrs[tags['as']] = copy_field(
3444- field, __name__=tags['as'], readonly=readonly)
3445+ attrs[tags["as"]] = copy_field(
3446+ field, __name__=tags["as"], readonly=readonly
3447+ )
3448 class_name = _versioned_class_name(
3449- "%sEntry" % interface.__name__, version)
3450+ "%sEntry" % interface.__name__, version
3451+ )
3452 entry_interface = InterfaceClass(
3453- class_name, bases=(IEntry, ), attrs=attrs,
3454- __doc__=interface.__doc__, __module__=interface.__module__)
3455+ class_name,
3456+ bases=(IEntry,),
3457+ attrs=attrs,
3458+ __doc__=interface.__doc__,
3459+ __module__=interface.__module__,
3460+ )
3461
3462 versioned_tag = entry_tags.dict_for_name(version)
3463- entry_interface.setTaggedValue(LAZR_WEBSERVICE_NAME, dict(
3464- singular=versioned_tag['singular_name'],
3465- plural=versioned_tag['plural_name'],
3466- publish_web_link=versioned_tag['publish_web_link']))
3467+ entry_interface.setTaggedValue(
3468+ LAZR_WEBSERVICE_NAME,
3469+ dict(
3470+ singular=versioned_tag["singular_name"],
3471+ plural=versioned_tag["plural_name"],
3472+ publish_web_link=versioned_tag["publish_web_link"],
3473+ ),
3474+ )
3475 generated_interfaces.append(VersionedObject(version, entry_interface))
3476 return generated_interfaces
3477
3478
3479 def generate_entry_adapters(
3480- content_interface, contributors, webservice_interfaces):
3481+ content_interface, contributors, webservice_interfaces
3482+):
3483 """Create classes adapting from content_interface to webservice_interfaces.
3484
3485 Unlike with generate_collection_adapter and
3486@@ -1278,7 +1412,7 @@ def generate_entry_adapters(
3487
3488 :param return: A list of 2-tuples (version string, adapter class)
3489 """
3490- _check_tagged_interface(content_interface, 'entry')
3491+ _check_tagged_interface(content_interface, "entry")
3492
3493 # Go through the fields and build up a picture of what this entry looks
3494 # like for every version.
3495@@ -1298,21 +1432,23 @@ def generate_entry_adapters(
3496
3497 # Set '_orig_interfaces' as early as possible as we want that
3498 # attribute (even if empty) on all adapters.
3499- orig_interfaces = adapter_dict.setdefault('_orig_interfaces', {})
3500+ orig_interfaces = adapter_dict.setdefault("_orig_interfaces", {})
3501
3502 # Figure out the mutator for this version and add it to
3503 # this version's adapter class dictionary.
3504- if tags.get('exported') is False:
3505+ if tags.get("exported") is False:
3506 continue
3507 mutator, mutator_annotations = tags.get(
3508- 'mutator_annotations', (None, {}))
3509+ "mutator_annotations", (None, {})
3510+ )
3511 accessor, accessor_annotations = tags.get(
3512- 'accessor_annotations', (None, {}))
3513+ "accessor_annotations", (None, {})
3514+ )
3515
3516 # Always use the field's original_name here as we've combined
3517 # fields from the content interface with fields of the webservice
3518 # interfaces (where they may have different names).
3519- orig_name = tags['original_name']
3520+ orig_name = tags["original_name"]
3521
3522 # Some fields may be provided by an adapter of the content class
3523 # instead of being provided directly by the content class. In
3524@@ -1325,25 +1461,39 @@ def generate_entry_adapters(
3525 if orig_name in contributor:
3526 orig_iface = contributor
3527 assert orig_name in orig_iface, (
3528- "Could not find interface where %s is defined" % orig_name)
3529+ "Could not find interface where %s is defined" % orig_name
3530+ )
3531
3532 if accessor is not None and mutator is not None:
3533 prop = PropertyWithAccessorAndMutator(
3534- orig_name, 'context', accessor,
3535- accessor_annotations, mutator,
3536- mutator_annotations, orig_iface)
3537+ orig_name,
3538+ "context",
3539+ accessor,
3540+ accessor_annotations,
3541+ mutator,
3542+ mutator_annotations,
3543+ orig_iface,
3544+ )
3545 elif mutator is not None and accessor is None:
3546 prop = PropertyWithMutator(
3547- orig_name, 'context', mutator,
3548- mutator_annotations, orig_iface)
3549+ orig_name,
3550+ "context",
3551+ mutator,
3552+ mutator_annotations,
3553+ orig_iface,
3554+ )
3555 elif accessor is not None and mutator is None:
3556 prop = PropertyWithAccessor(
3557- orig_name, 'context', accessor,
3558- accessor_annotations, orig_iface)
3559+ orig_name,
3560+ "context",
3561+ accessor,
3562+ accessor_annotations,
3563+ orig_iface,
3564+ )
3565 else:
3566- prop = _ScopeChecker(orig_name, 'context', orig_iface)
3567+ prop = _ScopeChecker(orig_name, "context", orig_iface)
3568
3569- adapter_dict[tags['as']] = prop
3570+ adapter_dict[tags["as"]] = prop
3571
3572 # A dict mapping field names to the interfaces where they came
3573 # from.
3574@@ -1352,17 +1502,17 @@ def generate_entry_adapters(
3575 adapters = []
3576 for version, webservice_interface in webservice_interfaces:
3577 if not isinstance(webservice_interface, InterfaceClass):
3578- raise TypeError('webservice_interface is not an interface.')
3579+ raise TypeError("webservice_interface is not an interface.")
3580 class_dict = adapters_by_version.get(version, {})
3581
3582 # If this interface doesn't export a single field/operation,
3583 # class_dict will be empty, and so we'll add '_orig_interfaces'
3584 # manually as it should be always available, even if empty.
3585 if class_dict == {}:
3586- class_dict['_orig_interfaces'] = {}
3587+ class_dict["_orig_interfaces"] = {}
3588
3589- class_dict['schema'] = webservice_interface
3590- class_dict['__doc__'] = webservice_interface.__doc__
3591+ class_dict["schema"] = webservice_interface
3592+ class_dict["__doc__"] = webservice_interface.__doc__
3593 # The webservice interface class name already includes the version
3594 # string, so there's no reason to add it again to the end
3595 # of the class name.
3596@@ -1370,7 +1520,8 @@ def generate_entry_adapters(
3597 factory = type(classname, (Entry,), class_dict)
3598 classImplements(factory, webservice_interface)
3599 protect_schema(
3600- factory, webservice_interface, write_permission=CheckerPublic)
3601+ factory, webservice_interface, write_permission=CheckerPublic
3602+ )
3603 adapters.append(VersionedObject(version, factory))
3604 return adapters
3605
3606@@ -1381,7 +1532,8 @@ def params_with_dereferenced_user(params):
3607 for name, value in params.items():
3608 if value is REQUEST_USER:
3609 params[name] = getUtility(
3610- IWebServiceConfiguration).get_request_user()
3611+ IWebServiceConfiguration
3612+ ).get_request_user()
3613 return params
3614
3615
3616@@ -1391,7 +1543,8 @@ def _check_request(context, required_scopes):
3617 See `IWebServiceConfiguration.checkRequest`.
3618 """
3619 check_request = getattr(
3620- getUtility(IWebServiceConfiguration), 'checkRequest', None)
3621+ getUtility(IWebServiceConfiguration), "checkRequest", None
3622+ )
3623 if check_request is not None:
3624 check_request(context, required_scopes)
3625
3626@@ -1424,7 +1577,8 @@ class _AccessorWrapper:
3627 def __get__(self, obj, *args):
3628 """Call the accessor method to get the value."""
3629 params = params_with_dereferenced_user(
3630- self.accessor_annotations.get('call_with', {}))
3631+ self.accessor_annotations.get("call_with", {})
3632+ )
3633 context = getattr(obj, self.contextvar)
3634 if self.adaptation is not None:
3635 context = self.adaptation(context)
3636@@ -1445,7 +1599,8 @@ class _MutatorWrapper:
3637 def __set__(self, obj, new_value):
3638 """Call the mutator method to set the value."""
3639 params = params_with_dereferenced_user(
3640- self.mutator_annotations.get('call_with', {}))
3641+ self.mutator_annotations.get("call_with", {})
3642+ )
3643 context = getattr(obj, self.contextvar)
3644 if self.adaptation is not None:
3645 context = self.adaptation(context)
3646@@ -1459,8 +1614,9 @@ class _MutatorWrapper:
3647 class PropertyWithAccessor(_AccessorWrapper, _ScopeChecker):
3648 """A property with a accessor method."""
3649
3650- def __init__(self, name, context, accessor, accessor_annotations,
3651- adaptation):
3652+ def __init__(
3653+ self, name, context, accessor, accessor_annotations, adaptation
3654+ ):
3655 super(PropertyWithAccessor, self).__init__(name, context, adaptation)
3656 self.accessor = accessor.__name__
3657 self.accessor_annotations = accessor_annotations
3658@@ -1469,21 +1625,32 @@ class PropertyWithAccessor(_AccessorWrapper, _ScopeChecker):
3659 class PropertyWithMutator(_MutatorWrapper, _ScopeChecker):
3660 """A property with a mutator method."""
3661
3662- def __init__(self, name, context, mutator, mutator_annotations,
3663- adaptation):
3664+ def __init__(
3665+ self, name, context, mutator, mutator_annotations, adaptation
3666+ ):
3667 super(PropertyWithMutator, self).__init__(name, context, adaptation)
3668 self.mutator = mutator.__name__
3669 self.mutator_annotations = mutator_annotations
3670
3671
3672-class PropertyWithAccessorAndMutator(_AccessorWrapper, _MutatorWrapper,
3673- Passthrough):
3674+class PropertyWithAccessorAndMutator(
3675+ _AccessorWrapper, _MutatorWrapper, Passthrough
3676+):
3677 """A Property with both an accessor an a mutator."""
3678
3679- def __init__(self, name, context, accessor, accessor_annotations,
3680- mutator, mutator_annotations, adaptation):
3681+ def __init__(
3682+ self,
3683+ name,
3684+ context,
3685+ accessor,
3686+ accessor_annotations,
3687+ mutator,
3688+ mutator_annotations,
3689+ adaptation,
3690+ ):
3691 super(PropertyWithAccessorAndMutator, self).__init__(
3692- name, context, adaptation)
3693+ name, context, adaptation
3694+ )
3695 self.accessor = accessor.__name__
3696 self.accessor_annotations = accessor_annotations
3697 self.mutator = mutator.__name__
3698@@ -1510,9 +1677,11 @@ class CollectionEntrySchema:
3699 else:
3700 request = instance.request
3701 request_interface = getUtility(
3702- IWebServiceVersion, name=request.version)
3703+ IWebServiceVersion, name=request.version
3704+ )
3705 entry_class = getGlobalSiteManager().adapters.lookup(
3706- (self.model_schema, request_interface), IEntry)
3707+ (self.model_schema, request_interface), IEntry
3708+ )
3709 if entry_class is None:
3710 return None
3711 return EntryAdapterUtility(entry_class).entry_interface
3712@@ -1534,24 +1703,27 @@ class BaseCollectionAdapter(Collection):
3713
3714 def generate_collection_adapter(interface, version=None):
3715 """Create a class adapting from interface to ICollection."""
3716- _check_tagged_interface(interface, 'collection')
3717+ _check_tagged_interface(interface, "collection")
3718
3719 tag = interface.getTaggedValue(LAZR_WEBSERVICE_EXPORTED)
3720- default_content_by_version = tag['collection_default_content']
3721- assert (version in default_content_by_version), (
3722- "'%s' isn't tagged for export to web service "
3723- "version '%s'." % (interface.__name__, version))
3724+ default_content_by_version = tag["collection_default_content"]
3725+ assert (
3726+ version in default_content_by_version
3727+ ), "'%s' isn't tagged for export to web service " "version '%s'." % (
3728+ interface.__name__,
3729+ version,
3730+ )
3731 method_name, params = default_content_by_version[version]
3732- entry_schema = tag['collection_entry_schema']
3733+ entry_schema = tag["collection_entry_schema"]
3734 class_dict = {
3735- 'entry_schema' : CollectionEntrySchema(entry_schema),
3736- 'method_name': method_name,
3737- 'params': params,
3738- '__doc__': interface.__doc__,
3739- }
3740+ "entry_schema": CollectionEntrySchema(entry_schema),
3741+ "method_name": method_name,
3742+ "params": params,
3743+ "__doc__": interface.__doc__,
3744+ }
3745 classname = _versioned_class_name(
3746- "%sCollectionAdapter" % interface.__name__[1:],
3747- version)
3748+ "%sCollectionAdapter" % interface.__name__[1:], version
3749+ )
3750 factory = type(classname, (BaseCollectionAdapter,), class_dict)
3751
3752 protect_schema(factory, ICollection)
3753@@ -1575,16 +1747,18 @@ class BaseResourceOperationAdapter(ResourceOperation):
3754 # Handle renames.
3755 renames = dict(
3756 (param_def.__name__, orig_name)
3757- for orig_name, param_def in self._export_info['params'].items()
3758- if param_def.__name__ != orig_name)
3759+ for orig_name, param_def in self._export_info["params"].items()
3760+ if param_def.__name__ != orig_name
3761+ )
3762 params = OrderedDict()
3763 for name, value in kwargs.items():
3764 name = renames.get(name, name)
3765 params[name] = value
3766
3767 # Handle fixed parameters.
3768- params.update(params_with_dereferenced_user(
3769- self._export_info['call_with']))
3770+ params.update(
3771+ params_with_dereferenced_user(self._export_info["call_with"])
3772+ )
3773 return params
3774
3775 def call(self, **kwargs):
3776@@ -1593,13 +1767,15 @@ class BaseResourceOperationAdapter(ResourceOperation):
3777
3778 # For responses to GET requests, tell the client to cache the
3779 # response.
3780- if (IResourceGETOperation.providedBy(self)
3781- and 'cache_for' in self._export_info):
3782+ if (
3783+ IResourceGETOperation.providedBy(self)
3784+ and "cache_for" in self._export_info
3785+ ):
3786 self.request.response.setHeader(
3787- 'Cache-control', 'max-age=%i'
3788- % self._export_info['cache_for'])
3789+ "Cache-control", "max-age=%i" % self._export_info["cache_for"]
3790+ )
3791
3792- _check_request(self.context, self._export_info.get('scopes', []))
3793+ _check_request(self.context, self._export_info.get("scopes", []))
3794 result = self._getMethod()(**params)
3795 return self.encodeResult(result)
3796
3797@@ -1617,8 +1793,8 @@ class BaseFactoryResourceOperationAdapter(BaseResourceOperationAdapter):
3798 result = self._getMethod()(**params)
3799 response = self.request.response
3800 response.setStatus(201)
3801- response.setHeader('Location', absoluteURL(result, self.request))
3802- return u''
3803+ response.setHeader("Location", absoluteURL(result, self.request))
3804+ return u""
3805
3806
3807 def generate_operation_adapter(method, version=None):
3808@@ -1635,11 +1811,14 @@ def generate_operation_adapter(method, version=None):
3809 tag = method.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
3810 if tag is None:
3811 raise TypeError(
3812- "'%s' isn't tagged for webservice export." % method.__name__)
3813+ "'%s' isn't tagged for webservice export." % method.__name__
3814+ )
3815 match = tag.dict_for_name(version)
3816 if match is None:
3817- raise AssertionError("'%s' isn't tagged for export to web service "
3818- "version '%s'" % (method.__name__, version))
3819+ raise AssertionError(
3820+ "'%s' isn't tagged for export to web service "
3821+ "version '%s'" % (method.__name__, version)
3822+ )
3823 config = getUtility(IWebServiceConfiguration)
3824 if version is None:
3825 # This code path is not currently used except in a test, since
3826@@ -1652,41 +1831,43 @@ def generate_operation_adapter(method, version=None):
3827 'version "%s", but the service configuration requires '
3828 "all version declarations to be explicit. You should add "
3829 '@operation_for_version("%s") to the bottom of the '
3830- 'annotation stack.' % (method.__name__, version, version))
3831+ "annotation stack." % (method.__name__, version, version)
3832+ )
3833
3834- bases = (BaseResourceOperationAdapter, )
3835- operation_type = match['type']
3836- if operation_type == 'read_operation':
3837- prefix = 'GET'
3838+ bases = (BaseResourceOperationAdapter,)
3839+ operation_type = match["type"]
3840+ if operation_type == "read_operation":
3841+ prefix = "GET"
3842 provides = IResourceGETOperation
3843- elif operation_type in ('factory', 'write_operation'):
3844+ elif operation_type in ("factory", "write_operation"):
3845 provides = IResourcePOSTOperation
3846- prefix = 'POST'
3847- if operation_type == 'factory':
3848+ prefix = "POST"
3849+ if operation_type == "factory":
3850 bases = (BaseFactoryResourceOperationAdapter,)
3851- elif operation_type == 'destructor':
3852+ elif operation_type == "destructor":
3853 provides = IResourceDELETEOperation
3854- prefix = 'DELETE'
3855+ prefix = "DELETE"
3856 else:
3857- raise AssertionError('Unknown method export type: %s' % operation_type)
3858+ raise AssertionError("Unknown method export type: %s" % operation_type)
3859
3860- return_type = match['return_type']
3861- scopes = match.get('scopes') or []
3862+ return_type = match["return_type"]
3863+ scopes = match.get("scopes") or []
3864
3865 name = _versioned_class_name(
3866- '%s_%s_%s' % (prefix, method.interface.__name__, match['as']),
3867- version)
3868+ "%s_%s_%s" % (prefix, method.interface.__name__, match["as"]), version
3869+ )
3870 class_dict = {
3871- 'params': tuple(match['params'].values()),
3872- 'return_type': return_type,
3873- 'scopes': tuple(scopes),
3874- '_orig_iface': method.interface,
3875- '_export_info': match,
3876- '_method_name': method.__name__,
3877- '__doc__': method.__doc__}
3878-
3879- if operation_type == 'write_operation':
3880- class_dict['send_modification_event'] = True
3881+ "params": tuple(match["params"].values()),
3882+ "return_type": return_type,
3883+ "scopes": tuple(scopes),
3884+ "_orig_iface": method.interface,
3885+ "_export_info": match,
3886+ "_method_name": method.__name__,
3887+ "__doc__": method.__doc__,
3888+ }
3889+
3890+ if operation_type == "write_operation":
3891+ class_dict["send_modification_event"] = True
3892 factory = type(name, bases, class_dict)
3893 classImplements(factory, provides)
3894 protect_schema(factory, provides)
3895@@ -1694,7 +1875,7 @@ def generate_operation_adapter(method, version=None):
3896 return factory
3897
3898
3899-def _normalize_field_annotations(field, versions, error_prefix=''):
3900+def _normalize_field_annotations(field, versions, error_prefix=""):
3901 """Make sure a field has annotations for every published version.
3902
3903 If a field lacks annotations for a given version, it will not show
3904@@ -1720,8 +1901,11 @@ def _normalize_field_annotations(field, versions, error_prefix=''):
3905 earliest_version = versions[0]
3906 stack = versioned_dict.stack
3907
3908- if (len(stack) >= 2 and stack[0].version is None
3909- and stack[1].version == earliest_version):
3910+ if (
3911+ len(stack) >= 2
3912+ and stack[0].version is None
3913+ and stack[1].version == earliest_version
3914+ ):
3915 # The behavior of the earliest version is defined with keyword
3916 # arguments, but the first explicitly-defined version also
3917 # refers to the earliest version. We need to consolidate the
3918@@ -1739,15 +1923,15 @@ def _normalize_field_annotations(field, versions, error_prefix=''):
3919 if implicit_value == value:
3920 # The two values are in sync.
3921 continue
3922- if key == 'as' and implicit_value == field.__name__:
3923+ if key == "as" and implicit_value == field.__name__:
3924 # The implicit value was set by the system, not by the
3925 # user. The later value will simply take precedence.
3926 continue
3927 raise ValueError(
3928 error_prefix + 'Annotation "%s" has conflicting values '
3929 'for the earliest version: "%s" (from keyword arguments) '
3930- 'and "%s" (defined explicitly).' % (
3931- key, implicit_value, value))
3932+ 'and "%s" (defined explicitly).' % (key, implicit_value, value)
3933+ )
3934 stack[0].object.update(stack[1].object)
3935 stack.remove(stack[1])
3936
3937@@ -1765,61 +1949,74 @@ def _normalize_field_annotations(field, versions, error_prefix=''):
3938 # the normal tag stack.
3939 implicit_earliest_mutator = mutator_annotations.get(None, None)
3940 explicit_earliest_mutator = mutator_annotations.get(earliest_version, None)
3941- if (implicit_earliest_mutator is not None
3942- and explicit_earliest_mutator is not None):
3943+ if (
3944+ implicit_earliest_mutator is not None
3945+ and explicit_earliest_mutator is not None
3946+ ):
3947 raise ValueError(
3948 error_prefix + " Both implicit and explicit mutator definitions "
3949- "found for earliest version %s." % earliest_version)
3950+ "found for earliest version %s." % earliest_version
3951+ )
3952 earliest_mutator = implicit_earliest_mutator or explicit_earliest_mutator
3953 if earliest_mutator is not None:
3954- stack[0].object['mutator_annotations'] = earliest_mutator
3955+ stack[0].object["mutator_annotations"] = earliest_mutator
3956
3957 # Make sure there is at most one accessor for the earliest version.
3958 # If there is one, move it from the accessor-specific dictionary to
3959 # the normal tag stack.
3960 implicit_earliest_accessor = accessor_annotations.get(None, None)
3961 explicit_earliest_accessor = accessor_annotations.get(
3962- earliest_version, None)
3963- if (implicit_earliest_accessor is not None
3964- and explicit_earliest_accessor is not None):
3965+ earliest_version, None
3966+ )
3967+ if (
3968+ implicit_earliest_accessor is not None
3969+ and explicit_earliest_accessor is not None
3970+ ):
3971 raise ValueError(
3972 error_prefix + " Both implicit and explicit accessor definitions "
3973- "found for earliest version %s." % earliest_version)
3974+ "found for earliest version %s." % earliest_version
3975+ )
3976 earliest_accessor = (
3977- implicit_earliest_accessor or explicit_earliest_accessor)
3978+ implicit_earliest_accessor or explicit_earliest_accessor
3979+ )
3980 if earliest_accessor is not None:
3981- stack[0].object['accessor_annotations'] = earliest_accessor
3982+ stack[0].object["accessor_annotations"] = earliest_accessor
3983
3984 # Fill out the stack so that there is one set of tags for each
3985 # version.
3986 versioned_dict.normalize_for_versions(
3987- versions, dict(exported=False), error_prefix)
3988+ versions, dict(exported=False), error_prefix
3989+ )
3990
3991 # Make sure that a mutator defined in version N is inherited in
3992 # version N+1.
3993 most_recent_mutator_tags = earliest_mutator
3994 for version in versions[1:]:
3995 most_recent_mutator_tags = mutator_annotations.get(
3996- version, most_recent_mutator_tags)
3997+ version, most_recent_mutator_tags
3998+ )
3999 # Install a (possibly inherited) mutator for this field in
4000 # this version.
4001 if most_recent_mutator_tags is not None:
4002 tags_for_version = versioned_dict.dict_for_name(version)
4003- tags_for_version['mutator_annotations'] = copy.deepcopy(
4004- most_recent_mutator_tags)
4005+ tags_for_version["mutator_annotations"] = copy.deepcopy(
4006+ most_recent_mutator_tags
4007+ )
4008
4009 # Make sure that a accessor defined in version N is inherited in
4010 # version N+1.
4011 most_recent_accessor_tags = earliest_accessor
4012 for version in versions[1:]:
4013 most_recent_accessor_tags = accessor_annotations.get(
4014- version, most_recent_accessor_tags)
4015+ version, most_recent_accessor_tags
4016+ )
4017 # Install a (possibly inherited) accessor for this field in
4018 # this version.
4019 if most_recent_accessor_tags is not None:
4020 tags_for_version = versioned_dict.dict_for_name(version)
4021- tags_for_version['accessor_annotations'] = copy.deepcopy(
4022- most_recent_accessor_tags)
4023+ tags_for_version["accessor_annotations"] = copy.deepcopy(
4024+ most_recent_accessor_tags
4025+ )
4026
4027 return field
4028
4029@@ -1839,12 +2036,13 @@ def _enforce_explicit_version(versioned_dict, error_prefix):
4030 return
4031
4032 for version, annotations in versioned_dict.stack:
4033- if annotations.get('exported', False):
4034- if not annotations.get('_as_of_was_used', False):
4035+ if annotations.get("exported", False):
4036+ if not annotations.get("_as_of_was_used", False):
4037 raise ValueError(
4038 error_prefix + "Exported in version %s, but not"
4039 " by using as_of. The service configuration"
4040- " requires that you use as_of." % version)
4041+ " requires that you use as_of." % version
4042+ )
4043 break
4044
4045
4046diff --git a/src/lazr/restful/directives/__init__.py b/src/lazr/restful/directives/__init__.py
4047index c695ea7..4db78d1 100644
4048--- a/src/lazr/restful/directives/__init__.py
4049+++ b/src/lazr/restful/directives/__init__.py
4050@@ -5,9 +5,10 @@
4051 from __future__ import absolute_import, print_function
4052
4053 __metaclass__ = type
4054-__all__ = ['request_class',
4055- 'publication_class',
4056- 'settings',
4057+__all__ = [
4058+ "request_class",
4059+ "publication_class",
4060+ "settings",
4061 ]
4062
4063 import martian
4064@@ -18,13 +19,21 @@ from zope.traversing.browser import AbsoluteURL
4065 from zope.traversing.browser.interfaces import IAbsoluteURL
4066
4067 from lazr.restful.interfaces import (
4068- IServiceRootResource, IWebServiceClientRequest, IWebServiceConfiguration,
4069- IWebServiceLayer)
4070+ IServiceRootResource,
4071+ IWebServiceClientRequest,
4072+ IWebServiceConfiguration,
4073+ IWebServiceLayer,
4074+)
4075 from lazr.restful._resource import (
4076- register_versioned_request_utility, ServiceRootResource)
4077+ register_versioned_request_utility,
4078+ ServiceRootResource,
4079+)
4080 from lazr.restful.simple import (
4081- BaseWebServiceConfiguration, Publication, Request,
4082- RootResourceAbsoluteURL)
4083+ BaseWebServiceConfiguration,
4084+ Publication,
4085+ Request,
4086+ RootResourceAbsoluteURL,
4087+)
4088 from lazr.restful.utils import make_identifier_safe
4089
4090
4091@@ -33,6 +42,7 @@ class request_class(martian.Directive):
4092
4093 The default is lazr.restful.simple.Request.
4094 """
4095+
4096 scope = martian.CLASS
4097 store = martian.ONCE
4098 default = Request
4099@@ -43,6 +53,7 @@ class publication_class(martian.Directive):
4100
4101 The default is lazr.restful.simple.Publication.
4102 """
4103+
4104 scope = martian.CLASS
4105 store = martian.ONCE
4106 default = Publication
4107@@ -69,15 +80,16 @@ class ConfigurationGrokker(martian.ClassGrokker):
4108 version defined in the configuration, and registers each as an
4109 IWebServiceVersion utility.
4110 """
4111+
4112 martian.component(BaseWebServiceConfiguration)
4113 martian.directive(request_class)
4114 martian.directive(publication_class)
4115
4116- def execute(self, cls, config, request_class,
4117- publication_class, *kw):
4118+ def execute(self, cls, config, request_class, publication_class, *kw):
4119 # If implementations of the IWebServiceConfiguration methods are
4120 # missing, create them from the declarations.
4121- if not getattr(cls, 'createRequest', None):
4122+ if not getattr(cls, "createRequest", None):
4123+
4124 def createRequest(self, body_instream, environ):
4125 """See `IWebServiceConfiguration`."""
4126 request = request_class(body_instream, environ)
4127@@ -88,11 +100,14 @@ class ConfigurationGrokker(martian.ClassGrokker):
4128 # WebServiceRequestTraversal._removeVirtualHostTraversals().
4129 request.setPublication(publication_class(None))
4130 return request
4131+
4132 cls.createRequest = createRequest
4133
4134- if not getattr(cls, 'get_request_user', None):
4135+ if not getattr(cls, "get_request_user", None):
4136+
4137 def get_request_user(self):
4138 return None
4139+
4140 cls.get_request_user = get_request_user
4141
4142 # Register as utility.
4143@@ -102,10 +117,10 @@ class ConfigurationGrokker(martian.ClassGrokker):
4144
4145 # Create and register marker interfaces for request objects.
4146 superclass = IWebServiceClientRequest
4147- for version in (utility.active_versions):
4148+ for version in utility.active_versions:
4149 name_part = make_identifier_safe(version)
4150- if not name_part.startswith('_'):
4151- name_part = '_' + name_part
4152+ if not name_part.startswith("_"):
4153+ name_part = "_" + name_part
4154 classname = "IWebServiceClientRequestVersion" + name_part
4155 marker_interface = InterfaceClass(classname, (superclass,), {})
4156 register_versioned_request_utility(marker_interface, version)
4157@@ -115,6 +130,7 @@ class ConfigurationGrokker(martian.ClassGrokker):
4158
4159 class ServiceRootGrokker(martian.ClassGrokker):
4160 """Registers your service root as a singleton object."""
4161+
4162 martian.component(ServiceRootResource)
4163
4164 def execute(self, cls, config, *kw):
4165@@ -128,6 +144,7 @@ class RootResourceAbsoluteURLGrokker(martian.ClassGrokker):
4166 In most cases you can simply create an empty subclass of
4167 RootResourceAbsoluteURL.
4168 """
4169+
4170 martian.component(RootResourceAbsoluteURL)
4171
4172 def execute(self, cls, config, *kw):
4173@@ -140,6 +157,7 @@ class location_interface(martian.Directive):
4174
4175 The default is zope.location.interfaces.ILocation
4176 """
4177+
4178 scope = martian.CLASS
4179 store = martian.ONCE
4180 default = ILocation
4181@@ -151,10 +169,12 @@ class AbsoluteURLGrokker(martian.ClassGrokker):
4182 In most cases you can simply create an empty subclass of
4183 AbsoluteURL.
4184 """
4185+
4186 martian.component(AbsoluteURL)
4187 martian.directive(location_interface)
4188
4189 def execute(self, cls, config, location_interface, *kw):
4190 getSiteManager().registerAdapter(
4191- cls, (location_interface, IWebServiceLayer), IAbsoluteURL)
4192+ cls, (location_interface, IWebServiceLayer), IAbsoluteURL
4193+ )
4194 return True
4195diff --git a/src/lazr/restful/docs/conf.py b/src/lazr/restful/docs/conf.py
4196index 5a5b367..bd3dea3 100644
4197--- a/src/lazr/restful/docs/conf.py
4198+++ b/src/lazr/restful/docs/conf.py
4199@@ -35,21 +35,21 @@ from lazr.restful import __version__
4200 extensions = []
4201
4202 # Add any paths that contain templates here, relative to this directory.
4203-templates_path = ['_templates']
4204+templates_path = ["_templates"]
4205
4206 # The suffix(es) of source filenames.
4207 # You can specify multiple suffix as a list of string:
4208 #
4209 # source_suffix = ['.rst', '.md']
4210-source_suffix = '.rst'
4211+source_suffix = ".rst"
4212
4213 # The master toctree document.
4214-master_doc = 'index'
4215+master_doc = "index"
4216
4217 # General information about the project.
4218-project = u'lazr.restful'
4219-copyright = u'2004-2019, Canonical Ltd.'
4220-author = u'LAZR Developers <lazr-developers@lists.launchpad.net>'
4221+project = u"lazr.restful"
4222+copyright = u"2004-2019, Canonical Ltd."
4223+author = u"LAZR Developers <lazr-developers@lists.launchpad.net>"
4224
4225 # The version info for the project you're documenting, acts as replacement for
4226 # |version| and |release|, also used in various other places throughout the
4227@@ -70,10 +70,10 @@ language = None
4228 # List of patterns, relative to source directory, that match files and
4229 # directories to ignore when looking for source files.
4230 # This patterns also effect to html_static_path and html_extra_path
4231-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
4232+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
4233
4234 # The name of the Pygments (syntax highlighting) style to use.
4235-pygments_style = 'sphinx'
4236+pygments_style = "sphinx"
4237
4238 # If true, `todo` and `todoList` produce output, else they produce nothing.
4239 todo_include_todos = False
4240@@ -84,7 +84,7 @@ todo_include_todos = False
4241 # The theme to use for HTML and HTML Help pages. See the documentation for
4242 # a list of builtin themes.
4243 #
4244-html_theme = 'alabaster'
4245+html_theme = "alabaster"
4246
4247 # Theme options are theme-specific and customize the look and feel of a theme
4248 # further. For a list of options available for each theme, see the
4249@@ -103,9 +103,9 @@ html_static_path = []
4250 # This is required for the alabaster theme
4251 # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
4252 html_sidebars = {
4253- '**': [
4254- 'relations.html', # needs 'show_related': True theme option to display
4255- 'searchbox.html',
4256+ "**": [
4257+ "relations.html", # needs 'show_related': True theme option to display
4258+ "searchbox.html",
4259 ]
4260 }
4261
4262@@ -113,7 +113,7 @@ html_sidebars = {
4263 # -- Options for HTMLHelp output ------------------------------------------
4264
4265 # Output file base name for HTML help builder.
4266-htmlhelp_basename = 'lazrrestfuldoc'
4267+htmlhelp_basename = "lazrrestfuldoc"
4268
4269
4270 # -- Options for LaTeX output ---------------------------------------------
4271@@ -122,15 +122,12 @@ latex_elements = {
4272 # The paper size ('letterpaper' or 'a4paper').
4273 #
4274 # 'papersize': 'letterpaper',
4275-
4276 # The font size ('10pt', '11pt' or '12pt').
4277 #
4278 # 'pointsize': '10pt',
4279-
4280 # Additional stuff for the LaTeX preamble.
4281 #
4282 # 'preamble': '',
4283-
4284 # Latex figure (float) alignment
4285 #
4286 # 'figure_align': 'htbp',
4287@@ -140,8 +137,13 @@ latex_elements = {
4288 # (source start file, target name, title,
4289 # author, documentclass [howto, manual, or own class]).
4290 latex_documents = [
4291- (master_doc, 'lazrrestful.tex', u'lazr.restful Documentation',
4292- u'LAZR Developers \\textless{}lazr-developers@lists.launchpad.net\\textgreater{}', 'manual'),
4293+ (
4294+ master_doc,
4295+ "lazrrestful.tex",
4296+ u"lazr.restful Documentation",
4297+ u"LAZR Developers \\textless{}lazr-developers@lists.launchpad.net\\textgreater{}", # noqa: E501
4298+ "manual",
4299+ ),
4300 ]
4301
4302
4303@@ -150,8 +152,7 @@ latex_documents = [
4304 # One entry per manual page. List of tuples
4305 # (source start file, name, description, authors, manual section).
4306 man_pages = [
4307- (master_doc, 'lazrrestful', u'lazr.restful Documentation',
4308- [author], 1)
4309+ (master_doc, "lazrrestful", u"lazr.restful Documentation", [author], 1)
4310 ]
4311
4312
4313@@ -161,7 +162,13 @@ man_pages = [
4314 # (source start file, target name, title, author,
4315 # dir menu entry, description, category)
4316 texinfo_documents = [
4317- (master_doc, 'lazrrestful', u'lazr.restful Documentation',
4318- author, 'lazrrestful', 'One line description of project.',
4319- 'Miscellaneous'),
4320+ (
4321+ master_doc,
4322+ "lazrrestful",
4323+ u"lazr.restful Documentation",
4324+ author,
4325+ "lazrrestful",
4326+ "One line description of project.",
4327+ "Miscellaneous",
4328+ ),
4329 ]
4330diff --git a/src/lazr/restful/error.py b/src/lazr/restful/error.py
4331index 389c2b8..86cef16 100644
4332--- a/src/lazr/restful/error.py
4333+++ b/src/lazr/restful/error.py
4334@@ -6,14 +6,14 @@ from __future__ import absolute_import, division, print_function
4335
4336 __metaclass__ = type
4337 __all__ = [
4338- 'ClientErrorView',
4339- 'expose',
4340- 'NotFoundView',
4341- 'RequestExpiredView',
4342- 'SystemErrorView',
4343- 'UnauthorizedView',
4344- 'WebServiceExceptionView',
4345- ]
4346+ "ClientErrorView",
4347+ "expose",
4348+ "NotFoundView",
4349+ "RequestExpiredView",
4350+ "SystemErrorView",
4351+ "UnauthorizedView",
4352+ "WebServiceExceptionView",
4353+]
4354
4355 import traceback
4356
4357@@ -28,16 +28,18 @@ except ImportError:
4358 from zope.interface import Interface
4359
4360 class ISystemErrorView(Interface):
4361- """Error views that can classify their contexts as system errors
4362- """
4363+ """Error views that can classify their contexts as system errors"""
4364
4365 def isSystemError():
4366- """Return a boolean indicating whether the error is a system error"""
4367+ """
4368+ Return a boolean indicating whether the error is a system error
4369+ """
4370+
4371
4372 from lazr.restful.interfaces import (
4373 IWebServiceConfiguration,
4374 IWebServiceExceptionView,
4375- )
4376+)
4377
4378
4379 @implementer(IWebServiceExceptionView)
4380@@ -55,7 +57,7 @@ class WebServiceExceptionView:
4381 By default, use the __lazr_webservice_error__ attribute on
4382 the exception.
4383 """
4384- return getattr(self.context, '__lazr_webservice_error__', 500)
4385+ return getattr(self.context, "__lazr_webservice_error__", 500)
4386
4387 def isSystemError(self):
4388 """See `ISystemErrorView`."""
4389@@ -65,14 +67,13 @@ class WebServiceExceptionView:
4390 """Generate the HTTP response describing the exception."""
4391 response = self.request.response
4392 response.setStatus(self.status)
4393- response.setHeader('Content-Type', 'text/plain')
4394- if getattr(self.request, 'oopsid', None) is not None:
4395- response.setHeader('X-Lazr-OopsId', self.request.oopsid)
4396+ response.setHeader("Content-Type", "text/plain")
4397+ if getattr(self.request, "oopsid", None) is not None:
4398+ response.setHeader("X-Lazr-OopsId", self.request.oopsid)
4399
4400- show_tracebacks = getUtility(
4401- IWebServiceConfiguration).show_tracebacks
4402+ show_tracebacks = getUtility(IWebServiceConfiguration).show_tracebacks
4403 server_error = self.status // 100 == 5
4404- if (not show_tracebacks and server_error):
4405+ if not show_tracebacks and server_error:
4406 # Don't show the exception message; it might contain
4407 # private information.
4408 result = [self.context.__class__.__name__]
4409@@ -81,10 +82,10 @@ class WebServiceExceptionView:
4410 result = [str(self.context)]
4411
4412 if show_tracebacks and server_error:
4413- result.append('\n\n')
4414+ result.append("\n\n")
4415 result.append(traceback.format_exc())
4416
4417- return ''.join(result)
4418+ return "".join(result)
4419
4420
4421 # lazr/restful/configure.zcml registers these classes as adapter
4422@@ -123,15 +124,18 @@ class RequestExpiredView(WebServiceExceptionView):
4423
4424
4425 def expose(exception, status=400):
4426- """Tell lazr.restful to deliver details about the exception to the client.
4427+ """
4428+ Tell lazr.restful to deliver details about the exception to the client.
4429 """
4430 # Since Python lets us raise exception types without instantiating them
4431 # (like "raise RuntimeError" instead of "raise RuntimeError()", we want to
4432 # make sure the caller doesn't get confused and try that with us.
4433 if not isinstance(exception, BaseException):
4434- raise ValueError('This function only accepts exception instances. '
4435- 'Use the @error_status decorator to publish an '
4436- 'exception class as a web service error.')
4437+ raise ValueError(
4438+ "This function only accepts exception instances. "
4439+ "Use the @error_status decorator to publish an "
4440+ "exception class as a web service error."
4441+ )
4442
4443 exception.__lazr_webservice_error__ = status
4444
4445diff --git a/src/lazr/restful/example/base/filemanager.py b/src/lazr/restful/example/base/filemanager.py
4446index 8e03049..fccb0fc 100644
4447--- a/src/lazr/restful/example/base/filemanager.py
4448+++ b/src/lazr/restful/example/base/filemanager.py
4449@@ -5,16 +5,18 @@
4450 from __future__ import absolute_import, print_function
4451
4452 __metaclass__ = type
4453-__all__ = ['FileManager',
4454- 'ManagedFileResource']
4455+__all__ = ["FileManager", "ManagedFileResource"]
4456
4457 import datetime
4458+
4459 # Import SHA in a way compatible with both Python 2.4 and Python 2.6.
4460 try:
4461 import hashlib
4462+
4463 sha_constructor = hashlib.sha1
4464 except ImportError:
4465 import sha
4466+
4467 sha_constructor = sha.new
4468
4469 from zope.interface import implementer
4470@@ -28,7 +30,6 @@ from lazr.restful.utils import get_current_web_service_request
4471
4472 @implementer(IFileManager)
4473 class FileManager:
4474-
4475 def __init__(self):
4476 """Initialize with an empty list of files."""
4477 self.files = {}
4478@@ -43,7 +44,8 @@ class FileManager:
4479 id = str(self.counter)
4480 self.counter += 1
4481 self.files[id] = ManagedFileResource(
4482- representation, mediaType, filename, datetime.datetime.now())
4483+ representation, mediaType, filename, datetime.datetime.now()
4484+ )
4485 return id
4486
4487 def delete(self, key):
4488@@ -56,7 +58,6 @@ grokcore.component.global_utility(FileManager)
4489
4490
4491 class ManagedFileResource(ReadOnlyResource):
4492-
4493 def __init__(self, representation, mediaType, filename, last_modified):
4494 """Initialize with a file to be managed."""
4495 self.representation = representation
4496@@ -73,16 +74,15 @@ class ManagedFileResource(ReadOnlyResource):
4497 response = request.response
4498
4499 # Handle a conditional request
4500- incoming_etag = request.getHeader('If-None-Match')
4501+ incoming_etag = request.getHeader("If-None-Match")
4502 if incoming_etag == self.etag:
4503 response.setStatus(304)
4504 return
4505
4506 response.setHeader("Content-Type", self.mediaType)
4507- response.setHeader(
4508- "Last-Modified", self.last_modified.isoformat())
4509+ response.setHeader("Last-Modified", self.last_modified.isoformat())
4510 response.setHeader("ETag", self.etag)
4511 response.setHeader(
4512- "Content-Disposition",
4513- 'attachment; filename="%s"' % self.filename)
4514+ "Content-Disposition", 'attachment; filename="%s"' % self.filename
4515+ )
4516 return self.representation
4517diff --git a/src/lazr/restful/example/base/interfaces.py b/src/lazr/restful/example/base/interfaces.py
4518index afd6b34..b6d7d85 100644
4519--- a/src/lazr/restful/example/base/interfaces.py
4520+++ b/src/lazr/restful/example/base/interfaces.py
4521@@ -6,19 +6,21 @@
4522 from __future__ import absolute_import, print_function
4523
4524 __metaclass__ = type
4525-__all__ = ['AlreadyNew',
4526- 'Cuisine',
4527- 'ICookbook',
4528- 'ICookbookSet',
4529- 'ICookbookSubclass',
4530- 'IDish',
4531- 'IDishSet',
4532- 'IFileManager',
4533- 'IHasGet',
4534- 'IFileManagerBackedByteStorage',
4535- 'IRecipe',
4536- 'IRecipeSet',
4537- 'NameAlreadyTaken']
4538+__all__ = [
4539+ "AlreadyNew",
4540+ "Cuisine",
4541+ "ICookbook",
4542+ "ICookbookSet",
4543+ "ICookbookSubclass",
4544+ "IDish",
4545+ "IDishSet",
4546+ "IFileManager",
4547+ "IHasGet",
4548+ "IFileManagerBackedByteStorage",
4549+ "IRecipe",
4550+ "IRecipeSet",
4551+ "NameAlreadyTaken",
4552+]
4553
4554 from zope.interface import Attribute, Interface
4555 from zope.schema import Bool, Bytes, Choice, Date, Float, Int, TextLine, Text
4556@@ -41,16 +43,18 @@ from lazr.restful.declarations import (
4557 operation_returns_collection_of,
4558 operation_returns_entry,
4559 webservice_error,
4560- )
4561+)
4562
4563
4564 class AlreadyNew(Exception):
4565 """A cookbook's name prohibits the cheap 'The New' trick."""
4566+
4567 webservice_error(400)
4568
4569
4570 class NameAlreadyTaken(Exception):
4571 """The name given for a cookbook is in use by another cookbook."""
4572+
4573 webservice_error(409)
4574
4575
4576@@ -76,17 +80,22 @@ class Cuisine(EnumeratedType):
4577
4578 class IHasGet(Interface):
4579 """A marker interface objects that implement traversal with get()."""
4580+
4581 def get(name):
4582 """Traverse to a contained object."""
4583
4584
4585-@exported_as_webservice_entry(plural_name='dishes')
4586+@exported_as_webservice_entry(plural_name="dishes")
4587 class IDish(ILocation):
4588 """A dish, annotated for export to the web service."""
4589+
4590 name = exported(TextLine(title=u"Name", required=True))
4591- recipes = exported(CollectionField(
4592+ recipes = exported(
4593+ CollectionField(
4594 title=u"Recipes in this cookbook",
4595- value_type=Reference(schema=Interface)))
4596+ value_type=Reference(schema=Interface),
4597+ )
4598+ )
4599
4600 def removeRecipe(recipe):
4601 """Remove one of this dish's recipes."""
4602@@ -95,16 +104,19 @@ class IDish(ILocation):
4603 @exported_as_webservice_entry()
4604 class IRecipe(ILocation):
4605 """A recipe, annotated for export to the web service."""
4606+
4607 id = exported(Int(title=u"Unique ID", required=True))
4608 dish = exported(Reference(title=u"Dish", schema=IDish))
4609 cookbook = exported(Reference(title=u"Cookbook", schema=Interface))
4610- instructions = exported(Text(title=u"How to prepare the recipe",
4611- required=True))
4612- private = exported(Bool(title=u"Whether the public can see this recipe.",
4613- default=False))
4614+ instructions = exported(
4615+ Text(title=u"How to prepare the recipe", required=True)
4616+ )
4617+ private = exported(
4618+ Bool(title=u"Whether the public can see this recipe.", default=False)
4619+ )
4620 prepared_image = exported(
4621- Bytes(0, 5000, title=u"An image of the prepared dish.",
4622- readonly=True))
4623+ Bytes(0, 5000, title=u"An image of the prepared dish.", readonly=True)
4624+ )
4625
4626 @export_destructor_operation()
4627 def delete():
4628@@ -114,41 +126,65 @@ class IRecipe(ILocation):
4629 @exported_as_webservice_entry()
4630 class ICookbook(IHasGet, ILocation):
4631 """A cookbook, annotated for export to the web service."""
4632+
4633 name = exported(TextLine(title=u"Name", required=True))
4634 copyright_date = exported(
4635- Date(title=u"Copyright Date",
4636- description=u"The copyright date for this work."), readonly=True)
4637+ Date(
4638+ title=u"Copyright Date",
4639+ description=u"The copyright date for this work.",
4640+ ),
4641+ readonly=True,
4642+ )
4643 description = exported(
4644- WhitespaceStrippingTextLine(title=u"Description", required=False))
4645+ WhitespaceStrippingTextLine(title=u"Description", required=False)
4646+ )
4647 revision_number = exported(
4648- Int(title=u"A record of the number of times "
4649- "this cookbook has been modified."))
4650- confirmed = exported(Bool(
4651- title=u"Whether this information has been confirmed",
4652- default=False))
4653- cuisine = exported(Choice(
4654- vocabulary=Cuisine, title=u"Cuisine", required=False, default=None))
4655+ Int(
4656+ title=u"A record of the number of times "
4657+ "this cookbook has been modified."
4658+ )
4659+ )
4660+ confirmed = exported(
4661+ Bool(
4662+ title=u"Whether this information has been confirmed", default=False
4663+ )
4664+ )
4665+ cuisine = exported(
4666+ Choice(
4667+ vocabulary=Cuisine, title=u"Cuisine", required=False, default=None
4668+ )
4669+ )
4670 last_printing = exported(
4671- Date(title=u"Last printing",
4672- description=u"The date of this work's most recent printing."))
4673+ Date(
4674+ title=u"Last printing",
4675+ description=u"The date of this work's most recent printing.",
4676+ )
4677+ )
4678 # Don't try this at home! Float is a bad choice for a 'price'
4679 # field because it's imprecise. Decimal is a better choice. But
4680 # this is just an example and we need a Float field, so...
4681 price = exported(Float(title=u"Retail price of the cookbook"))
4682- recipes = exported(CollectionField(title=u"Recipes in this cookbook",
4683- value_type=Reference(schema=IRecipe)))
4684+ recipes = exported(
4685+ CollectionField(
4686+ title=u"Recipes in this cookbook",
4687+ value_type=Reference(schema=IRecipe),
4688+ )
4689+ )
4690 cover = exported(
4691- Bytes(0, 5000, title=u"An image of the cookbook's cover."))
4692+ Bytes(0, 5000, title=u"An image of the cookbook's cover.")
4693+ )
4694
4695 @operation_parameters(
4696- search=TextLine(title=u"String to search for in recipe name."))
4697+ search=TextLine(title=u"String to search for in recipe name.")
4698+ )
4699 @operation_returns_collection_of(IRecipe)
4700 @export_read_operation()
4701 def find_recipes(search):
4702 """Search for recipes in this cookbook."""
4703
4704 @operation_parameters(
4705- dish=Reference(title=u"Dish to search for.", schema=IDish))
4706+ dish=Reference(title=u"Dish to search for.", schema=IDish)
4707+ )
4708 @operation_returns_entry(IRecipe)
4709 @export_read_operation()
4710 def find_recipe_for(dish):
4711@@ -168,8 +204,8 @@ class ICookbook(IHasGet, ILocation):
4712
4713
4714 # Resolve dangling references
4715-IDish['recipes'].value_type.schema = IRecipe
4716-IRecipe['cookbook'].schema = ICookbook
4717+IDish["recipes"].value_type.schema = IRecipe
4718+IRecipe["cookbook"].schema = ICookbook
4719
4720
4721 @exported_as_webservice_entry()
4722@@ -196,26 +232,41 @@ class ICookbookSet(IHasGet):
4723
4724 @operation_parameters(
4725 search=TextLine(title=u"String to search for in recipe name."),
4726- vegetarian=Bool(title=u"Whether or not to limit the search to "
4727- "vegetarian cookbooks.", default=False))
4728+ vegetarian=Bool(
4729+ title=u"Whether or not to limit the search to "
4730+ "vegetarian cookbooks.",
4731+ default=False,
4732+ ),
4733+ )
4734 @operation_returns_collection_of(IRecipe)
4735 @export_read_operation()
4736 def find_recipes(search, vegetarian):
4737 """Search for recipes across cookbooks."""
4738
4739 @operation_parameters(
4740- cuisine=Choice(vocabulary=Cuisine,
4741- title=u"Cuisine to search for in recipe name."))
4742+ cuisine=Choice(
4743+ vocabulary=Cuisine, title=u"Cuisine to search for in recipe name."
4744+ )
4745+ )
4746 @operation_returns_collection_of(ICookbook)
4747 @export_read_operation()
4748 def find_for_cuisine(cuisine):
4749 """Search for cookbooks of a given cuisine."""
4750
4751 @export_factory_operation(
4752- ICookbook, ['name', 'description', 'cuisine', 'copyright_date',
4753- 'last_printing', 'price'])
4754- def create(name, description, cuisine, copyright_date, last_printing,
4755- price):
4756+ ICookbook,
4757+ [
4758+ "name",
4759+ "description",
4760+ "cuisine",
4761+ "copyright_date",
4762+ "last_printing",
4763+ "price",
4764+ ],
4765+ )
4766+ def create(
4767+ name, description, cuisine, copyright_date, last_printing, price
4768+ ):
4769 """Create a new cookbook."""
4770
4771 featured = Attribute("The currently featured cookbook.")
4772@@ -239,7 +290,8 @@ class IRecipeSet(IHasGet):
4773 """Return the list of recipes."""
4774
4775 @export_factory_operation(
4776- IRecipe, ['id', 'cookbook', 'dish', 'instructions', 'private'])
4777+ IRecipe, ["id", "cookbook", "dish", "instructions", "private"]
4778+ )
4779 def createRecipe(id, cookbook, dish, instructions, private=False):
4780 """Create a new recipe."""
4781
4782diff --git a/src/lazr/restful/example/base/root.py b/src/lazr/restful/example/base/root.py
4783index 197d5df..424c971 100644
4784--- a/src/lazr/restful/example/base/root.py
4785+++ b/src/lazr/restful/example/base/root.py
4786@@ -5,11 +5,13 @@
4787 from __future__ import absolute_import, print_function
4788
4789 __metaclass__ = type
4790-__all__ = ['Cookbook',
4791- 'CookbookServiceRootResource',
4792- 'CookbookSet',
4793- 'CookbookWebServiceObject',
4794- 'WebServiceConfiguration']
4795+__all__ = [
4796+ "Cookbook",
4797+ "CookbookServiceRootResource",
4798+ "CookbookSet",
4799+ "CookbookWebServiceObject",
4800+ "WebServiceConfiguration",
4801+]
4802
4803 from datetime import date
4804
4805@@ -24,11 +26,25 @@ from zope.security.proxy import removeSecurityProxy
4806 from lazr.restful import directives, ServiceRootResource
4807
4808 from lazr.restful.interfaces import (
4809- IByteStorage, IEntry, IServiceRootResource, ITopLevelEntryLink,
4810- IWebServiceConfiguration)
4811+ IByteStorage,
4812+ IEntry,
4813+ IServiceRootResource,
4814+ ITopLevelEntryLink,
4815+ IWebServiceConfiguration,
4816+)
4817 from lazr.restful.example.base.interfaces import (
4818- AlreadyNew, Cuisine, ICookbook, ICookbookSet, IDish, IDishSet,
4819- IFileManager, IRecipe, IRecipeSet, IHasGet, NameAlreadyTaken)
4820+ AlreadyNew,
4821+ Cuisine,
4822+ ICookbook,
4823+ ICookbookSet,
4824+ IDish,
4825+ IDishSet,
4826+ IFileManager,
4827+ IRecipe,
4828+ IRecipeSet,
4829+ IHasGet,
4830+ NameAlreadyTaken,
4831+)
4832 from lazr.restful.simple import BaseWebServiceConfiguration
4833 from lazr.restful.testing.webservice import WebServiceTestPublication
4834 from lazr.restful.utils import get_current_web_service_request
4835@@ -40,17 +56,18 @@ class CookbookWebServiceObject:
4836
4837
4838 @implementer(IByteStorage, ILocation)
4839-class SimpleByteStorage(CookbookWebServiceObject,
4840- grokcore.component.MultiAdapter):
4841+class SimpleByteStorage(
4842+ CookbookWebServiceObject, grokcore.component.MultiAdapter
4843+):
4844 """A simple IByteStorage implementation"""
4845+
4846 grokcore.component.adapts(IEntry, IBytes)
4847 grokcore.component.provides(IByteStorage)
4848
4849 def __init__(self, entry, field):
4850 self.entry = entry
4851 self.field = field
4852- self.is_stored = getattr(
4853- self.entry, field.__name__, None) is not None
4854+ self.is_stored = getattr(self.entry, field.__name__, None) is not None
4855 if self.is_stored:
4856 self.filename = getattr(self.entry, field.__name__).filename
4857 self.id = getattr(self.entry, field.__name__).id
4858@@ -72,16 +89,18 @@ class SimpleByteStorage(CookbookWebServiceObject,
4859 implementation, and 2) the ByteStorage implementation cannot
4860 change between versions.
4861 """
4862- return 'http://cookbooks.dev/%s/filemanager/%s' % (
4863+ return "http://cookbooks.dev/%s/filemanager/%s" % (
4864 getUtility(IWebServiceConfiguration).active_versions[-1],
4865- self.id)
4866+ self.id,
4867+ )
4868
4869 def createStored(self, mediaType, representation, filename=None):
4870 self.representation = representation
4871 if filename is None:
4872 filename = self.field.__name__
4873 self.id = getUtility(IFileManager).put(
4874- mediaType, representation, filename)
4875+ mediaType, representation, filename
4876+ )
4877 self.filename = filename
4878 setattr(self.entry, self.field.__name__, self)
4879
4880@@ -93,8 +112,17 @@ class SimpleByteStorage(CookbookWebServiceObject,
4881 @implementer(ICookbook)
4882 class Cookbook(CookbookWebServiceObject):
4883 """An object representing a cookbook"""
4884- def __init__(self, name, description, cuisine, copyright_date,
4885- last_printing=None, price=0, confirmed=False):
4886+
4887+ def __init__(
4888+ self,
4889+ name,
4890+ description,
4891+ cuisine,
4892+ copyright_date,
4893+ last_printing=None,
4894+ price=0,
4895+ confirmed=False,
4896+ ):
4897 self.name = name
4898 self.cuisine = cuisine
4899 self.description = description
4900@@ -116,8 +144,7 @@ class Cookbook(CookbookWebServiceObject):
4901
4902 def get(self, name):
4903 """See `IHasGet`."""
4904- match = [recipe for recipe in self.recipes
4905- if recipe.dish.name == name]
4906+ match = [recipe for recipe in self.recipes if recipe.dish.name == name]
4907 if len(match) > 0:
4908 return match[0]
4909 return None
4910@@ -135,7 +162,8 @@ class Cookbook(CookbookWebServiceObject):
4911 if self.name.find("The New ") == 0:
4912 raise AlreadyNew(
4913 "The 'New' trick can't be used on this cookbook "
4914- "because its name already starts with 'The New'.")
4915+ "because its name already starts with 'The New'."
4916+ )
4917 self.name = "The New " + self.name
4918
4919 def find_recipe_for(self, dish):
4920@@ -151,14 +179,14 @@ class Cookbook(CookbookWebServiceObject):
4921
4922 def replace_cover(self, cover):
4923 entry = getMultiAdapter(
4924- (self, get_current_web_service_request()), IEntry)
4925- storage = SimpleByteStorage(entry, ICookbook['cover'])
4926- storage.createStored('application/octet-stream', cover, 'cover')
4927+ (self, get_current_web_service_request()), IEntry
4928+ )
4929+ storage = SimpleByteStorage(entry, ICookbook["cover"])
4930+ storage.createStored("application/octet-stream", cover, "cover")
4931
4932
4933 @implementer(IDish)
4934 class Dish(CookbookWebServiceObject):
4935-
4936 def __init__(self, name):
4937 self.name = name
4938 self.recipes = []
4939@@ -177,7 +205,6 @@ class Dish(CookbookWebServiceObject):
4940
4941 @implementer(IRecipe)
4942 class Recipe(CookbookWebServiceObject):
4943-
4944 def __init__(self, id, cookbook, dish, instructions, private=False):
4945 self.id = id
4946 self.dish = dish
4947@@ -202,8 +229,9 @@ class Recipe(CookbookWebServiceObject):
4948
4949 # Top-level objects.
4950 @implementer(ILocation)
4951-class CookbookTopLevelObject(CookbookWebServiceObject,
4952- grokcore.component.GlobalUtility):
4953+class CookbookTopLevelObject(
4954+ CookbookWebServiceObject, grokcore.component.GlobalUtility
4955+):
4956 """An object published at the top level of the web service."""
4957
4958 @property
4959@@ -218,6 +246,7 @@ class CookbookTopLevelObject(CookbookWebServiceObject,
4960 @implementer(ITopLevelEntryLink)
4961 class FeaturedCookbookLink(CookbookTopLevelObject):
4962 """A link to the currently featured cookbook."""
4963+
4964 grokcore.component.provides(ITopLevelEntryLink)
4965
4966 @property
4967@@ -233,6 +262,7 @@ class FeaturedCookbookLink(CookbookTopLevelObject):
4968 @implementer(ICookbookSet)
4969 class CookbookSet(CookbookTopLevelObject):
4970 """The set of all cookbooks."""
4971+
4972 grokcore.component.provides(ICookbookSet)
4973
4974 __name__ = "cookbooks"
4975@@ -267,14 +297,23 @@ class CookbookSet(CookbookTopLevelObject):
4976 cookbooks.append(cookbook)
4977 return cookbooks
4978
4979- def create(self, name, description, cuisine, copyright_date,
4980- last_printing=None, price=0):
4981+ def create(
4982+ self,
4983+ name,
4984+ description,
4985+ cuisine,
4986+ copyright_date,
4987+ last_printing=None,
4988+ price=0,
4989+ ):
4990 for cookbook in self.cookbooks:
4991 if cookbook.name == name:
4992 raise NameAlreadyTaken(
4993- 'A cookbook called "%s" already exists.' % name)
4994- cookbook = Cookbook(name, description, cuisine, copyright_date,
4995- last_printing, price)
4996+ 'A cookbook called "%s" already exists.' % name
4997+ )
4998+ cookbook = Cookbook(
4999+ name, description, cuisine, copyright_date, last_printing, price
5000+ )
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches