[Checkins] SVN: grok/branches/faassen-rest/src/grok/ Add initial REST support. Need a lot more tightening and testing, but this

Martijn Faassen faassen at infrae.com
Thu Sep 20 16:36:16 EDT 2007


Log message for revision 79771:
  Add initial REST support. Need a lot more tightening and testing, but this
  should be the basics.
  

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

-=-
Modified: grok/branches/faassen-rest/src/grok/__init__.py
===================================================================
--- grok/branches/faassen-rest/src/grok/__init__.py	2007-09-20 20:30:17 UTC (rev 79770)
+++ grok/branches/faassen-rest/src/grok/__init__.py	2007-09-20 20:36:15 UTC (rev 79771)
@@ -30,13 +30,15 @@
     IContainerModifiedEvent, ContainerModifiedEvent)
 
 from martian import ClassGrokker, InstanceGrokker, GlobalGrokker
-from grok.components import Model, Adapter, MultiAdapter, View, XMLRPC, JSON
+from grok.components import Model, Adapter, MultiAdapter, View
+from grok.components import XMLRPC, REST, JSON
 from grok.components import PageTemplate, PageTemplateFile, Container, Traverser
 from grok.components import Site, GlobalUtility, LocalUtility, Annotation
 from grok.components import Application, Form, AddForm, EditForm, DisplayForm
 from grok.components import Indexes
 from grok.components import Permission, Role
 from grok.components import Skin, IGrokLayer
+from grok.components import RESTProtocol, IRESTLayer
 from grok.directive import (context, name, title, template, templatedir,
                             provides, baseclass, global_utility, local_utility,
                             permissions, require, site, layer)

Modified: grok/branches/faassen-rest/src/grok/components.py
===================================================================
--- grok/branches/faassen-rest/src/grok/components.py	2007-09-20 20:30:17 UTC (rev 79770)
+++ grok/branches/faassen-rest/src/grok/components.py	2007-09-20 20:36:15 UTC (rev 79771)
@@ -191,6 +191,10 @@
     pass
 
 
+class REST(object):
+    pass
+
+
 class JSON(BrowserPage):
 
     def __call__(self):
@@ -278,6 +282,12 @@
         self.request = request
 
     def browserDefault(self, request):
+        # if we have a RESTful request, we will handle
+        # GET, POST and HEAD differently (PUT and DELETE are handled already
+        # but not on the BrowserRequest layer but the HTTPRequest layer)
+        if IRESTLayer.providedBy(request):
+            view_uri = '@@%s' % request.method
+            return self.context, (view_uri,)
         view_name = getDefaultViewName(self.context, request)
         view_uri = "@@%s" % view_name
         return self.context, (view_uri,)
@@ -492,5 +502,11 @@
 class IGrokLayer(interface.Interface):
     pass
 
+class IRESTLayer(interface.Interface):
+    pass
+
 class Skin(object):
     pass
+
+class RESTProtocol(object):
+    pass

Modified: grok/branches/faassen-rest/src/grok/configure.zcml
===================================================================
--- grok/branches/faassen-rest/src/grok/configure.zcml	2007-09-20 20:30:17 UTC (rev 79770)
+++ grok/branches/faassen-rest/src/grok/configure.zcml	2007-09-20 20:36:15 UTC (rev 79771)
@@ -50,6 +50,13 @@
       provides="zope.traversing.browser.interfaces.IAbsoluteURL"
       />
 
+  <!-- we register a ++rest++ traversal namespace -->
+  <view
+    name="rest" type="zope.publisher.interfaces.browser.IHTTPRequest"
+    provides="zope.traversing.interfaces.ITraversable" for="*"
+    factory=".rest.rest_skin"
+    />
+
   <!-- this overrides Zope 3's publication factories because they have
        the same name; we also need to change the priority because of
        the ZCML discriminator -->

Added: grok/branches/faassen-rest/src/grok/ftests/rest/__init__.py
===================================================================
--- grok/branches/faassen-rest/src/grok/ftests/rest/__init__.py	                        (rev 0)
+++ grok/branches/faassen-rest/src/grok/ftests/rest/__init__.py	2007-09-20 20:36:15 UTC (rev 79771)
@@ -0,0 +1 @@
+#

Added: grok/branches/faassen-rest/src/grok/ftests/rest/rest.py
===================================================================
--- grok/branches/faassen-rest/src/grok/ftests/rest/rest.py	                        (rev 0)
+++ grok/branches/faassen-rest/src/grok/ftests/rest/rest.py	2007-09-20 20:36:15 UTC (rev 79771)
@@ -0,0 +1,73 @@
+"""
+Let's examine Grok's REST support.
+
+Let's create a simple application with REST support::
+
+  >>> from grok.ftests.rest.rest import MyApp
+  >>> root = getRootFolder()
+  >>> root['app'] = MyApp()
+
+Issue a GET request::
+
+  >>> response = http_call('GET', 'http://localhost/++rest++test/app')
+  >>> print response.getBody()
+  GET
+
+Issue a POST request::
+
+  >>> response = http_call('POST', 'http://localhost/++rest++test/app')
+  >>> print response.getBody()
+  POST
+
+Issue a PUT request::
+
+  >>> response = http_call('PUT', 'http://localhost/++rest++test/app')
+  >>> print response.getBody()
+  PUT
+
+Issue a DELETE request::
+
+  >>> response = http_call('DELETE', 'http://localhost/++rest++test/app')
+  >>> print response.getBody()
+  DELETE
+
+405: Method not allowed
+PUT not supported. GET not supported
+
+Fall-back on base registrations.
+
+Action against sub-object in container.
+
+Test skin story.
+
+Security tests.
+"""
+
+import grok
+
+class MyApp(grok.Model, grok.Application):
+    pass
+
+class MySkinLayer(grok.IRESTLayer):
+    pass
+
+class MySkin(grok.RESTProtocol):
+    grok.name('test')
+    grok.layer(MySkinLayer)
+
+class MyRest(grok.REST):
+    grok.layer(MySkinLayer)
+    
+    def GET(self):
+        return "GET"
+
+    def POST(self):
+        return "POST"
+
+    def PUT(self):
+        return "PUT"
+
+    def DELETE(self):
+        return "DELETE"
+    
+    

Modified: grok/branches/faassen-rest/src/grok/ftests/test_grok_functional.py
===================================================================
--- grok/branches/faassen-rest/src/grok/ftests/test_grok_functional.py	2007-09-20 20:30:17 UTC (rev 79770)
+++ grok/branches/faassen-rest/src/grok/ftests/test_grok_functional.py	2007-09-20 20:36:15 UTC (rev 79771)
@@ -22,6 +22,25 @@
     (re.compile(r'httperror_seek_wrapper:', re.M), 'HTTPError:'),
     ])
 
+def http_call(method, path, data=None, **kw):
+    """Function to help make RESTful calls.
+
+    method - HTTP method to use
+    path - testbrowser style path
+    data - (body) data to submit
+    kw - any request parameters
+    """
+    
+    if path.startswith('http://localhost'):
+        path = path[len('http://localhost'):]
+    request_string = '%s %s HTTP/1.1\n' % (method, path)
+    for key, value in kw.items():
+        request_string += '%s: %s\n' % (key, value)
+    if data is not None:
+        request_string += '\r\n'
+        request_string += data
+    return HTTPCaller()(request_string, handle_errors=False)
+
 def suiteFromPackage(name):
     files = resource_listdir(__name__, name)
     suite = unittest.TestSuite()
@@ -36,6 +55,7 @@
             dottedname, setUp=setUp, tearDown=tearDown,
             checker=checker,
             extraglobs=dict(http=HTTPCaller(),
+                            http_call=http_call,
                             getRootFolder=getRootFolder,
                             sync=sync),
             optionflags=(doctest.ELLIPSIS+
@@ -49,8 +69,8 @@
 
 def test_suite():
     suite = unittest.TestSuite()
-    for name in ['view', 'staticdir', 'xmlrpc', 'traversal', 'form', 'url',
-                 'security', 'utility', 'catalog', 'admin']:
+    for name in ['view', 'staticdir', 'xmlrpc', 'rest', 'traversal',
+                 'form', 'url', 'security', 'utility', 'catalog', 'admin']:
         suite.addTest(suiteFromPackage(name))
     return suite
 

Modified: grok/branches/faassen-rest/src/grok/interfaces.py
===================================================================
--- grok/branches/faassen-rest/src/grok/interfaces.py	2007-09-20 20:30:17 UTC (rev 79770)
+++ grok/branches/faassen-rest/src/grok/interfaces.py	2007-09-20 20:36:15 UTC (rev 79771)
@@ -36,6 +36,8 @@
     LocalUtility = interface.Attribute("Base class for local utilities.")
     View = interface.Attribute("Base class for browser views.")
     XMLRPC = interface.Attribute("Base class for XML-RPC methods.")
+    JSON = interface.Attribute("Base class for JSON methods.")
+    REST = interface.Attribute("Base class for REST views.")
     Traverser = interface.Attribute("Base class for custom traversers.")
     Form = interface.Attribute("Base class for forms.")
     AddForm = interface.Attribute("Base class for add forms.")

Modified: grok/branches/faassen-rest/src/grok/meta.py
===================================================================
--- grok/branches/faassen-rest/src/grok/meta.py	2007-09-20 20:30:17 UTC (rev 79770)
+++ grok/branches/faassen-rest/src/grok/meta.py	2007-09-20 20:36:15 UTC (rev 79771)
@@ -16,6 +16,7 @@
 import os
 
 import zope.component.interface
+import zope.location
 from zope import interface, component
 from zope.publisher.interfaces.browser import (IDefaultBrowserLayer,
                                                IBrowserRequest,
@@ -47,8 +48,8 @@
 import grok
 from grok import components, formlib
 from grok.util import check_adapts, get_default_permission, make_checker
+from grok.rest import IDefaultRestLayer, IRestSkinType
 
-
 class AdapterGrokker(martian.ClassGrokker):
     component_class = grok.Adapter
 
@@ -120,7 +121,55 @@
             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
+        
+    def browserDefault(self, request):
+        return self, ()
+    
+class RESTGrokker(martian.ClassGrokker):
+    component_class = grok.REST
+    
+    def grok(self, name, factory, context, module_info, templates):
+        view_context = util.determine_class_context(factory, context)
+        # XXX We should really not make __FOO__ methods available to
+        # the outside -- need to discuss how to restrict such things.
+        methods = util.methods_from_class(factory)
+
+        default_permission = get_default_permission(factory)
+
+        # grab layer from class or module
+        view_layer = determine_class_directive('grok.layer', factory,
+                                               module_info,
+                                               default=IDefaultRestLayer)
+
+        for method in methods:
+            # Make sure that the class inherits RestPublisher, so that the
+            # views have a location
+            method_view = type(
+                factory.__name__, (factory, RestPublisher),
+                {'__call__': method}
+                )
+
+            component.provideAdapter(
+                method_view, (view_context, view_layer),
+                interface.Interface,
+                name=method.__name__)
+
+            # Protect method_view with either the permission that was
+            # set on the method, the default permission from the class
+            # level or zope.Public.
+            permission = getattr(method, '__grok_require__',
+                                 default_permission)
+            make_checker(factory, method_view, permission)
+        return True
+    
+
 class ViewGrokker(martian.ClassGrokker):
     component_class = grok.View
 
@@ -650,14 +699,28 @@
     component_class = grok.Skin
 
     def grok(self, name, factory, context, module_info, templates):
-
-        layer = determine_class_directive('grok.layer', factory, module_info, default=IBrowserRequest)
-        name = grok.util.class_annotation(factory, 'grok.name', factory.__name__.lower())
-        zope.component.interface.provideInterface(name, layer, IBrowserSkinType)
+        layer = determine_class_directive('grok.layer', factory,
+                                          module_info, default=IBrowserRequest)
+        name = grok.util.class_annotation(factory, 'grok.name',
+                                          factory.__name__.lower())
+        zope.component.interface.provideInterface(name, layer,
+                                                  IBrowserSkinType)
         return True
 
+class RESTProtocolGrokker(martian.ClassGrokker):
+    component_class = grok.RESTProtocol
 
-def determine_class_directive(directive_name, factory, module_info, default=None):
+    def grok(self, name, factory, context, module_info, templates):
+        layer = determine_class_directive('grok.layer', factory,
+                                          module_info, default=IBrowserRequest)
+        name = grok.util.class_annotation(factory, 'grok.name',
+                                          factory.__name__.lower())
+        zope.component.interface.provideInterface(name, layer,
+                                                  IRestSkinType)
+        return True
+    
+def determine_class_directive(directive_name, factory, module_info,
+                              default=None):
     directive = util.class_annotation(factory, directive_name, None)
     if directive is None:
         directive = module_info.getAnnotation(directive_name, None)

Added: grok/branches/faassen-rest/src/grok/rest.py
===================================================================
--- grok/branches/faassen-rest/src/grok/rest.py	                        (rev 0)
+++ grok/branches/faassen-rest/src/grok/rest.py	2007-09-20 20:36:15 UTC (rev 79771)
@@ -0,0 +1,20 @@
+from zope.traversing.namespace import skin
+from zope.interface.interfaces import IInterface
+from zope.publisher.interfaces.browser import IBrowserRequest
+
+class IDefaultRestLayer(IBrowserRequest):
+    pass
+
+class IRestSkinType(IInterface):
+    """Skin for REST requests.
+    """
+
+class rest_skin(skin):
+    skin_type = IRestSkinType
+
+    #def traverse(self, name, ignored):
+    #    import pdb; pdb.set_trace()
+    #    return super(rest_skin, self).traverse(name, ignored)
+    
+
+        



More information about the Checkins mailing list