Merge ~jugmac00/lazr.restful:apply-black into lazr.restful:main
- Git
- lp:~jugmac00/lazr.restful
- apply-black
- Merge into 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) |
Related bugs: |
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
Description of the change
To post a comment you must log in.
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
1 | diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs |
2 | new file mode 100644 |
3 | index 0000000..04ed7f8 |
4 | --- /dev/null |
5 | +++ b/.git-blame-ignore-revs |
6 | @@ -0,0 +1,2 @@ |
7 | +# apply black |
8 | +2605ef4e4b85988957963224e51f03ce7778405d |
9 | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml |
10 | index 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 |
39 | diff --git a/NEWS.rst b/NEWS.rst |
40 | index 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 | ================== |
51 | diff --git a/setup.py b/setup.py |
52 | index 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 | +) |
191 | diff --git a/src/lazr/__init__.py b/src/lazr/__init__.py |
192 | index 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__) |
205 | diff --git a/src/lazr/restful/__init__.py b/src/lazr/restful/__init__.py |
206 | index 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) |
217 | diff --git a/src/lazr/restful/_bytestorage.py b/src/lazr/restful/_bytestorage.py |
218 | index 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 | |
277 | diff --git a/src/lazr/restful/_operation.py b/src/lazr/restful/_operation.py |
278 | index 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) |
467 | diff --git a/src/lazr/restful/_resource.py b/src/lazr/restful/_resource.py |
468 | index 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)) |
2114 | diff --git a/src/lazr/restful/debug.py b/src/lazr/restful/debug.py |
2115 | index 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 | |
2170 | diff --git a/src/lazr/restful/declarations.py b/src/lazr/restful/declarations.py |
2171 | index 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 | |
4046 | diff --git a/src/lazr/restful/directives/__init__.py b/src/lazr/restful/directives/__init__.py |
4047 | index 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 |
4195 | diff --git a/src/lazr/restful/docs/conf.py b/src/lazr/restful/docs/conf.py |
4196 | index 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 | ] |
4330 | diff --git a/src/lazr/restful/error.py b/src/lazr/restful/error.py |
4331 | index 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 | |
4445 | diff --git a/src/lazr/restful/example/base/filemanager.py b/src/lazr/restful/example/base/filemanager.py |
4446 | index 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 |
4517 | diff --git a/src/lazr/restful/example/base/interfaces.py b/src/lazr/restful/example/base/interfaces.py |
4518 | index 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 | |
4782 | diff --git a/src/lazr/restful/example/base/root.py b/src/lazr/restful/example/base/root.py |
4783 | index 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.
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. 👍