[Checkins] SVN: grok/branches/faassen-rest/src/grok/ Improve error reporting for REST.

Martijn Faassen faassen at infrae.com
Thu Sep 20 18:26:54 EDT 2007


Log message for revision 79775:
  Improve error reporting for REST.
  

Changed:
  U   grok/branches/faassen-rest/src/grok/components.py
  U   grok/branches/faassen-rest/src/grok/configure.zcml
  U   grok/branches/faassen-rest/src/grok/ftests/rest/rest.py
  U   grok/branches/faassen-rest/src/grok/meta.py
  U   grok/branches/faassen-rest/src/grok/rest.py

-=-
Modified: grok/branches/faassen-rest/src/grok/components.py
===================================================================
--- grok/branches/faassen-rest/src/grok/components.py	2007-09-20 21:47:48 UTC (rev 79774)
+++ grok/branches/faassen-rest/src/grok/components.py	2007-09-20 22:26:54 UTC (rev 79775)
@@ -51,6 +51,7 @@
 from zope.app.container.contained import Contained
 from zope.app.container.interfaces import IReadContainer
 from zope.app.component.site import SiteManagerContainer
+from zope.app.publication.http import MethodNotAllowed
 
 import z3c.flashmessage.interfaces
 
@@ -192,9 +193,19 @@
 
 
 class REST(object):
-    pass
+    def GET(self):
+        raise MethodNotAllowed(self.context, self.request)
+    
+    def POST(self):
+        raise MethodNotAllowed(self.context, self.request)
 
+    def PUT(self):
+        raise MethodNotAllowed(self.context, self.request)
+    
+    def DELETE(self):
+        raise MethodNotAllowed(self.context, self.request)
 
+
 class JSON(BrowserPage):
 
     def __call__(self):

Modified: grok/branches/faassen-rest/src/grok/configure.zcml
===================================================================
--- grok/branches/faassen-rest/src/grok/configure.zcml	2007-09-20 21:47:48 UTC (rev 79774)
+++ grok/branches/faassen-rest/src/grok/configure.zcml	2007-09-20 22:26:54 UTC (rev 79775)
@@ -76,6 +76,9 @@
       priority="11"
       />
 
+  <!-- need to grok this for some basic REST support -->
+  <grok:grok package=".rest" />
+
   <grok:grok package=".admin" />
 
 </configure>

Modified: grok/branches/faassen-rest/src/grok/ftests/rest/rest.py
===================================================================
--- grok/branches/faassen-rest/src/grok/ftests/rest/rest.py	2007-09-20 21:47:48 UTC (rev 79774)
+++ grok/branches/faassen-rest/src/grok/ftests/rest/rest.py	2007-09-20 22:26:54 UTC (rev 79775)
@@ -6,57 +6,159 @@
   >>> from grok.ftests.rest.rest import MyApp
   >>> root = getRootFolder()
   >>> root['app'] = MyApp()
-
+  >>> root['app']['alpha'] = MyContent()
+  
 Issue a GET request::
 
-  >>> response = http_call('GET', 'http://localhost/++rest++test/app')
+  >>> response = http_call('GET', 'http://localhost/++rest++a/app')
   >>> print response.getBody()
   GET
 
 Issue a POST request::
 
-  >>> response = http_call('POST', 'http://localhost/++rest++test/app')
+  >>> response = http_call('POST', 'http://localhost/++rest++a/app')
   >>> print response.getBody()
   POST
 
 Issue a PUT request::
 
-  >>> response = http_call('PUT', 'http://localhost/++rest++test/app')
+  >>> response = http_call('PUT', 'http://localhost/++rest++a/app')
   >>> print response.getBody()
   PUT
 
 Issue a DELETE request::
 
-  >>> response = http_call('DELETE', 'http://localhost/++rest++test/app')
+  >>> response = http_call('DELETE', 'http://localhost/++rest++a/app')
   >>> print response.getBody()
   DELETE
 
-405: Method not allowed
-PUT not supported. GET not supported
+Let's examine a rest protocol b which has no POST or DELETE request defined::
 
-Fall-back on base registrations.
+The GET request works as expected::
 
-Action against sub-object in container.
+  >>> response = http_call('GET', 'http://localhost/++rest++b/app')
+  >>> print response.getBody()
+  GET
 
-Test skin story.
+So does the PUT request::
 
-Security tests.
+  >>> response = http_call('PUT', 'http://localhost/++rest++b/app')
+  >>> print response.getBody()
+  PUT
+
+POST is not defined, however, and we should get a 405 (Method not
+allowed) error::
+
+  >>> response = http_call('POST', 'http://localhost/++rest++b/app')
+  Traceback (most recent call last):
+    ...
+  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...>,
+  <zope.publisher.browser.BrowserRequest instance URL=http://localhost/++rest++b/app/@@POST>
+
+DELETE is also not defined, so we also expect a 405 error::
+
+  >>> response = http_call('DELETE', 'http://localhost/++rest++b/app')
+  Traceback (most recent call last):
+    ...
+  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...>,
+  <zope.publisher.http.HTTPRequest instance URL=http://localhost/++rest++b/app>
+
+Let's examine protocol c where no method is allowed::
+
+  >>> response = http_call('GET', 'http://localhost/++rest++c/app')
+  Traceback (most recent call last):
+    ...
+  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  >>> response = http_call('POST', 'http://localhost/++rest++c/app')
+  Traceback (most recent call last):
+    ...
+  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  >>> response = http_call('PUT', 'http://localhost/++rest++c/app')
+  Traceback (most recent call last):
+    ...
+  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  >>> response = http_call('DELETE', 'http://localhost/++rest++c/app')
+  Traceback (most recent call last):
+    ...
+  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+
+Let's examine the default protocol d, where nothing should work as well::
+
+  >>> response = http_call('GET', 'http://localhost/++rest++d/app')
+  Traceback (most recent call last):
+    ...
+  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  >>> response = http_call('POST', 'http://localhost/++rest++d/app')
+  Traceback (most recent call last):
+    ...
+  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  >>> response = http_call('PUT', 'http://localhost/++rest++d/app')
+  Traceback (most recent call last):
+    ...
+  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  >>> response = http_call('DELETE', 'http://localhost/++rest++d/app')
+  Traceback (most recent call last):
+    ...
+  MethodNotAllowed: <grok.ftests.rest.rest.MyApp object at ...
+  
+We have added support for GET for the ``alpha`` subobject only, in
+the default rest layer::
+
+  >>> response = http_call('GET', 'http://localhost/++rest++d/app/alpha')
+  >>> response.getBody()
+  'GET2'
+
+But not for POST::
+
+  >>> response = http_call('POST', 'http://localhost/++rest++d/app/alpha')
+  Traceback (most recent call last):
+    ...
+  MethodNotAllowed: <grok.ftests.rest.rest.MyContent object at ...
+
+Todo:
+
+* MethodNotAllowed error URLs for GET and POST have @@GET and @@POST
+  attached. Not pretty.
+
+* Security tests.
+
+* According to HTTP spec: 405 Method Not Allowed:
+  The response MUST include an Allow header containing a list of valid
+  methods for the requested resource.
 """
 
 import grok
 
-class MyApp(grok.Model, grok.Application):
+class MyApp(grok.Container, grok.Application):
     pass
 
-class MySkinLayer(grok.IRESTLayer):
+class MyContent(grok.Model):
     pass
 
-class MySkin(grok.RESTProtocol):
-    grok.name('test')
-    grok.layer(MySkinLayer)
+class LayerA(grok.IRESTLayer):
+    pass
 
-class MyRest(grok.REST):
-    grok.layer(MySkinLayer)
+class LayerB(grok.IRESTLayer):
+    pass
+
+class LayerC(grok.IRESTLayer):
+    pass
+
+class A(grok.RESTProtocol):
+    grok.layer(LayerA)
+
+class B(grok.RESTProtocol):
+    grok.layer(LayerB)
+
+class C(grok.RESTProtocol):
+    grok.layer(LayerC)
+
+class D(grok.RESTProtocol):
+    grok.layer(grok.IRESTLayer)
+
+class ARest(grok.REST):
+    grok.layer(LayerA)
+    grok.context(MyApp)
     
     def GET(self):
         return "GET"
@@ -69,5 +171,23 @@
 
     def DELETE(self):
         return "DELETE"
+
+class BRest(grok.REST):
+    grok.layer(LayerB)
+    grok.context(MyApp)
     
+    def GET(self):
+        return "GET"
+
+    def PUT(self):
+        return "PUT"
+
+class CRest(grok.REST):
+    grok.layer(LayerC)
+    grok.context(MyApp)
+
+class DRest(grok.REST):
+    grok.context(MyContent)
     
+    def GET(self):
+        return "GET2"

Modified: grok/branches/faassen-rest/src/grok/meta.py
===================================================================
--- grok/branches/faassen-rest/src/grok/meta.py	2007-09-20 21:47:48 UTC (rev 79774)
+++ grok/branches/faassen-rest/src/grok/meta.py	2007-09-20 22:26:54 UTC (rev 79775)
@@ -48,7 +48,7 @@
 import grok
 from grok import components, formlib
 from grok.util import check_adapts, get_default_permission, make_checker
-from grok.rest import IDefaultRestLayer, IRestSkinType
+from grok.rest import IRESTSkinType
 
 class AdapterGrokker(martian.ClassGrokker):
     component_class = grok.Adapter
@@ -146,7 +146,7 @@
         # grab layer from class or module
         view_layer = determine_class_directive('grok.layer', factory,
                                                module_info,
-                                               default=IDefaultRestLayer)
+                                               default=grok.IRESTLayer)
 
         for method in methods:
             # Make sure that the class inherits RestPublisher, so that the
@@ -225,7 +225,9 @@
                                 "'render' method." % factory, factory)
 
         # grab layer from class or module
-        view_layer = determine_class_directive('grok.layer', factory, module_info, default=IDefaultBrowserLayer)
+        view_layer = determine_class_directive('grok.layer',
+                                               factory, module_info,
+                                               default=IDefaultBrowserLayer)
 
         view_name = util.class_annotation(factory, 'grok.name',
                                           factory_name)
@@ -716,7 +718,7 @@
         name = grok.util.class_annotation(factory, 'grok.name',
                                           factory.__name__.lower())
         zope.component.interface.provideInterface(name, layer,
-                                                  IRestSkinType)
+                                                  IRESTSkinType)
         return True
     
 def determine_class_directive(directive_name, factory, module_info,

Modified: grok/branches/faassen-rest/src/grok/rest.py
===================================================================
--- grok/branches/faassen-rest/src/grok/rest.py	2007-09-20 21:47:48 UTC (rev 79774)
+++ grok/branches/faassen-rest/src/grok/rest.py	2007-09-20 22:26:54 UTC (rev 79775)
@@ -1,20 +1,20 @@
+import grok
+
 from zope.traversing.namespace import skin
+from zope.interface import Interface
 from zope.interface.interfaces import IInterface
 from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.interfaces.http import IHTTPRequest
+from zope.app.publication.http import MethodNotAllowed
 
-class IDefaultRestLayer(IBrowserRequest):
-    pass
-
-class IRestSkinType(IInterface):
+class IRESTSkinType(IInterface):
     """Skin for REST requests.
     """
-
+    
 class rest_skin(skin):
-    skin_type = IRestSkinType
+    skin_type = IRESTSkinType
 
-    #def traverse(self, name, ignored):
-    #    import pdb; pdb.set_trace()
-    #    return super(rest_skin, self).traverse(name, ignored)
+class DefaultRest(grok.REST):
+    grok.context(Interface)
+    grok.layer(grok.IRESTLayer)
     
-
-        



More information about the Checkins mailing list