Merge lp:~cjwatson/lazr.restful/exported-interfaces-honour-all into lp:lazr.restful

Proposed by Colin Watson
Status: Merged
Merged at revision: 214
Proposed branch: lp:~cjwatson/lazr.restful/exported-interfaces-honour-all
Merge into: lp:lazr.restful
Diff against target: 115 lines (+71/-1)
2 files modified
src/lazr/restful/metazcml.py (+7/-1)
src/lazr/restful/tests/test_declarations.py (+64/-0)
To merge this branch: bzr merge lp:~cjwatson/lazr.restful/exported-interfaces-honour-all
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+311867@code.launchpad.net

Commit message

Restrict find_exported_interfaces to names that would ordinarily be considered to be exported from a module.

Description of the change

find_exported_interfaces considers all names in a module (subject to _is_exported_interface, which only checks webservice-specific properties), and doesn't honour __all__. This produces some weird effects. For instance, in Launchpad, I tried to move IncompatibleArguments to lp.app.errors and therefore needed to add a webservice:register declaration for lp.app.errors; but this failed because lp.registry.errors imports NameLookupFailed from lp.app.errors in order to subclass it, and this is considered to be exported for webservice purposes even though it's not listed in __all__.

I think this is clearly a bug, although upgrading lazr.restful to a fixed version may require some minor adjustments in application code. We should probably bump the next version to 0.20.0 to indicate that.

As for the particular policy implemented here, I used the rules for public names given in https://docs.python.org/2/reference/simple_stmts.html#import.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/lazr/restful/metazcml.py'
--- src/lazr/restful/metazcml.py 2015-04-08 20:11:29 +0000
+++ src/lazr/restful/metazcml.py 2016-11-26 01:04:38 +0000
@@ -245,8 +245,14 @@
245245
246 :return: iterator of interfaces.246 :return: iterator of interfaces.
247 """247 """
248 try:
249 module_all = set(module.__all__)
250 is_exported_name = lambda name: name in module_all
251 except AttributeError:
252 is_exported_name = lambda name: not name.startswith('_')
248 return (interface for name, interface253 return (interface for name, interface
249 in inspect.getmembers(module, _is_exported_interface))254 in inspect.getmembers(module, _is_exported_interface)
255 if is_exported_name(name))
250256
251257
252class ConflictInContributingInterfaces(Exception):258class ConflictInContributingInterfaces(Exception):
253259
=== modified file 'src/lazr/restful/tests/test_declarations.py'
--- src/lazr/restful/tests/test_declarations.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/tests/test_declarations.py 2016-11-26 01:04:38 +0000
@@ -2,6 +2,7 @@
22
3"""Unit tests for the conversion of interfaces into a web service."""3"""Unit tests for the conversion of interfaces into a web service."""
44
5import testtools
5from zope.component import (6from zope.component import (
6 adapter,7 adapter,
7 getMultiAdapter,8 getMultiAdapter,
@@ -31,6 +32,7 @@
31from lazr.restful.declarations import (32from lazr.restful.declarations import (
32 accessor_for,33 accessor_for,
33 call_with,34 call_with,
35 error_status,
34 export_as_webservice_entry,36 export_as_webservice_entry,
35 exported,37 exported,
36 export_read_operation,38 export_read_operation,
@@ -58,6 +60,7 @@
58 AttemptToContributeToNonExportedInterface,60 AttemptToContributeToNonExportedInterface,
59 ConflictInContributingInterfaces,61 ConflictInContributingInterfaces,
60 contribute_to_interface,62 contribute_to_interface,
63 find_exported_interfaces,
61 generate_and_register_webservice_operations,64 generate_and_register_webservice_operations,
62 )65 )
63from lazr.restful._resource import (66from lazr.restful._resource import (
@@ -328,6 +331,67 @@
328 'product', EntryAdapterUtility(adapter.__class__).singular_type)331 'product', EntryAdapterUtility(adapter.__class__).singular_type)
329332
330333
334class NotExportedException(Exception):
335 pass
336
337
338@error_status(400)
339class ExportedExceptionOne(Exception):
340 pass
341
342
343@error_status(400)
344class ExportedExceptionTwo(Exception):
345 pass
346
347
348class INotExported(Interface):
349 pass
350
351
352class IExported(Interface):
353 export_as_webservice_entry()
354
355
356class TestFindExportedInterfaces(testtools.TestCase):
357 """Tests for find_exported_interfaces."""
358
359 def assertExportsNames(self, module, names):
360 self.assertEqual(
361 sorted(names),
362 sorted(
363 iface.__name__ for iface in find_exported_interfaces(module)))
364
365 def test_finds_exported_exceptions(self):
366 class Module(object):
367 NotExportedException_copy = NotExportedException
368 ExportedExceptionOne_copy = ExportedExceptionOne
369
370 self.assertExportsNames(Module, ['ExportedExceptionOne'])
371
372 def test_finds_exported_interfaces(self):
373 class Module(object):
374 INotExported_copy = INotExported
375 IExported_copy = IExported
376
377 self.assertExportsNames(Module, ['IExported'])
378
379 def test_honours_all(self):
380 class Module(object):
381 __all__ = ['ExportedExceptionOne_copy']
382 ExportedExceptionOne_copy = ExportedExceptionOne
383 ExportedExceptionTwo_copy = ExportedExceptionTwo
384
385 self.assertExportsNames(Module, ['ExportedExceptionOne'])
386
387 def test_skips_leading_underscore_without_all(self):
388 class Module(object):
389 ExportedExceptionOne_copy = ExportedExceptionOne
390 _ExportedExceptionTwo_copy = ExportedExceptionTwo
391
392 self.assertExportsNames(Module, ['ExportedExceptionOne'])
393
394
331class IProduct(Interface):395class IProduct(Interface):
332 export_as_webservice_entry()396 export_as_webservice_entry()
333 title = exported(TextLine(title=u'The product title'))397 title = exported(TextLine(title=u'The product title'))

Subscribers

People subscribed via source and target branches