[Checkins] SVN: grok/trunk/src/grok/ Improvements to the REST support now that I try to use it. :)

Martijn Faassen faassen at infrae.com
Mon Oct 29 18:18:40 EDT 2007


Log message for revision 81184:
  Improvements to the REST support now that I try to use it. :)
  
  * a fundamental problem existed with the way NotAllowed errors were generated. 
    Previously a REST base class contained the methods that generated the 
    NotAllowedMethod. Unfortunately this meant that the whole basic idea of
    falling back on other registrations of REST views actually didn't work
    anymore. Added some tests demonstrating this, and instead register 
    a single REST view on interface/IRESTLayer (i.e. everything) that raises
    these errors. This gets grokked and all is fine and dandy.
  
  * Rearrange locations of some stuff while I'm at it.
  
  * Allow IRESTSkinType to be imported from Grok, as you may need it when
    setting up a rest skin manually.
  

Changed:
  U   grok/trunk/src/grok/__init__.py
  U   grok/trunk/src/grok/components.py
  U   grok/trunk/src/grok/ftests/rest/rest.py
  U   grok/trunk/src/grok/interfaces.py
  U   grok/trunk/src/grok/meta.py
  U   grok/trunk/src/grok/publication.py
  U   grok/trunk/src/grok/rest.py

-=-
Modified: grok/trunk/src/grok/__init__.py
===================================================================
--- grok/trunk/src/grok/__init__.py	2007-10-29 18:32:22 UTC (rev 81183)
+++ grok/trunk/src/grok/__init__.py	2007-10-29 22:18:39 UTC (rev 81184)
@@ -39,6 +39,7 @@
 from grok.components import Permission, Role
 from grok.components import Skin, IGrokLayer
 from grok.components import RESTProtocol, IRESTLayer
+from grok.interfaces import IRESTSkinType
 from grok.directive import (context, name, title, template, templatedir,
                             provides, baseclass, global_utility, local_utility,
                             permissions, require, site, layer)

Modified: grok/trunk/src/grok/components.py
===================================================================
--- grok/trunk/src/grok/components.py	2007-10-29 18:32:22 UTC (rev 81183)
+++ grok/trunk/src/grok/components.py	2007-10-29 22:18:39 UTC (rev 81184)
@@ -44,7 +44,6 @@
 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
 
@@ -94,6 +93,11 @@
     pass
 
 
+class ViewBase(object):
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
 class View(BrowserPage):
     interface.implements(interfaces.IGrokView)
 
@@ -177,10 +181,6 @@
 class XMLRPC(object):
     pass
 
-
-class GrokMethodNotAllowed(MethodNotAllowed):
-    pass
-
 class REST(object):
     interface.implements(interfaces.IREST)
     
@@ -188,18 +188,22 @@
         self.context = context
         self.request = request
         self.body = request.bodyStream.getCacheStream().read()
+
+    @property
+    def response(self):
+        return self.request.response
     
-    def GET(self):
-        raise GrokMethodNotAllowed(self.context, self.request)
+##     def GET(self):
+##         raise GrokMethodNotAllowed(self.context, self.request)
     
-    def POST(self):
-        raise GrokMethodNotAllowed(self.context, self.request)
+##     def POST(self):
+##         raise GrokMethodNotAllowed(self.context, self.request)
     
-    def PUT(self):
-        raise GrokMethodNotAllowed(self.context, self.request)
+##     def PUT(self):
+##         raise GrokMethodNotAllowed(self.context, self.request)
     
-    def DELETE(self):
-        raise GrokMethodNotAllowed(self.context, self.request)
+##     def DELETE(self):
+##         raise GrokMethodNotAllowed(self.context, self.request)
 
 class JSON(BrowserPage):
 

Modified: grok/trunk/src/grok/ftests/rest/rest.py
===================================================================
--- grok/trunk/src/grok/ftests/rest/rest.py	2007-10-29 18:32:22 UTC (rev 81183)
+++ grok/trunk/src/grok/ftests/rest/rest.py	2007-10-29 22:18:39 UTC (rev 81184)
@@ -244,9 +244,52 @@
   A server error occurred.
   </body></html>
   <BLANKLINE>
-  
+
 XXX shouldn't this really give a FORBIDDEN response?
 
+Let's add another two pieces of content, one for which a REST view is
+declared on the IFoo interface, and another one where this is also the
+case, but a more specific REST view is declared on the class itself::
+
+  >>> root['app']['one'] = MyInterfaceContent()
+  >>> root['app']['two'] = MyNoInterfaceContent()
+
+We should get a different result for the GET request::
+
+  >>> response = http_call('GET', 'http://localhost/++rest++g/app/one')
+  >>> print response.getBody()
+  GET interface registered
+  >>> response = http_call('GET', 'http://localhost/++rest++g/app/two')
+  >>> print response.getBody()
+  GET directly registered
+
+We should also get a different result for the PUT request::
+
+  >>> response = http_call('PUT', 'http://localhost/++rest++g/app/one')
+  >>> print response.getBody()
+  PUT interface registered
+  >>> response = http_call('PUT', 'http://localhost/++rest++g/app/two')
+  >>> print response.getBody()
+  PUT directly registered
+  
+We expect POST and DELETE to be the same on both. For the directly
+registered object (two) it should fall back to the interface as there
+is none more specifically declared::
+
+  >>> response = http_call('POST', 'http://localhost/++rest++g/app/one')
+  >>> print response.getBody()
+  POST interface registered
+  >>> response = http_call('POST', 'http://localhost/++rest++g/app/two')
+  >>> print response.getBody()
+  POST interface registered
+
+  >>> response = http_call('DELETE', 'http://localhost/++rest++g/app/one')
+  >>> print response.getBody()
+  DELETE interface registered
+  >>> response = http_call('DELETE', 'http://localhost/++rest++g/app/two')
+  >>> print response.getBody()
+  DELETE interface registered
+
 Todo:
 
 * Support for OPTIONS, HEAD, other methods?
@@ -255,13 +298,17 @@
 """
 
 import grok
+from zope.interface import Interface
 
+class IFoo(Interface):
+    pass
+
 class MyApp(grok.Container, grok.Application):
     pass
 
 class MyContent(grok.Model):
     pass
-
+    
 class LayerA(grok.IRESTLayer):
     pass
 
@@ -277,6 +324,9 @@
 class LayerContent(grok.IRESTLayer):
     pass
 
+class LayerInterface(grok.IRESTLayer):
+    pass
+
 class A(grok.RESTProtocol):
     grok.layer(LayerA)
 
@@ -294,6 +344,9 @@
 
 class F(grok.RESTProtocol):
     grok.layer(LayerContent)
+
+class G(grok.RESTProtocol):
+    grok.layer(LayerInterface)
     
 class ARest(grok.REST):
     grok.layer(LayerA)
@@ -360,4 +413,36 @@
 
     def PUT(self):
         return self.body
+
+class MyInterfaceContent(grok.Model):
+    grok.implements(IFoo)
+
+class MyNoInterfaceContent(grok.Model):
+    grok.implements(IFoo)
+
+class InterfaceRest(grok.REST):
+    grok.context(IFoo)
+    grok.layer(LayerInterface)
     
+    def GET(self):
+        return "GET interface registered"
+
+    def POST(self):
+        return "POST interface registered"
+
+    def PUT(self):
+        return "PUT interface registered"
+
+    def DELETE(self):
+        return "DELETE interface registered"
+
+class NoInterfaceRest(grok.REST):
+    grok.context(MyNoInterfaceContent)
+    grok.layer(LayerInterface)
+    
+    def GET(self):
+        return "GET directly registered"
+
+    def PUT(self):
+        return "PUT directly registered"
+

Modified: grok/trunk/src/grok/interfaces.py
===================================================================
--- grok/trunk/src/grok/interfaces.py	2007-10-29 18:32:22 UTC (rev 81183)
+++ grok/trunk/src/grok/interfaces.py	2007-10-29 22:18:39 UTC (rev 81184)
@@ -16,6 +16,7 @@
 from zope import interface, schema
 from zope.publisher.interfaces.browser import IBrowserPage
 from zope.formlib.interfaces import reConstraint
+from zope.interface.interfaces import IInterface
 
 class IGrokBaseClasses(interface.Interface):
     ClassGrokker = interface.Attribute("Base class to define a class "
@@ -252,6 +253,7 @@
         """grok-specific action decorator.
         """
 
+    IRESTSkinType = interface.Attribute('The REST skin type')
 
 class IGrokView(IBrowserPage):
     """Grok views all provide this interface."""
@@ -439,3 +441,7 @@
         Use name for index name and attribute to index. Set up
         index for interface or class context.
         """
+
+class IRESTSkinType(IInterface):
+    """Skin type for REST requests.
+    """

Modified: grok/trunk/src/grok/meta.py
===================================================================
--- grok/trunk/src/grok/meta.py	2007-10-29 18:32:22 UTC (rev 81183)
+++ grok/trunk/src/grok/meta.py	2007-10-29 22:18:39 UTC (rev 81184)
@@ -16,7 +16,6 @@
 import os
 
 import zope.component.interface
-import zope.location
 from zope import interface, component
 from zope.publisher.interfaces.browser import (IDefaultBrowserLayer,
                                                IBrowserRequest,
@@ -48,7 +47,8 @@
 import grok
 from grok import components, formlib
 from grok.util import check_adapts, get_default_permission, make_checker
-from grok.rest import IRESTSkinType
+from grok.rest import RestPublisher
+from grok.interfaces import IRESTSkinType
 
 class AdapterGrokker(martian.ClassGrokker):
     component_class = grok.Adapter
@@ -121,14 +121,6 @@
             make_checker(factory, method_view, permission)
         return True
 
-class RestPublisher(zope.location.Location):
-    interface.implements(IBrowserPublisher)
-
-    def __init__(self, context, request):
-        self.context = context
-        self.request = request
-        self.__parent__ = self.context
-    
 class RESTGrokker(martian.ClassGrokker):
     component_class = grok.REST
     
@@ -144,20 +136,13 @@
         view_layer = determine_class_directive('grok.layer', factory,
                                                module_info,
                                                default=grok.IRESTLayer)
-
+        
         for method in methods:
-            # determine if the method is not allowed (it's the same
-            # as the method on the superclass)
-            is_not_allowed_method = (method.im_func is
-                                     getattr(grok.REST,
-                                             method.__name__).im_func)
-
             # Make sure that the class inherits RestPublisher, so that the
             # views have a location
             method_view = type(
                 factory.__name__, (factory, RestPublisher),
-                {'__call__': method,
-                 'is_not_allowed': is_not_allowed_method }
+                {'__call__': method }
                 )
 
             component.provideAdapter(

Modified: grok/trunk/src/grok/publication.py
===================================================================
--- grok/trunk/src/grok/publication.py	2007-10-29 18:32:22 UTC (rev 81183)
+++ grok/trunk/src/grok/publication.py	2007-10-29 22:18:39 UTC (rev 81184)
@@ -14,7 +14,7 @@
 """Grok publication objects
 """
 
-from grok.components import GrokMethodNotAllowed
+from grok.rest import GrokMethodNotAllowed
 
 from zope import component
 from zope.security.proxy import removeSecurityProxy

Modified: grok/trunk/src/grok/rest.py
===================================================================
--- grok/trunk/src/grok/rest.py	2007-10-29 18:32:22 UTC (rev 81183)
+++ grok/trunk/src/grok/rest.py	2007-10-29 22:18:39 UTC (rev 81184)
@@ -6,14 +6,24 @@
 from zope.interface import Interface
 from zope.interface.interfaces import IInterface
 from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.interfaces.browser import IBrowserPublisher
 from zope.publisher.interfaces.http import IHTTPRequest
+from zope.app.publication.http import MethodNotAllowed
+import zope.location
 
-from grok.components import GrokMethodNotAllowed
+from grok.interfaces import IRESTSkinType
 
-class IRESTSkinType(IInterface):
-    """Skin for REST requests.
-    """
+class RestPublisher(zope.location.Location):
+    grok.implements(IBrowserPublisher)
 
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+        self.__parent__ = self.context
+
+class GrokMethodNotAllowed(MethodNotAllowed):
+    pass
+
 class MethodNotAllowedView(grok.MultiAdapter):
     grok.adapts(GrokMethodNotAllowed, IHTTPRequest)
     grok.name('index.html')
@@ -30,8 +40,10 @@
             view = component.queryMultiAdapter(
                 (self.error.object, self.error.request),
                 name=method)
-            if view is not None and not view.is_not_allowed:
-                allow.append(method)
+            if view is not None:
+                is_not_allowed = getattr(view, 'is_not_allowed', False)
+                if not is_not_allowed:
+                    allow.append(method)
         allow.sort()
         return allow
     
@@ -46,3 +58,27 @@
 class DefaultRest(grok.REST):
     grok.context(Interface)
     grok.layer(grok.IRESTLayer)
+
+class NotAllowedREST(grok.REST):
+    """These are registered for everything by default to cause the correct
+    errors.
+
+    Any more specific REST view overrides this.
+    """
+    grok.layer(grok.IRESTLayer)
+    grok.context(Interface)
+
+    is_not_allowed = True
+    
+    def GET(self):
+        raise GrokMethodNotAllowed(self.context, self.request)
+            
+    def POST(self):
+        raise GrokMethodNotAllowed(self.context, self.request)
+            
+    def PUT(self):
+        raise GrokMethodNotAllowed(self.context, self.request)
+    
+    def DELETE(self):
+        raise GrokMethodNotAllowed(self.context, self.request)
+            



More information about the Checkins mailing list