[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