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 %}
Documentation¶
restfulgrok API¶
restfulgrok.view¶
-
exception
restfulgrok.view.
CouldNotDetermineContentType
(querystring_error, acceptheader_error, acceptable_mimetypes)[source]¶ Bases:
exceptions.Exception
Raised when
GrokRestViewMixin.get_content_type()
fails to detect a supported content-type.
-
class
restfulgrok.view.
GrokRestViewMixin
[source]¶ Bases:
object
Mix-in class for
five.grok.View
.-
add_attachment_header
()[source]¶ Adds Content-Disposition header for filedownload if “downloadfile=yes” is in the querystring (request.get).
Called by
render()
to authorize the user before callinghandle()
.The permissions required for each method is defined in
permissions
.Raises: AccessControl.Unauthorized – If the current user do not have permission to perform the requested method.
-
content_types
= <restfulgrok.contenttype.ContentTypesRegistry object>¶ A
ContentTypesRegistry
object containing all content-types supported by the API.
-
create_response
(status, statusmsg, body)[source]¶ Respond with the given
status
andstatusmessage
, withbody
as response body.
-
decode_input_data
(rawdata)[source]¶ Decode the given
rawdata
.Raises: restfulgrok.contenttype.ContentTypeLoadError – If rawdata
can not be decoded.
-
encode_output_data
(pydata)[source]¶ Encode the given python datastructure.
Raises: restfulgrok.contenttype.ContentTypeDumpError – If pydata
can not be encoded.
-
get_requestdata
()[source]¶ Decode the body of the request using
decode_input_data()
, and return the decoded data.
-
get_requestdata_dict
()[source]¶ Just like
get_requestdata()
, however, theValueError
is raised if the decoded data is not adict
.
-
handle
()[source]¶ Takes care of all the logic for
render()
, except that it does notencode_output_data()
the response data, and it does not do authorization. This makes this method very suitable for use in tests or custom render() implementations.
-
handle_delete
()[source]¶ Override in subclasses. Defaults to
response_405_method_not_allowed()
.
-
handle_get
()[source]¶ Override in subclasses. Defaults to
response_405_method_not_allowed()
.
-
handle_head
()[source]¶ Override in subclasses. Defaults to
response_405_method_not_allowed()
.
-
handle_options
()[source]¶ Override in subclasses. Defaults to
response_405_method_not_allowed()
.
-
handle_post
()[source]¶ Override in subclasses. Defaults to
response_405_method_not_allowed()
.
-
handle_put
()[source]¶ Override in subclasses. Defaults to
response_405_method_not_allowed()
.
-
permissions
= {'put': 'Modify portal content', 'default': 'Modify portal content', 'post': 'Add portal content', 'get': 'View'}¶ Map of request method to permission. You should have one (lowercase) key for each request method in
supported_methods
, or a “default” key defining a default permission.
-
render
()[source]¶ Called to render the view. Uses
handle()
to handle all the logic andencode_output_data()
to encode the response fromhandle()
.
-
response_400_bad_request
(body)[source]¶ Respond with 400 Bad Request, and the
body
parameter as response body.
Respond with 401 Unauthorized, and
{'error': 'Unauthorized'}
as body.
-
set_contenttype_header
(mimetype=None)[source]¶ Set the content type header. Called by
handle()
, and may be overridden.
-
supported_methods
= ['get', 'post', 'put', 'delete', 'options', 'head']¶ List of supported HTTP request methods in lowercase. Defaults to
['get', 'post', 'put', 'delete', 'options', 'head']
. You do not have to override this to disallow any of those request methods since their defaulthandle_<method>()
implementations responds with 405 Method Not Allowed. However, if you implement other methods, such as TRACE, you need to add them to the list.
-
restfulgrok.fancyhtmlview¶
-
class
restfulgrok.fancyhtmlview.
GrokRestViewWithFancyHtmlMixin
[source]¶ Bases:
restfulgrok.view.GrokRestViewMixin
Adds
HtmlContentType
tocontent_types
.
-
class
restfulgrok.fancyhtmlview.
HtmlContentType
[source]¶ Bases:
restfulgrok.contenttype.ContentType
XHTML content type. Provides a dumps-method that uses a jinja2-template to generate a bootrap-styled HTML-document which is suitable as a default view for a REST API.
-
error_template_name
= 'restfulgrok/errorview.jinja.html'¶ jinja2 template name for the
errorview()
.
-
classmethod
get_previewdata
(pydata)[source]¶ Get the data that should be added to the data preview box.
Returns: A string containing the data.
-
classmethod
get_template_data
(pydata, view)[source]¶ Get the template data.
Returns: Template data. Return type: dict
-
html_brandingtitle
= 'REST API'¶ Variable forwarded to the template as
brandingtitle
.
-
html_heading
= 'REST API'¶ Variable forwarded to the template as
heading
.
-
html_title
= 'REST API'¶ Variable forwarded to the template as
title
.
-
template_environment
= <jinja2.environment.Environment object>¶ The
jinja2.Environment
-
template_name
= 'restfulgrok/fancyhtmlview.jinja.html'¶ jinja2 template name for the normal html view (not for errors)
-
restfulgrok.contenttype¶
-
class
restfulgrok.contenttype.
ContentType
[source]¶ Bases:
object
Superclass for all content-types for the
ContentTypesRegistry
.-
description
= ''¶ A short description for users of the content-type.
-
classmethod
dumps
(pydata, view)[source]¶ Dump
pydata
to a string and return the string.Parameters: - pydata – The python data to encode.
- view – A
GrokRestViewMixin
instance.
-
extension
= None¶ Extension for files of this type. Must be set in subclasses.
-
classmethod
loads
(rawdata, view)[source]¶ Load the
rawdata
bytestring and return it as a decoded pyton object.Parameters: - rawdata – The bytestring to decode.
- view – A
GrokRestViewMixin
instance.
-
mimetype
= None¶ The mimetype of this content type. Must be set in subclasses.
-
-
exception
restfulgrok.contenttype.
ContentTypeDumpError
[source]¶ Bases:
restfulgrok.contenttype.ContentTypeError
Raised when
ContentType.dumps()
fails.
-
exception
restfulgrok.contenttype.
ContentTypeError
[source]¶ Bases:
exceptions.Exception
Superclass for
ContentType
errors.
-
exception
restfulgrok.contenttype.
ContentTypeLoadError
[source]¶ Bases:
restfulgrok.contenttype.ContentTypeError
Raised when
ContentType.loads()
fails.
-
class
restfulgrok.contenttype.
ContentTypesRegistry
(*content_types)[source]¶ Bases:
object
Registry of
ContentType
objects.
-
class
restfulgrok.contenttype.
JsonContentType
[source]¶ Bases:
restfulgrok.contenttype.ContentType
JSON content type. Implements both loads and dumps.
-
class
restfulgrok.contenttype.
YamlContentType
[source]¶ Bases:
restfulgrok.contenttype.ContentType
YAML content type. Implements both loads and dumps.
restfulgrok.mock¶
Mock classes to simplify testing. See the sourcecode (or the source links below).
-
class
restfulgrok.mock.
MockRequest
(method='GET', body='', getdata={}, headers={'Accept': 'application/json'})[source]¶ Bases:
object
-
class
restfulgrok.mock.
MockRestView
(request=None, response=<restfulgrok.mock.MockResponse object>, context=None)[source]¶
-
class
restfulgrok.mock.
MockRestViewWithFancyHtml
(request=None, response=<restfulgrok.mock.MockResponse object>, context=None)[source]¶ Bases:
restfulgrok.fancyhtmlview.GrokRestViewWithFancyHtmlMixin
Add custom content-type/mimetype¶
Content-types are defined as subclasses of
restfulgrok.contenttype.ContentType
. Take look at its docs, and the
sourcecode of restfulgrok.contenttype.JsonContentType
:
class JsonContentType(ContentType):
"""
JSON content type. Implements both loads and dumps.
"""
mimetype = 'application/json'
extension = 'json'
description = json_description
@classmethod
def dumps(cls, pydata, view=None):
try:
return json.dumps(pydata, indent=2)
except TypeError, e:
raise ContentTypeDumpError(str(e))
except ValueError, e:
raise ContentTypeDumpError(str(e))
@classmethod
def loads(cls, rawdata, view=None):
try:
return json.loads(rawdata)
except TypeError, e:
raise ContentTypeLoadError(str(e))
except ValueError, e:
raise ContentTypeDumpError(str(e))