Welcome to restfulgrok’s documentation!¶
restfulgrok
provides a very simple RESTful view mixin for
five.grok.View
. It is not meant to be a full-featured REST library, only a
quick solution for simple REST APIs using five.grok.View
.
Features:
- Content negotiation:
- Assumes same input and out mimetype (simplifies the implementation)
- Can be specified in a GET parameter (E.g.:
?mimetype=application/yaml
) - Can be specified use HTTP ACCEPT header.
- Supports:
- JSON
- YAML
- HTML (read only)
- ... Add custom content-type/mimetype
- HTTP response helpers for common response types.
Getting started¶
Create a class that inherit from GrokRestViewMixin:
from restfulgrok.fancyhtmlview import GrokRestViewWithFancyHtmlMixin
class ExampleRestViewMixin(GrokRestViewWithFancyHtmlMixin):
def handle_get(self):
# Return something that can be encoded by JSON and YAML
return {'hello': 'world'}
def handle_post(self):
try:
# Decode request body as a dict (JSON or YAML)
request_data = self.get_requestdata_dict()
except ValueError, e:
# Did not get a dict
return self.response_400_bad_request({'error': str(e)})
else:
# save to database or something....
# --- not included in example ---
# Respond with 201 status and the request_data
# NOTE: If you just return normally, 200 response status is used
return self.response_201_created(request_data)
def handle_put(self):
try:
# Decode request body as a dict (JSON or YAML)
request_data = self.get_requestdata_dict()
except ValueError, e:
# Did not get a dict
return self.response_400_bad_request({'error': str(e)})
else:
data = request_data.copy() # pretend we got this from a database
data['last-modified'] = 'now' # Update some data
# would save here if this was real...
# NOTE: If you just return normally, 200 response status is used
return {'Updated': 'yes',
'result': data}
And a testcase:
from unittest import TestCase
from restfulgrok.mock import MockResponse
from restfulgrok.mock import MockRequest
from restfulgrok.mock import MockContext
from example import ExampleRestViewMixin
class MockExampleRestMixin(ExampleRestViewMixin):
def __init__(self, method='GET', body=''):
self.response = MockResponse()
self.request = MockRequest(method, body=body)
self.context = MockContext()
class TestExampleRestMixin(TestCase):
def test_get(self):
result = MockExampleRestMixin('GET').handle()
self.assertEquals(result, {'hello': 'world'})
def test_post(self):
import json
data = {'a': 'test'}
result = MockExampleRestMixin('POST', body=json.dumps(data)).handle()
self.assertEquals(result, {'a': 'test'})
def test_put(self):
import json
data = {'a': 'test'}
result = MockExampleRestMixin('PUT', body=json.dumps(data)).handle()
self.assertEquals(result,
{'Updated': 'yes',
'result': {'a': 'test',
'last-modified': 'now'}})
if __name__ == '__main__':
import unittest
unittest.main()
And finally use the mixin to create a grok.View
:
from five import grok
class ExampleRestView(ExampleRestViewMixin, grok.View):
grok.context(IMyInterface)
grok.name('rest')
Extending and styling the HtmlContentType view¶
The html provided with restfulgrok.fancyhtmlview.HtmlContentType
does
not have a stylesheet, however it is designed to work with Bootstrap. Just override the template,
and override the head_extra
block. For example:
Create your own html content type (example assumes your app is
my.package
):from jinja2 import Environment, PrefixLoader, PackageLoader class MyHtmlContentType(HtmlContentType): template_name = 'fancyhtmlview.jinja.html' template_environment = Environment(loader = PrefixLoader({ 'restfulgrok': PackageLoader('restfulgrok'), 'mypackage': PackageLoader('my.package') }))
Create
my/package/templates
andmy/package/staticfiles
.Add static directory to
configure.zcml
:<browser:resourceDirectory name="my.package" directory="staticfiles" />
Create your own template,
my/package/templates/view.jinja.html
, extending the one fromrestfulgrok
:{% extends "restfulgrok/fancyhtmlview.jinja.html" %} {% block head_extra %} <link rel="stylesheet/less" href="++resource++my.package/bootstrap/less/bootstrap.less"> <script src="++resource++my.package/less-1.3.0.min.js"></script> {% endblock %}