Merge lp:~leonardr/lazr.restful/launchpad-integration into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Merged at revision: not available
Proposed branch: lp:~leonardr/lazr.restful/launchpad-integration
Merge into: lp:lazr.restful
Diff against target: 516 lines (+141/-47)
9 files modified
src/lazr/restful/_resource.py (+5/-5)
src/lazr/restful/declarations.py (+2/-2)
src/lazr/restful/docs/multiversion.txt (+3/-3)
src/lazr/restful/example/base/filemanager.py (+2/-2)
src/lazr/restful/example/base/root.py (+3/-2)
src/lazr/restful/tales.py (+11/-11)
src/lazr/restful/tests/test_utils.py (+3/-0)
src/lazr/restful/tests/test_webservice.py (+93/-22)
src/lazr/restful/utils.py (+19/-0)
To merge this branch: bzr merge lp:~leonardr/lazr.restful/launchpad-integration
Reviewer Review Type Date Requested Status
Paul Hummer (community) code Approve
Review via email: mp+19639@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

This branch creates a new utility function get_current_web_service_request() and changes all lazr.restful code to use it instead of get_current_browser_request(). I have not removed get_current_browser_request(), because I know it's used correctly elsewhere (eg. in Launchpad).

The multi-version code creates a big distinction between requests initiated by a web service client, and requests initiated by a web browser. Specifically, web browser requests don't have a version number or version-specific marker interface associated with them.

When I integrated the multi-version lazr.restful into Launchpadlib, this caused many Windmill tests to fail: the browser would make a request, and Launchpad would try to generate a representation of the context as though the incoming request were a web service request. But since the incoming request wasn't versioned, the versioned IEntry lookup would fail, and it would appear as though (eg.) bugs and users weren't published on the web service. Without a value in LP.cache.context, subsequent Ajax requests would fail, causing the Windmill test to fail.

The most confusing part of this branch is only somewhat related. While trying to get test_applyChanges_binds_to_resource_context to use a versioned request object, I noticed that applyChanges wasn't really doing anything (even in the case where the test supposedly passed). I added tests that prove that applyChanges changes the value for the field it's trying to change, and to get them to work I had to define a dummy IAbsoluteURL implementation for the entry on which applyChanges was running.

Revision history for this message
Paul Hummer (rockstar) wrote :

Thanks for giving me an opportunity to look at this code. It all looks good to me.

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/_resource.py'
--- src/lazr/restful/_resource.py 2010-02-15 12:18:53 +0000
+++ src/lazr/restful/_resource.py 2010-02-18 17:57:15 +0000
@@ -87,7 +87,7 @@
87 IUnmarshallingDoesntNeedValue, IWebServiceClientRequest,87 IUnmarshallingDoesntNeedValue, IWebServiceClientRequest,
88 IWebServiceConfiguration, IWebServiceLayer, IWebServiceVersion,88 IWebServiceConfiguration, IWebServiceLayer, IWebServiceVersion,
89 LAZR_WEBSERVICE_NAME)89 LAZR_WEBSERVICE_NAME)
90from lazr.restful.utils import get_current_browser_request90from lazr.restful.utils import get_current_web_service_request
9191
9292
93# The path to the WADL XML Schema definition.93# The path to the WADL XML Schema definition.
@@ -158,7 +158,7 @@
158 return tuple(obj)158 return tuple(obj)
159 if isinstance(underlying_object, dict):159 if isinstance(underlying_object, dict):
160 return dict(obj)160 return dict(obj)
161 request = get_current_browser_request()161 request = get_current_web_service_request()
162 if queryMultiAdapter((obj, request), IEntry):162 if queryMultiAdapter((obj, request), IEntry):
163 obj = EntryResource(obj, request)163 obj = EntryResource(obj, request)
164164
@@ -1549,8 +1549,8 @@
15491549
1550 @property1550 @property
1551 def request(self):1551 def request(self):
1552 """Fetch the current browser request."""1552 """Fetch the current web service request."""
1553 return get_current_browser_request()1553 return get_current_web_service_request()
15541554
1555 def _getETagCore(self, cache=None):1555 def _getETagCore(self, cache=None):
1556 """Calculate an ETag for a representation of this resource.1556 """Calculate an ETag for a representation of this resource.
@@ -1793,7 +1793,7 @@
17931793
1794 def _service_root_url(self):1794 def _service_root_url(self):
1795 """Return the URL to the service root."""1795 """Return the URL to the service root."""
1796 request = get_current_browser_request()1796 request = get_current_web_service_request()
1797 return absoluteURL(request.publication.getApplication(request),1797 return absoluteURL(request.publication.getApplication(request),
1798 request)1798 request)
17991799
18001800
=== modified file 'src/lazr/restful/declarations.py'
--- src/lazr/restful/declarations.py 2010-02-16 16:14:01 +0000
+++ src/lazr/restful/declarations.py 2010-02-18 17:57:15 +0000
@@ -60,7 +60,7 @@
60 Collection, Entry, EntryAdapterUtility, ResourceOperation, ObjectLink)60 Collection, Entry, EntryAdapterUtility, ResourceOperation, ObjectLink)
61from lazr.restful.security import protect_schema61from lazr.restful.security import protect_schema
62from lazr.restful.utils import (62from lazr.restful.utils import (
63 camelcase_to_underscore_separated, get_current_browser_request,63 camelcase_to_underscore_separated, get_current_web_service_request,
64 make_identifier_safe, VersionedDict)64 make_identifier_safe, VersionedDict)
6565
66LAZR_WEBSERVICE_EXPORTED = '%s.exported' % LAZR_WEBSERVICE_NS66LAZR_WEBSERVICE_EXPORTED = '%s.exported' % LAZR_WEBSERVICE_NS
@@ -1030,7 +1030,7 @@
1030 def __get__(self, instance, owner):1030 def __get__(self, instance, owner):
1031 """Look up the entry schema that adapts the model schema."""1031 """Look up the entry schema that adapts the model schema."""
1032 if instance is None or instance.request is None:1032 if instance is None or instance.request is None:
1033 request = get_current_browser_request()1033 request = get_current_web_service_request()
1034 else:1034 else:
1035 request = instance.request1035 request = instance.request
1036 request_interface = getUtility(1036 request_interface = getUtility(
10371037
=== modified file 'src/lazr/restful/docs/multiversion.txt'
--- src/lazr/restful/docs/multiversion.txt 2010-02-11 17:57:16 +0000
+++ src/lazr/restful/docs/multiversion.txt 2010-02-18 17:57:15 +0000
@@ -174,7 +174,7 @@
174 >>> from zope.publisher.interfaces.browser import IBrowserRequest174 >>> from zope.publisher.interfaces.browser import IBrowserRequest
175 >>> from lazr.restful.interfaces import IServiceRootResource175 >>> from lazr.restful.interfaces import IServiceRootResource
176 >>> from lazr.restful.simple import TraverseWithGet176 >>> from lazr.restful.simple import TraverseWithGet
177 >>> from lazr.restful.utils import get_current_browser_request177 >>> from lazr.restful.utils import get_current_web_service_request
178 >>> class ContactSet(TraverseWithGet):178 >>> class ContactSet(TraverseWithGet):
179 ... implements(IContactSet, ILocation)179 ... implements(IContactSet, ILocation)
180 ...180 ...
@@ -204,13 +204,13 @@
204 ...204 ...
205 ... @property205 ... @property
206 ... def __parent__(self):206 ... def __parent__(self):
207 ... request = get_current_browser_request()207 ... request = get_current_web_service_request()
208 ... return getUtility(208 ... return getUtility(
209 ... IServiceRootResource, name=request.version)209 ... IServiceRootResource, name=request.version)
210 ...210 ...
211 ... @property211 ... @property
212 ... def __name__(self):212 ... def __name__(self):
213 ... request = get_current_browser_request()213 ... request = get_current_web_service_request()
214 ... if request.version == 'beta':214 ... if request.version == 'beta':
215 ... return 'contact_list'215 ... return 'contact_list'
216 ... return 'contacts'216 ... return 'contacts'
217217
=== modified file 'src/lazr/restful/example/base/filemanager.py'
--- src/lazr/restful/example/base/filemanager.py 2009-10-20 17:00:23 +0000
+++ src/lazr/restful/example/base/filemanager.py 2010-02-18 17:57:15 +0000
@@ -21,7 +21,7 @@
2121
22from lazr.restful import ReadOnlyResource22from lazr.restful import ReadOnlyResource
23from lazr.restful.example.base.interfaces import IFileManager23from lazr.restful.example.base.interfaces import IFileManager
24from lazr.restful.utils import get_current_browser_request24from lazr.restful.utils import get_current_web_service_request
2525
2626
27class FileManager:27class FileManager:
@@ -65,7 +65,7 @@
6565
66 def __call__(self):66 def __call__(self):
67 """Write the file to the current request."""67 """Write the file to the current request."""
68 request = get_current_browser_request()68 request = get_current_web_service_request()
69 response = request.response69 response = request.response
7070
71 # Handle a conditional request71 # Handle a conditional request
7272
=== modified file 'src/lazr/restful/example/base/root.py'
--- src/lazr/restful/example/base/root.py 2010-02-11 17:57:16 +0000
+++ src/lazr/restful/example/base/root.py 2010-02-18 17:57:15 +0000
@@ -29,7 +29,7 @@
29 IFileManager, IRecipe, IRecipeSet, IHasGet, NameAlreadyTaken)29 IFileManager, IRecipe, IRecipeSet, IHasGet, NameAlreadyTaken)
30from lazr.restful.simple import BaseWebServiceConfiguration30from lazr.restful.simple import BaseWebServiceConfiguration
31from lazr.restful.testing.webservice import WebServiceTestPublication31from lazr.restful.testing.webservice import WebServiceTestPublication
32from lazr.restful.utils import get_current_browser_request32from lazr.restful.utils import get_current_web_service_request
3333
3434
35#Entry classes.35#Entry classes.
@@ -148,7 +148,8 @@
148 self.recipes.remove(recipe)148 self.recipes.remove(recipe)
149149
150 def replace_cover(self, cover):150 def replace_cover(self, cover):
151 entry = getMultiAdapter((self, get_current_browser_request()), IEntry)151 entry = getMultiAdapter(
152 (self, get_current_web_service_request()), IEntry)
152 storage = SimpleByteStorage(entry, ICookbook['cover'])153 storage = SimpleByteStorage(entry, ICookbook['cover'])
153 storage.createStored('application/octet-stream', cover, 'cover')154 storage.createStored('application/octet-stream', cover, 'cover')
154155
155156
=== modified file 'src/lazr/restful/tales.py'
--- src/lazr/restful/tales.py 2010-02-11 16:32:17 +0000
+++ src/lazr/restful/tales.py 2010-02-18 17:57:15 +0000
@@ -34,7 +34,7 @@
34 IResourceOperation, IResourcePOSTOperation, IScopedCollection,34 IResourceOperation, IResourcePOSTOperation, IScopedCollection,
35 ITopLevelEntryLink, IWebServiceClientRequest, IWebServiceVersion,35 ITopLevelEntryLink, IWebServiceClientRequest, IWebServiceVersion,
36 LAZR_WEBSERVICE_NAME)36 LAZR_WEBSERVICE_NAME)
37from lazr.restful.utils import get_current_browser_request37from lazr.restful.utils import get_current_web_service_request
3838
3939
40class WadlDocstringLinker(DocstringLinker):40class WadlDocstringLinker(DocstringLinker):
@@ -110,13 +110,13 @@
110 @property110 @property
111 def is_entry(self):111 def is_entry(self):
112 """Whether the object is published as an entry."""112 """Whether the object is published as an entry."""
113 return queryMultiAdapter(113 request = get_current_web_service_request()
114 (self.context, get_current_browser_request()), IEntry) != None114 return queryMultiAdapter((self.context, request), IEntry) != None
115115
116 @property116 @property
117 def json(self):117 def json(self):
118 """Return a JSON description of the object."""118 """Return a JSON description of the object."""
119 request = IWebServiceClientRequest(get_current_browser_request())119 request = get_current_web_service_request
120 if queryMultiAdapter((self.context, request), IEntry):120 if queryMultiAdapter((self.context, request), IEntry):
121 resource = EntryResource(self.context, request)121 resource = EntryResource(self.context, request)
122 else:122 else:
@@ -137,7 +137,7 @@
137 @property137 @property
138 def url(self):138 def url(self):
139 """Return the full URL to the resource."""139 """Return the full URL to the resource."""
140 return absoluteURL(self.context, get_current_browser_request())140 return absoluteURL(self.context, get_current_web_service_request())
141141
142142
143class WadlEntryResourceAPI(WadlResourceAPI):143class WadlEntryResourceAPI(WadlResourceAPI):
@@ -182,7 +182,7 @@
182 else:182 else:
183 relationship_name = self.context.relationship.__name__183 relationship_name = self.context.relationship.__name__
184 return (absoluteURL(self.context.context,184 return (absoluteURL(self.context.context,
185 get_current_browser_request()) + '/' +185 get_current_web_service_request()) + '/' +
186 urllib.quote(relationship_name))186 urllib.quote(relationship_name))
187 else:187 else:
188 return super(WadlCollectionResourceAPI, self).url188 return super(WadlCollectionResourceAPI, self).url
@@ -287,7 +287,7 @@
287 model_class = self._model_class287 model_class = self._model_class
288 operations = []288 operations = []
289 request_interface = getUtility(289 request_interface = getUtility(
290 IWebServiceVersion, get_current_browser_request().version)290 IWebServiceVersion, get_current_web_service_request().version)
291 for interface in (IResourceGETOperation, IResourcePOSTOperation):291 for interface in (IResourceGETOperation, IResourcePOSTOperation):
292 operations.extend(getGlobalSiteManager().adapters.lookupAll(292 operations.extend(getGlobalSiteManager().adapters.lookupAll(
293 (model_class, request_interface), interface))293 (model_class, request_interface), interface))
@@ -309,7 +309,7 @@
309 super(WadlEntryInterfaceAdapterAPI, self).__init__(309 super(WadlEntryInterfaceAdapterAPI, self).__init__(
310 entry_interface, IEntry)310 entry_interface, IEntry)
311 self.utility = EntryAdapterUtility.forEntryInterface(311 self.utility = EntryAdapterUtility.forEntryInterface(
312 entry_interface, get_current_browser_request())312 entry_interface, get_current_web_service_request())
313313
314 @property314 @property
315 def entry_page_representation_link(self):315 def entry_page_representation_link(self):
@@ -383,7 +383,7 @@
383 def supports_delete(self):383 def supports_delete(self):
384 """Return true if this entry responds to DELETE."""384 """Return true if this entry responds to DELETE."""
385 request_interface = getUtility(385 request_interface = getUtility(
386 IWebServiceVersion, get_current_browser_request().version)386 IWebServiceVersion, get_current_web_service_request().version)
387 operations = getGlobalSiteManager().adapters.lookupAll(387 operations = getGlobalSiteManager().adapters.lookupAll(
388 (self._model_class, request_interface),388 (self._model_class, request_interface),
389 IResourceDELETEOperation)389 IResourceDELETEOperation)
@@ -521,7 +521,7 @@
521 assert schema is not IObject, (521 assert schema is not IObject, (
522 "Null schema provided for %s" % self.field.__name__)522 "Null schema provided for %s" % self.field.__name__)
523 return EntryAdapterUtility.forSchemaInterface(523 return EntryAdapterUtility.forSchemaInterface(
524 schema, get_current_browser_request())524 schema, get_current_web_service_request())
525525
526526
527 @property527 @property
@@ -546,7 +546,7 @@
546 def type_link(self):546 def type_link(self):
547 return EntryAdapterUtility.forSchemaInterface(547 return EntryAdapterUtility.forSchemaInterface(
548 self.entry_link.entry_type,548 self.entry_link.entry_type,
549 get_current_browser_request()).type_link549 get_current_web_service_request()).type_link
550550
551551
552class WadlOperationAPI(RESTUtilityBase):552class WadlOperationAPI(RESTUtilityBase):
553553
=== modified file 'src/lazr/restful/tests/test_utils.py'
--- src/lazr/restful/tests/test_utils.py 2010-01-15 14:12:26 +0000
+++ src/lazr/restful/tests/test_utils.py 2010-02-18 17:57:15 +0000
@@ -27,3 +27,6 @@
27 newInteraction(request)27 newInteraction(request)
28 self.assertEquals(request, get_current_browser_request())28 self.assertEquals(request, get_current_browser_request())
29 endInteraction()29 endInteraction()
30
31 # For the sake of convenience, test_get_current_web_service_request()
32 # is tested in test_webservice.py.
3033
=== modified file 'src/lazr/restful/tests/test_webservice.py'
--- src/lazr/restful/tests/test_webservice.py 2010-02-11 17:57:16 +0000
+++ src/lazr/restful/tests/test_webservice.py 2010-02-18 17:57:15 +0000
@@ -5,6 +5,7 @@
5__metaclass__ = type5__metaclass__ = type
66
7from cStringIO import StringIO7from cStringIO import StringIO
8from operator import attrgetter
8import sys9import sys
9from types import ModuleType10from types import ModuleType
10import unittest11import unittest
@@ -12,25 +13,30 @@
12from zope.component import getGlobalSiteManager, getUtility13from zope.component import getGlobalSiteManager, getUtility
13from zope.configuration import xmlconfig14from zope.configuration import xmlconfig
14from zope.interface import alsoProvides, implements, Interface15from zope.interface import alsoProvides, implements, Interface
16from zope.publisher.browser import TestRequest
15from zope.schema import Date, Datetime, TextLine17from zope.schema import Date, Datetime, TextLine
18from zope.security.management import (
19 endInteraction, newInteraction, queryInteraction)
16from zope.testing.cleanup import CleanUp20from zope.testing.cleanup import CleanUp
17from zope.traversing.browser.interfaces import IAbsoluteURL21from zope.traversing.browser.interfaces import IAbsoluteURL
1822
19from lazr.restful.fields import Reference23from lazr.restful.fields import Reference
20from lazr.restful.interfaces import (24from lazr.restful.interfaces import (
21 ICollection, IEntry, IEntryResource, IResourceGETOperation,25 ICollection, IEntry, IEntryResource, IResourceGETOperation,
22 IServiceRootResource, IWebServiceClientRequest, IWebServiceVersion)26 IServiceRootResource, IWebServiceConfiguration,
27 IWebServiceClientRequest, IWebServiceVersion)
23from lazr.restful import (28from lazr.restful import (
24 EntryResource, ServiceRootResource, ResourceGETOperation)29 EntryResource, ServiceRootResource, ResourceGETOperation)
25from lazr.restful.simple import BaseWebServiceConfiguration, Request30from lazr.restful.simple import BaseWebServiceConfiguration, Request
26from lazr.restful.declarations import (31from lazr.restful.declarations import (
27 collection_default_content, exported, export_as_webservice_collection,32 collection_default_content, exported, export_as_webservice_collection,
28 export_as_webservice_entry, export_read_operation, operation_parameters)33 export_as_webservice_entry, export_read_operation, operation_parameters)
29from lazr.restful.interfaces import IWebServiceConfiguration
30from lazr.restful.simple import RootResourceAbsoluteURL34from lazr.restful.simple import RootResourceAbsoluteURL
31from lazr.restful.testing.webservice import (35from lazr.restful.testing.webservice import (
32 create_web_service_request, WebServiceTestPublication)36 create_web_service_request, WebServiceTestPublication)
33from lazr.restful.testing.tales import test_tales37from lazr.restful.testing.tales import test_tales
38from lazr.restful.utils import (
39 get_current_browser_request, get_current_web_service_request)
3440
3541
36def get_resource_factory(model_interface, resource_interface):42def get_resource_factory(model_interface, resource_interface):
@@ -42,7 +48,7 @@
42 `IEntry` or `ICollection`.48 `IEntry` or `ICollection`.
43 :return: the resource factory (the autogenerated adapter class.49 :return: the resource factory (the autogenerated adapter class.
44 """50 """
45 request_interface = getUtility(IWebServiceVersion, name='trunk')51 request_interface = getUtility(IWebServiceVersion, name='2.0')
46 return getGlobalSiteManager().adapters.lookup(52 return getGlobalSiteManager().adapters.lookup(
47 (model_interface, request_interface), resource_interface)53 (model_interface, request_interface), resource_interface)
4854
@@ -56,7 +62,7 @@
56 :return: the factory (autogenerated class) that implements the operation62 :return: the factory (autogenerated class) that implements the operation
57 on the webservice.63 on the webservice.
58 """64 """
59 request_interface = getUtility(IWebServiceVersion, name='trunk')65 request_interface = getUtility(IWebServiceVersion, name='2.0')
60 return getGlobalSiteManager().adapters.lookup(66 return getGlobalSiteManager().adapters.lookup(
61 (model_interface, request_interface),67 (model_interface, request_interface),
62 IResourceGETOperation, name=name)68 IResourceGETOperation, name=name)
@@ -95,23 +101,30 @@
95 """Returns all the entries."""101 """Returns all the entries."""
96102
97103
104class IWebServiceRequest10(IWebServiceClientRequest):
105 """A marker interface for requests to the '1.0' web service."""
106
107
108class IWebServiceRequest20(IWebServiceClientRequest):
109 """A marker interface for requests to the '2.0' web service."""
110
111
98class SimpleWebServiceConfiguration(BaseWebServiceConfiguration):112class SimpleWebServiceConfiguration(BaseWebServiceConfiguration):
99 implements(IWebServiceConfiguration)113 implements(IWebServiceConfiguration)
100 show_tracebacks = False114 show_tracebacks = False
101 active_versions = ['trunk']115 active_versions = ['1.0', '2.0']
102 hostname = "webservice_test"116 hostname = "webservice_test"
103117
104 def createRequest(self, body_instream, environ):118 def createRequest(self, body_instream, environ):
105 request = Request(body_instream, environ)119 request = Request(body_instream, environ)
106 request.setPublication(WebServiceTestPublication(None))120 request.version = '2.0'
107 request.version = 'trunk'121 alsoProvides(request, IWebServiceRequest20)
122 request.setPublication(WebServiceTestPublication(
123 getUtility(IServiceRootResource)))
124 request.annotations[request.VERSION_ANNOTATION] = '2.0'
108 return request125 return request
109126
110127
111class IWebServiceRequestTrunk(IWebServiceClientRequest):
112 """A marker interface for requests to the 'trunk' web service."""
113
114
115class WebServiceTestCase(CleanUp, unittest.TestCase):128class WebServiceTestCase(CleanUp, unittest.TestCase):
116 """A test case for web service operations."""129 """A test case for web service operations."""
117130
@@ -126,11 +139,14 @@
126 sm = getGlobalSiteManager()139 sm = getGlobalSiteManager()
127 sm.registerUtility(webservice_configuration)140 sm.registerUtility(webservice_configuration)
128141
129 # Register an IWebServiceVersion for the142 # Register IWebServiceVersions for the
130 # 'trunk' web service version.143 # '1.0' and '2.0' web service versions.
131 alsoProvides(IWebServiceRequestTrunk, IWebServiceVersion)144 alsoProvides(IWebServiceRequest10, IWebServiceVersion)
132 sm.registerUtility(145 sm.registerUtility(
133 IWebServiceRequestTrunk, IWebServiceVersion, name='trunk')146 IWebServiceRequest10, IWebServiceVersion, name='1.0')
147 alsoProvides(IWebServiceRequest20, IWebServiceVersion)
148 sm.registerUtility(
149 IWebServiceRequest20, IWebServiceVersion, name='2.0')
134150
135 # Register a service root resource151 # Register a service root resource
136 service_root = ServiceRootResource()152 service_root = ServiceRootResource()
@@ -224,11 +240,28 @@
224 self.a_field = value240 self.a_field = value
225241
226242
243class DummyAbsoluteURL:
244 """Implements IAbsoluteURL for when you don't care what the URL is."""
245 def __init__(self, *args):
246 pass
247
248 def __str__(self):
249 return 'http://dummyurl/'
250
251 __call__ = __str__
252
253
227class EntryTestCase(WebServiceTestCase):254class EntryTestCase(WebServiceTestCase):
228 """A test suite for entries."""255 """A test suite for entries."""
229256
230 testmodule_objects = [HasRestrictedField, IHasRestrictedField]257 testmodule_objects = [HasRestrictedField, IHasRestrictedField]
231258
259 def setUp(self):
260 super(EntryTestCase, self).setUp()
261 getGlobalSiteManager().registerAdapter(
262 DummyAbsoluteURL, [IHasRestrictedField, IWebServiceClientRequest],
263 IAbsoluteURL)
264
232 def test_applyChanges_binds_to_resource_context(self):265 def test_applyChanges_binds_to_resource_context(self):
233 """Make sure applyChanges binds fields to the resource context.266 """Make sure applyChanges binds fields to the resource context.
234267
@@ -238,13 +271,15 @@
238 expose the right interface, it will raise an exception.271 expose the right interface, it will raise an exception.
239 """272 """
240 entry_class = get_resource_factory(IHasRestrictedField, IEntry)273 entry_class = get_resource_factory(IHasRestrictedField, IEntry)
241 request = Request(StringIO(""), {})274 request = getUtility(IWebServiceConfiguration).createRequest(
275 StringIO(""), {})
242276
243 entry = entry_class(HasRestrictedField(""), request)277 entry = entry_class(HasRestrictedField(""), request)
244 resource = EntryResource(HasRestrictedField(""), request)278 resource = EntryResource(HasRestrictedField(""), request)
245
246 entry.schema['a_field'].restrict_to_interface = IHasRestrictedField279 entry.schema['a_field'].restrict_to_interface = IHasRestrictedField
247 resource.applyChanges({'a_field': 'a_value'})280 self.assertEquals(resource.entry.a_field, '')
281 resource.applyChanges({'a_field': u'a_value'})
282 self.assertEquals(resource.entry.a_field, 'a_value')
248283
249 # Make sure that IHasRestrictedField itself works correctly.284 # Make sure that IHasRestrictedField itself works correctly.
250 class IOtherInterface(Interface):285 class IOtherInterface(Interface):
@@ -252,8 +287,8 @@
252 pass287 pass
253 entry.schema['a_field'].restrict_to_interface = IOtherInterface288 entry.schema['a_field'].restrict_to_interface = IOtherInterface
254 self.assertRaises(AssertionError, resource.applyChanges,289 self.assertRaises(AssertionError, resource.applyChanges,
255 {'a_field': 'a_value'})290 {'a_field': u'a_new_value'})
256291 self.assertEquals(resource.entry.a_field, 'a_value')
257292
258class WadlAPITestCase(WebServiceTestCase):293class WadlAPITestCase(WebServiceTestCase):
259 """Test the docstring generation."""294 """Test the docstring generation."""
@@ -353,7 +388,7 @@
353 This will fail due to a name conflict.388 This will fail due to a name conflict.
354 """389 """
355 resource = getUtility(IServiceRootResource)390 resource = getUtility(IServiceRootResource)
356 request = create_web_service_request('/trunk')391 request = create_web_service_request('/2.0')
357 request.traverse(resource)392 request.traverse(resource)
358 try:393 try:
359 resource.toWADL()394 resource.toWADL()
@@ -390,5 +425,41 @@
390 "plural name 'generic_entrys'.")425 "plural name 'generic_entrys'.")
391426
392427
428class GetCurrentWebServiceRequestTestCase(WebServiceTestCase):
429 """Test the get_current_web_service_request utility function."""
430
431 testmodule_objects = [IGenericEntry]
432
433 def test_web_service_request_is_versioned(self):
434 """Ensure the get_current_web_service_request() result is versioned.
435
436 When a normal browser request is turned into a web service
437 request, it needs to have a version associated with it.
438 lazr.restful associates the new request with the latest
439 version of the web service: in this case, version 2.0.
440 """
441
442 # When there's no interaction setup, get_current_web_service_request()
443 # returns None.
444 self.assertEquals(None, queryInteraction())
445 self.assertEquals(None, get_current_web_service_request())
446
447 # Set up an interaction.
448 request = TestRequest()
449 newInteraction(request)
450
451 # A normal web browser request isn't associated with any version.
452 website_request = get_current_browser_request()
453 self.assertRaises(AttributeError,
454 attrgetter('version', website_request), None)
455
456 # But the result of get_current_web_service_request() is
457 # associated with version 2.0.
458 webservice_request = get_current_web_service_request()
459 self.assertEquals("2.0", webservice_request.version)
460 marker_20 = getUtility(IWebServiceVersion, "2.0")
461 marker_20.providedBy(webservice_request)
462
463
393def additional_tests():464def additional_tests():
394 return unittest.TestLoader().loadTestsFromName(__name__)465 return unittest.TestLoader().loadTestsFromName(__name__)
395466
=== modified file 'src/lazr/restful/utils.py'
--- src/lazr/restful/utils.py 2010-01-20 15:21:38 +0000
+++ src/lazr/restful/utils.py 2010-02-18 17:57:15 +0000
@@ -6,6 +6,7 @@
6__all__ = [6__all__ = [
7 'camelcase_to_underscore_separated',7 'camelcase_to_underscore_separated',
8 'get_current_browser_request',8 'get_current_browser_request',
9 'get_current_web_service_request',
9 'implement_from_dict',10 'implement_from_dict',
10 'make_identifier_safe',11 'make_identifier_safe',
11 'safe_js_escape',12 'safe_js_escape',
@@ -27,6 +28,8 @@
27from zope.schema import getFieldsInOrder28from zope.schema import getFieldsInOrder
28from zope.interface import classImplements29from zope.interface import classImplements
2930
31from lazr.restful.interfaces import IWebServiceClientRequest
32
3033
31missing = object()34missing = object()
3235
@@ -217,6 +220,22 @@
217 return requests[0]220 return requests[0]
218221
219222
223def get_current_web_service_request():
224 """Return the current web service request.
225
226 This may be a new request object based on the browser request (if
227 the client is a web browser that might make AJAX calls to a
228 separate web service) or it may be the same as the browser request
229 (if the client is a web service client accessing the service
230 directly).
231
232 :return: An object providing IWebserviceClientRequest.
233 """
234 request = get_current_browser_request()
235 if request is None:
236 return None
237 return IWebServiceClientRequest(request)
238
220def simple_popen2(command, input, env=None, in_bufsize=1024, out_bufsize=128):239def simple_popen2(command, input, env=None, in_bufsize=1024, out_bufsize=128):
221 """Run a command, give it input on its standard input, and capture its240 """Run a command, give it input on its standard input, and capture its
222 standard output.241 standard output.

Subscribers

People subscribed via source and target branches