Merge ~cjwatson/lazr.restful:remove-class-advisors into lazr.restful:main

Proposed by Colin Watson
Status: Merged
Merged at revision: 9a607fdf66cf658ad60ac2cdacea2aa718f1d1a0
Proposed branch: ~cjwatson/lazr.restful:remove-class-advisors
Merge into: lazr.restful:main
Diff against target: 396 lines (+17/-277)
3 files modified
NEWS.rst (+5/-0)
src/lazr/restful/declarations.py (+12/-112)
src/lazr/restful/tests/test_declarations.py (+0/-165)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Review via email: mp+413739@code.launchpad.net

Commit message

Remove old class advisors

Description of the change

The deprecated `export_as_webservice_entry` and `export_as_webservice_collection` class advisors only worked on Python 2, and were already replaced by `@exported_as_webservice_entry` and `@exported_as_webservice_collection` for Python 3.

This doesn't require a new major version, because we declare `python_requires=">=3.5"` and so pip (>= 9, anyway) on Python 2 won't upgrade to this version.

To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) wrote :

LGTM

> because we declare `python_requires=">=3.5"` and so pip on Python 2 won't upgrade to this version

pip >= 9 :-)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/NEWS.rst b/NEWS.rst
2index c9dff29..e2477fb 100644
3--- a/NEWS.rst
4+++ b/NEWS.rst
5@@ -20,6 +20,11 @@ NEWS for lazr.restful
6 => ``lazr.restful.testing.webservice.StubRootResourceURL``
7 - Apply black code formatter via pre-commit.
8 - Drop Python 2 support.
9+- Remove ``export_as_webservice_entry`` and
10+ ``export_as_webservice_collection``; these were deprecated in 0.22.0 and
11+ cannot work on Python 3. Use the class decorators
12+ ``@exported_as_webservice_entry`` and
13+ ``@exported_as_webservice_collection`` instead.
14
15 1.1.0 (2021-10-07)
16 ==================
17diff --git a/src/lazr/restful/declarations.py b/src/lazr/restful/declarations.py
18index f717592..886eedb 100644
19--- a/src/lazr/restful/declarations.py
20+++ b/src/lazr/restful/declarations.py
21@@ -16,8 +16,6 @@ __all__ = [
22 "call_with",
23 "collection_default_content",
24 "error_status",
25- "export_as_webservice_collection",
26- "export_as_webservice_entry",
27 "export_destructor_operation",
28 "export_factory_operation",
29 "export_operation_as",
30@@ -49,7 +47,6 @@ import sys
31 import six
32 from zope.component import getUtility, getGlobalSiteManager
33 from zope.interface import classImplements
34-from zope.interface.advice import addClassAdvisor
35 from zope.interface.interface import fromFunction, InterfaceClass, TAGGED_DATA
36 from zope.interface.interfaces import IInterface, IMethod
37 from zope.schema import (
38@@ -179,8 +176,7 @@ def _export_as_webservice_entry(
39 ):
40 """Tag an interface as exported on the web service as an entry.
41
42- This is the core of export_as_webservice_entry and
43- exported_as_webservice_entry.
44+ This is the core of exported_as_webservice_entry.
45 """
46 annotation_stack = VersionedDict()
47
48@@ -261,66 +257,6 @@ def _export_as_webservice_entry(
49 annotate_exported_methods(interface)
50
51
52-def export_as_webservice_entry(
53- singular_name=None,
54- plural_name=None,
55- contributes_to=None,
56- publish_web_link=True,
57- as_of=None,
58- versioned_annotations=None,
59-):
60- """Mark the content interface as exported on the web service as an entry.
61-
62- If contributes_to is a non-empty sequence of Interfaces, this entry will
63- actually not be exported on its own but instead will contribute its
64- attributes/methods to other exported entries.
65-
66- This function does not work with Python 3. Rather than calling this
67- within the class definition, decorate the class with
68- @exported_as_webservice_entry instead.
69-
70- :param singular_name: The human-readable singular name of the entry,
71- eg. "paintbrush".
72- :param plural_name: The human-readable plural name of the entry,
73- eg. "paintbrushes"
74- :param contributes_to: An optional list of exported interfaces to which
75- this interface contributes.
76- :param publish_web_link: This parameter is ignored unless there is
77- a correspondence between this web service's entries and the
78- pages on some website. If that is so, and if this parameter is
79- set to True, the representation of this entry will include a
80- web_link pointing to the corresponding page on the website. If
81- False, web_link will be omitted.
82- :param as_of: The first version of the web service to feature this entry.
83- :param versioned_annotations: A list of 2-tuples (version,
84- {params}), with more recent web service versions earlier in
85- the list and older versions later in the list.
86-
87- A 'params' dictionary may contain the key 'exported', which
88- controls whether or not to publish the entry at all in the
89- given version. It may also contain the keys 'singular_name',
90- 'plural_name', 'contributes_to', or 'publish_web_link', which
91- work just like the corresponding arguments to this method.
92- """
93- _check_called_from_interface_def("export_as_webservice_entry()")
94-
95- def mark_entry(interface):
96- """Class advisor that tags the interface once it is created."""
97- _check_interface("export_as_webservice_entry()", interface)
98- _export_as_webservice_entry(
99- interface,
100- singular_name=singular_name,
101- plural_name=plural_name,
102- contributes_to=contributes_to,
103- publish_web_link=publish_web_link,
104- as_of=as_of,
105- versioned_annotations=versioned_annotations,
106- )
107- return interface
108-
109- addClassAdvisor(mark_entry)
110-
111-
112 class exported_as_webservice_entry:
113 """Mark the content interface as exported on the web service as an entry.
114
115@@ -497,40 +433,6 @@ def _check_collection_default_content(name, interface):
116 )
117
118
119-def export_as_webservice_collection(entry_schema):
120- """Mark the interface as exported on the web service as a collection.
121-
122- This function does not work with Python 3. Rather than calling this
123- within the class definition, decorate the class with
124- @exported_as_webservice_collection instead.
125-
126- :raises TypeError: if the interface doesn't have a method decorated with
127- @collection_default_content.
128- """
129- _check_called_from_interface_def("export_as_webservice_collection()")
130-
131- if not IInterface.providedBy(entry_schema):
132- raise TypeError("entry_schema must be an interface.")
133-
134- # Set the tags at this point, so that future declarations can
135- # check it.
136- tags = _get_interface_tags()
137- tags[LAZR_WEBSERVICE_EXPORTED] = dict(
138- type=COLLECTION_TYPE, collection_entry_schema=entry_schema
139- )
140-
141- def mark_collection(interface):
142- """Class advisor that tags the interface once it is created."""
143- _check_interface("export_as_webservice_collection()", interface)
144- _check_collection_default_content(
145- "export_as_webservice_collection()", interface
146- )
147- annotate_exported_methods(interface)
148- return interface
149-
150- addClassAdvisor(mark_collection)
151-
152-
153 class exported_as_webservice_collection:
154 """Mark the interface as exported on the web service as a collection.
155
156@@ -580,14 +482,14 @@ class collection_default_content:
157 # We used to check that this decorator is being used from within an
158 # interface exported as a collection. However, it isn't possible to
159 # do this when using the @exported_as_webservice_collection
160- # decorator rather than the export_as_webservice_collection function
161- # (which is based on class advice, and so cannot work in Python 3):
162- # that decorator isn't called until the rest of the interface has
163- # been defined, so it hasn't had a chance to set any tags on the
164- # interface by the time decorators of methods in that interface are
165- # called. The best we can do is to check that we don't already
166- # positively know that the interface is exported as an entry
167- # instead.
168+ # decorator rather than the old export_as_webservice_collection
169+ # function (which was based on class advice, and so cannot work in
170+ # Python 3): that decorator isn't called until the rest of the
171+ # interface has been defined, so it hasn't had a chance to set any
172+ # tags on the interface by the time decorators of methods in that
173+ # interface are called. The best we can do is to check that we
174+ # don't already positively know that the interface is exported as an
175+ # entry instead.
176 tags = _get_interface_tags()
177 tag = tags.setdefault(LAZR_WEBSERVICE_EXPORTED, {})
178 if "type" in tag and tag["type"] != COLLECTION_TYPE:
179@@ -684,9 +586,7 @@ class _method_annotator:
180
181 The actual method will be wrapped in an IMethod specification once the
182 Interface is complete. So we save the annotations in an attribute of the
183- method, and either the class advisor invoked by
184- export_as_webservice_entry() and export_as_webservice_collection() or
185- the @exported_as_webservice_entry() and
186+ method, and the @exported_as_webservice_entry() and
187 @exported_as_webservice_collection() decorators will do the final
188 tagging.
189 """
190@@ -729,8 +629,8 @@ class _method_annotator:
191 :param f: the method being annotated.
192 :param annotations: the dict containing the method annotations.
193
194- The annotations will copied to the lazr.webservice.exported tag
195- by a class advisor.
196+ The annotations will be copied to the lazr.webservice.exported tag
197+ by a class decorator.
198 """
199 raise NotImplementedError
200
201diff --git a/src/lazr/restful/tests/test_declarations.py b/src/lazr/restful/tests/test_declarations.py
202index 305709b..6a2313b 100644
203--- a/src/lazr/restful/tests/test_declarations.py
204+++ b/src/lazr/restful/tests/test_declarations.py
205@@ -2,8 +2,6 @@
206
207 """Unit tests for the conversion of interfaces into a web service."""
208
209-import sys
210-
211 import testtools
212 from zope.component import (
213 adapter,
214@@ -35,17 +33,13 @@ from zope.security.management import (
215 from lazr.restful.declarations import (
216 accessor_for,
217 call_with,
218- collection_default_content,
219 error_status,
220- export_as_webservice_collection,
221- export_as_webservice_entry,
222 export_read_operation,
223 export_write_operation,
224 exported,
225 exported_as_webservice_entry,
226 generate_entry_interfaces,
227 generate_operation_adapter,
228- LAZR_WEBSERVICE_EXPORTED,
229 LAZR_WEBSERVICE_NAME,
230 mutator_for,
231 operation_for_version,
232@@ -430,165 +424,6 @@ class ContributingInterfacesTestCase(TestCaseWithWebServiceFixtures):
233 )
234
235
236-class TestExportAsWebserviceEntry(testtools.TestCase):
237- """Tests for export_as_webservice_entry."""
238-
239- def setUp(self):
240- super().setUp()
241- if sys.version_info[0] >= 3:
242- self.skipTest(
243- "export_as_webservice_entry is only supported on Python 2"
244- )
245-
246- def test_works_on_interface(self):
247- # export_as_webservice_entry works on an interface.
248- class IFoo(Interface):
249- export_as_webservice_entry()
250-
251- self.assertTrue(
252- IFoo.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)["exported"]
253- )
254-
255- def test_requires_interface(self):
256- # export_as_webservice_entry can only be used on Interface.
257- def export_non_interface():
258- class NotAnInterface:
259- export_as_webservice_entry()
260-
261- exception = self.assertRaises(TypeError, export_non_interface)
262- self.assertEqual(
263- "export_as_webservice_entry() can only be used on an interface.",
264- str(exception),
265- )
266-
267- def test_must_be_within_interface_definition(self):
268- # export_as_webservice_entry can only be used from within a
269- # interface definition.
270- exception = self.assertRaises(TypeError, export_as_webservice_entry)
271- self.assertEqual(
272- "export_as_webservice_entry() can only be used from within an "
273- "interface definition.",
274- str(exception),
275- )
276-
277-
278-class TestExportAsWebserviceCollection(testtools.TestCase):
279- """Tests for export_as_webservice_collection."""
280-
281- def setUp(self):
282- super().setUp()
283- if sys.version_info[0] >= 3:
284- self.skipTest(
285- "export_as_webservice_collection is only supported on "
286- "Python 2"
287- )
288-
289- def test_works_on_interface_with_tagged_method(self):
290- # export_as_webservice_collection works on an interface that has an
291- # entry schema and a method tagged with @collection_default_content.
292- class IFoo(Interface):
293- export_as_webservice_collection(Interface)
294-
295- @collection_default_content()
296- def getAll():
297- pass
298-
299- self.assertEqual(
300- {None: ("getAll", {})},
301- IFoo.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)[
302- "collection_default_content"
303- ],
304- )
305-
306- def test_requires_entry_schema(self):
307- # export_as_webservice_collection requires an entry schema.
308- def export_without_entry_schema():
309- class MissingEntrySchema(Interface):
310- export_as_webservice_collection()
311-
312- exception = self.assertRaises(TypeError, export_without_entry_schema)
313- self.assertEqual(
314- "export_as_webservice_collection() takes exactly 1 argument (0 "
315- "given)",
316- str(exception),
317- )
318-
319- def test_requires_entry_schema_as_interface(self):
320- # export_as_webservice_collection requires the entry schema to be an
321- # interface.
322- def export_with_invalid_entry_schema():
323- class InvalidEntrySchema(Interface):
324- export_as_webservice_collection("not an interface")
325-
326- exception = self.assertRaises(
327- TypeError, export_with_invalid_entry_schema
328- )
329- self.assertEqual("entry_schema must be an interface.", str(exception))
330-
331- def test_requires_tagged_method(self):
332- # export_as_webservice_collection can only be used on a collection
333- # that has a method marked as exporting the default content.
334- def export_missing_default_content():
335- class MissingDefaultContent(Interface):
336- export_as_webservice_collection(Interface)
337-
338- exception = self.assertRaises(
339- TypeError, export_missing_default_content
340- )
341- self.assertEqual(
342- "export_as_webservice_collection() is missing a method tagged "
343- "with @collection_default_content.",
344- str(exception),
345- )
346-
347- def test_refuses_multiple_tagged_methods(self):
348- # export_as_webservice_collection cannot be used on a collection
349- # that has multiple methods marked as exporting the default content.
350- def export_two_default_content():
351- class TwoDefaultContent(Interface):
352- export_as_webservice_collection(Interface)
353-
354- @collection_default_content()
355- def getAll1():
356- """A first getAll()."""
357-
358- @collection_default_content()
359- def getAll2():
360- """Another getAll()."""
361-
362- exception = self.assertRaises(TypeError, export_two_default_content)
363- self.assertEqual(
364- "Only one method can be marked with @collection_default_content "
365- "for version '(earliest version)'.",
366- str(exception),
367- )
368-
369- def test_requires_interface(self):
370- # export_as_webservice_collection can only be used on Interface.
371- def export_non_interface():
372- class NotAnInterface:
373- export_as_webservice_collection(Interface)
374-
375- exception = self.assertRaises(TypeError, export_non_interface)
376- self.assertEqual(
377- "export_as_webservice_collection() can only be used on an "
378- "interface.",
379- str(exception),
380- )
381-
382- def test_must_be_within_interface_definition(self):
383- # export_as_webservice_collection can only be used from within an
384- # interface definition.
385- exception = self.assertRaises(
386- TypeError, export_as_webservice_collection, Interface
387- )
388- self.assertEqual(
389- "export_as_webservice_collection() can only be used from within "
390- "an interface definition.",
391- str(exception),
392- )
393-
394-
395 class NotExportedException(Exception):
396 pass
397

Subscribers

People subscribed via source and target branches