Merge lp:~benji/lazr.restful/bug-539070 into lp:lazr.restful

Proposed by Benji York
Status: Merged
Approved by: Edwin Grubbs
Approved revision: 159
Merged at revision: 161
Proposed branch: lp:~benji/lazr.restful/bug-539070
Merge into: lp:lazr.restful
Diff against target: 119 lines (+59/-6)
3 files modified
src/lazr/restful/_resource.py (+18/-3)
src/lazr/restful/tales.py (+9/-2)
src/lazr/restful/tests/test_error.py (+32/-1)
To merge this branch: bzr merge lp:~benji/lazr.restful/bug-539070
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Approve
Review via email: mp+40003@code.launchpad.net

Description of the change

Bug 539070 describes a case where an error message doesn't give much of a hint as to where to start looking for a problem. This branch enhances the error message with the interface in question so the developer knows where to look.

To post a comment you must log in.
lp:~benji/lazr.restful/bug-539070 updated
157. By Benji York <benji@benji-laptop>

checkpoint

158. By Benji York

finish up reworking the error reporting

159. By Benji York

remove some dead code that snuck in

Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Hi Benji,

The changes you made look good for showing which interface attribute tries to set the schema to a class that doesn't have an IEntry adapter. I just have one comment below.

-Edwin

>=== modified file 'src/lazr/restful/tales.py'
>--- src/lazr/restful/tales.py 2010-08-24 18:57:50 +0000
>+++ src/lazr/restful/tales.py 2010-11-04 19:25:16 +0000
>@@ -29,6 +29,7 @@
> from lazr.restful import (
> EntryResource, ResourceJSONEncoder, CollectionResource,
> EntryAdapterUtility, IObjectLink, RESTUtilityBase)
>+from lazr.restful._resource import UnknownEntryAdapter
> from lazr.restful.interfaces import (
> ICollection, ICollectionField, IEntry, IJSONRequestCache,
> IReferenceChoice, IResourceDELETEOperation, IResourceGETOperation,
>@@ -540,8 +541,15 @@
> raise TypeError("Field is not of a supported type.")
> assert schema is not IObject, (
> "Null schema provided for %s" % self.field.__name__)
>- return EntryAdapterUtility.forSchemaInterface(
>- schema, get_current_web_service_request())
>+ try:
>+ return EntryAdapterUtility.forSchemaInterface(
>+ schema, get_current_web_service_request())
>+ except UnknownEntryAdapter, e:
>+ e.whence = (
>+ 'Encountered as a result of the entry interface %r, field %r.'
>+ % (self.field.interface, self.field.getName()))
>+ raise e
>+ return adapter.entry_page_type_link

Can you remove this line that can never be reached?

review: Approve (code)
Revision history for this message
Benji York (benji) wrote :

I removed the dead code. Thanks.

Revision history for this message
Jonathan Lange (jml) wrote :

Thanks so much for working on this change Benji – it promises to save me much hair-pulling in the future.

Perhaps it would also be worth adding a similar try/except/whence thingummy around WadlTopLevelEntryLinkAPI.type_link? (pasted below for convenience)

class WadlTopLevelEntryLinkAPI(RESTUtilityBase):
    """Namespace for WADL functions that operate on top-level entry links."""

    def __init__(self, entry_link):
        self.entry_link = entry_link

    def type_link(self):
        return EntryAdapterUtility.forSchemaInterface(
            self.entry_link.entry_type,
            get_current_web_service_request()).type_link

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/lazr/restful/_resource.py'
2--- src/lazr/restful/_resource.py 2010-10-25 16:31:09 +0000
3+++ src/lazr/restful/_resource.py 2010-11-04 19:39:42 +0000
4@@ -2060,6 +2060,22 @@
5 request)
6
7
8+class UnknownEntryAdapter(Exception):
9+ """Exception that signals no adaper could be found for an interface."""
10+
11+ def __init__(self, adapted_interface_name, version):
12+ self.adapted_interface_name = adapted_interface_name
13+ self.version = version
14+ self.whence = ''
15+
16+ def __str__(self):
17+ message = ("No IEntry adapter found for %s (web service version: %s)."
18+ % (self.adapted_interface_name, self.version))
19+ if self.whence:
20+ message += ' ' + self.whence
21+ return message
22+
23+
24 class EntryAdapterUtility(RESTUtilityBase):
25 """Useful information about an entry's presence in the web service.
26
27@@ -2078,9 +2094,8 @@
28 IWebServiceVersion, name=request.version)
29 entry_class = getGlobalSiteManager().adapters.lookup(
30 (entry_interface, request_interface), IEntry)
31- assert entry_class is not None, (
32- ("No IEntry adapter found for %s (web service version: %s)."
33- % (entry_interface.__name__, request.version)))
34+ if entry_class is None:
35+ raise UnknownEntryAdapter(entry_interface.__name__, request.version)
36 return EntryAdapterUtility(entry_class)
37
38 @classmethod
39
40=== modified file 'src/lazr/restful/tales.py'
41--- src/lazr/restful/tales.py 2010-08-24 18:57:50 +0000
42+++ src/lazr/restful/tales.py 2010-11-04 19:39:42 +0000
43@@ -29,6 +29,7 @@
44 from lazr.restful import (
45 EntryResource, ResourceJSONEncoder, CollectionResource,
46 EntryAdapterUtility, IObjectLink, RESTUtilityBase)
47+from lazr.restful._resource import UnknownEntryAdapter
48 from lazr.restful.interfaces import (
49 ICollection, ICollectionField, IEntry, IJSONRequestCache,
50 IReferenceChoice, IResourceDELETEOperation, IResourceGETOperation,
51@@ -540,8 +541,14 @@
52 raise TypeError("Field is not of a supported type.")
53 assert schema is not IObject, (
54 "Null schema provided for %s" % self.field.__name__)
55- return EntryAdapterUtility.forSchemaInterface(
56- schema, get_current_web_service_request())
57+ try:
58+ return EntryAdapterUtility.forSchemaInterface(
59+ schema, get_current_web_service_request())
60+ except UnknownEntryAdapter, e:
61+ e.whence = (
62+ 'Encountered as a result of the entry interface %r, field %r.'
63+ % (self.field.interface, self.field.getName()))
64+ raise e
65
66
67 @property
68
69=== modified file 'src/lazr/restful/tests/test_error.py'
70--- src/lazr/restful/tests/test_error.py 2010-09-06 19:25:17 +0000
71+++ src/lazr/restful/tests/test_error.py 2010-11-04 19:39:42 +0000
72@@ -10,8 +10,9 @@
73
74 from van.testing.layer import zcml_layer
75 from zope.component import getMultiAdapter
76+from zope.interface import Interface
77
78-from lazr.restful._resource import ReadWriteResource
79+from lazr.restful._resource import ReadWriteResource, UnknownEntryAdapter
80 from lazr.restful.error import expose, ClientErrorView, SystemErrorView
81 from lazr.restful.interfaces import IClientErrorView
82 from lazr.restful.testing.webservice import FakeRequest
83@@ -72,6 +73,36 @@
84 self.assertRaises(ValueError, expose, 'x')
85 self.assertRaises(ValueError, expose, RuntimeError)
86
87+ def test_missing_adapter(self):
88+ # If there is no multi-adapter from the entry interface (IMyEntry) and
89+ # a request to IEntry an exception is raised.
90+ class IMyEntry(Interface):
91+ pass
92+
93+ # Since the test wants to inspect the exception message (below) we're
94+ # not using self.assertRaises).
95+ try:
96+ EntryAdapterUtility.forSchemaInterface(
97+ IMyEntry, self.beta_request)
98+ except Exception, e:
99+ pass
100+ self.assert_(isinstance(e, Exception))
101+
102+ # The exception's message explains what went wrong.
103+ self.assertTrue(str(e),
104+ 'No IEntry adapter found for IMyEntry (web service version: beta).')
105+
106+ def test_missing_adapter_whence(self):
107+ # The UnknownEntryAdapter exception has a "whence" attribute that
108+ # higher-level code can set to give the reader of the exeption message
109+ # a hint about where in the code the mistake was made that triggered
110+ # the exception.
111+ exception = UnknownEntryAdapter('IInterfaceInQuestion', '2.0')
112+ exception.whence = 'Encounterd as a result of badness in foo.py.'
113+ self.assertEquals(str(exception),
114+ 'No IEntry adapter found for IInterfaceInQuestion (web service '
115+ 'version: 2.0). Encounterd as a result of badness in foo.py.')
116+
117
118 class FunctionalLayer:
119 allow_teardown = False

Subscribers

People subscribed via source and target branches