Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e188b5a
commit b0920f0
Showing
8 changed files
with
530 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# License | ||
|
||
This software is made available under the terms of a [BSD 2-Clause license][bsd-2-clause]. | ||
|
||
Copyright © 2014, Tom Christie | ||
All rights reserved. | ||
|
||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | ||
|
||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | ||
|
||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | ||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
|
||
[bsd-2-clause]: http://opensource.org/licenses/BSD-2-Clause |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Release Notes | ||
|
||
This project is currently in alpha. It is funcational and well tested but you are advised to pay close attention to the release notes when upgrading to future versions. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
# Parsers | ||
|
||
Parsers are responsible for taking the content of the request body as a bytestream, and transforming it into a native Python data representation. | ||
|
||
Flask API includes a few built-in parser classes and also provides support for defining your own custom parsers. | ||
|
||
## How the parser is determined | ||
|
||
The set of valid parsers for a view is always defined as a list of classes. When any of the properties `request.data`, `request.form` or `request.files` are accessed, Flask API will examine the `Content-Type` header on the incoming request, and determine which parser to use to handle the request content. | ||
|
||
--- | ||
|
||
**Note**: When developing client applications always remember to make sure you're setting the `Content-Type` header when sending data in an HTTP request. | ||
|
||
If you don't set the content type, most clients will default to using `'application/x-www-form-urlencoded'`, which may not be what you wanted. | ||
|
||
As an example, if you are sending `json` encoded data using jQuery with the [.ajax() method][jquery-ajax], you should make sure to include the `contentType: 'application/json'` setting. | ||
|
||
--- | ||
|
||
## Setting the parsers | ||
|
||
The default set of parsers may be set globally, using the `DEFAULT_PARSERS` configuration key. The default configuration will deal with parsing either JSON or form encoded requests. | ||
|
||
app.config['DEFAULT_PARSERS'] = [ | ||
'flaskapi.parsers.JSONParser', | ||
'flaskapi.parsers.URLEncodedParser', | ||
'flaskapi.parsers.MultiPartParser' | ||
] | ||
|
||
You can also set the parsers used for an individual view, using the `set_parsers` decorator. | ||
|
||
from flaskapi.decorators import set_parsers | ||
|
||
... | ||
|
||
@app.route('/example_view/') | ||
@set_parsers(JSONParser, MyCustomXMLParser) | ||
def example(): | ||
return { | ||
'example': 'Setting renderers on a per-view basis', | ||
'request data': request.data | ||
} | ||
|
||
--- | ||
|
||
# API Reference | ||
|
||
## JSONParser | ||
|
||
Parses `JSON` request content and populates `request.data`. | ||
|
||
**media_type**: `application/json` | ||
|
||
## FormParser | ||
|
||
Parses HTML form content. `request.data` will be populated with a `MultiDict` of data. | ||
|
||
You will typically want to use both `FormParser` and `MultiPartParser` together in order to fully support HTML form data. | ||
|
||
**media_type**: `application/x-www-form-urlencoded` | ||
|
||
## MultiPartParser | ||
|
||
Parses multipart HTML form content, which supports file uploads. Both `request.data` and `request.files` will be populated with a `MultiDict`. | ||
|
||
You will typically want to use both `FormParser` and `MultiPartParser` together in order to fully support HTML form data. | ||
|
||
**media_type**: `multipart/form-data` | ||
|
||
--- | ||
|
||
# Custom parsers | ||
|
||
To implement a custom parser, you should override `BaseParser`, set the `.media_type` property, and implement the `.parse(self, stream, media_type, **options)` method. | ||
|
||
The method should return the data that will be used to populate the `request.data` property. | ||
|
||
The arguments passed to `.parse()` are: | ||
|
||
**`stream`** | ||
|
||
A bytestream representing the body of the request. | ||
|
||
**`media_type`** | ||
|
||
An instance of MediaType indicating media type of the incoming request. | ||
|
||
Depending on the request's `Content-Type:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters. For example `"text/plain; charset=utf-8"`. | ||
|
||
**`**options`** | ||
|
||
Any additional contextual arguments that may be required in order to parse the request. | ||
By default this includes a single keyword argument: | ||
|
||
* `content_length` - An integer representing the length of the request body in bytes. | ||
|
||
## Example | ||
|
||
The following is an example plaintext parser that will populate the `request.data` property with a string representing the body of the request. | ||
|
||
class PlainTextParser(BaseParser): | ||
""" | ||
Plain text parser. | ||
""" | ||
media_type = 'text/plain' | ||
|
||
def parse(self, stream, media_type, **options): | ||
""" | ||
Simply return a string representing the body of the request. | ||
""" | ||
return stream.read().decode('utf8') | ||
|
||
[jquery-ajax]: http://api.jquery.com/jQuery.ajax/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
# Renderers | ||
|
||
Renderers are responsible for taking the response value from your view and transforming it into a string or bytestring that will be used as the response body. | ||
|
||
Flask API includes a few built-in renderer classes and also provides support for defining your own custom renderers. | ||
|
||
## Determining the renderer | ||
|
||
The set of valid renderers for a view is always defined as a list of classes. When a view is entered Flask API will perform content negotiation on the incoming request, and determine the most appropriate renderer to satisfy the request. | ||
|
||
The basic process of content negotiation involves examining the request's `Accept` header, to determine which media types it expects in the response. | ||
|
||
## Setting the renderers | ||
|
||
The default set of renderers may be set globally, using the `DEFAULT_RENDERERS` configuration key. The default configuration will render to JSON as standard, or will render the browsable API if the client requests HTML. | ||
|
||
app.config['DEFAULT_RENDERERS'] = [ | ||
'flaskapi.renderers.JSONRenderer', | ||
'flaskapi.renderers.BrowsableAPIRenderer', | ||
] | ||
|
||
You can also set the renderers used for an individual view, using the `set_renderers` decorator. | ||
|
||
from flaskapi.decorators import set_renderers | ||
|
||
... | ||
|
||
@app.route('/example_view/') | ||
@set_renderers(JSONRenderer, MyCustomXMLRenderer) | ||
def example(): | ||
return {'example': 'Setting renderers on a per-view basis'} | ||
|
||
## Ordering of renderers | ||
|
||
It's important when specifying the renderer classes for your API to think about what priority you want to assign to each media type. If a client underspecifies the representations it can accept, such as sending an `Accept: */*` header, or not including an `Accept` header at all, then Flask API will select the first renderer in the list to use for the response. | ||
|
||
--- | ||
|
||
# API Reference | ||
|
||
## JSONRenderer | ||
|
||
Renders the request data into `JSON`. | ||
|
||
The client may additionally include an `'indent'` media type parameter, in which case the returned `JSON` will be indented. For example `Accept: application/json; indent=4`. | ||
|
||
{ | ||
"example": "indented JSON" | ||
} | ||
|
||
**`media_type`**: `application/json` | ||
|
||
**`charset`**: `None` | ||
|
||
## HTMLRenderer | ||
|
||
A simple renderer that simply returns pre-rendered HTML. Unlike other renderers, the data passed to the response object should be a string representing the content to be returned. | ||
|
||
An example of a view that uses `HTMLRenderer`: | ||
|
||
@app.route('/hello-world/') | ||
@set_renderers(HTMLRenderer) | ||
def hello_world(): | ||
return '<html><body><h1>Hello, world</h1></body></html>' | ||
|
||
You can use `HTMLRenderer` either to return regular HTML pages using Flask API, or to return both HTML and API responses from a single endpoint. | ||
|
||
**`media_type`**: `text/html` | ||
|
||
**`charset`**: `utf-8` | ||
|
||
## BrowsableAPIRenderer | ||
|
||
Renders data into HTML for the Browsable API. This renderer will determine which other renderer would have been given highest priority, and use that to display an API style response within the HTML page. | ||
|
||
**`media_type`**: `text/html` | ||
|
||
**`charset`**: `utf-8` | ||
|
||
--- | ||
|
||
# Custom renderers | ||
|
||
To implement a custom renderer, you should override `BaseRenderer`, set the `.media_type` property, and implement the `.render(self, data, media_type, **options)` method. | ||
|
||
The method should return a string or bytestring, which will be used as the body of the HTTP response. | ||
|
||
The arguments passed to the `.render()` method are: | ||
|
||
**`data`** | ||
|
||
The request data, returned by the view. | ||
|
||
**`media_type`** | ||
|
||
Optional. If provided, this is the accepted media type, as determined by the content negotiation stage. | ||
|
||
Depending on the client's `Accept:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters. For example `"application/json; api-version="0.1"`. | ||
|
||
**`**options`** | ||
|
||
Any additional contextual arguments that may be required in order to render the response. | ||
By default this includes: | ||
|
||
* `status` - A string representing the response status. | ||
* `status_code` - An integer representing the response status code. | ||
* `headers` - A dictionary containing the response headers. | ||
|
||
## Example | ||
|
||
The following is an custom renderer that returns YAML. | ||
|
||
from flaskapi import renderers | ||
import yaml | ||
|
||
|
||
class YAMLRenderer(renderers.BaseRenderer): | ||
media_type = 'application/yaml' | ||
def render(self, data, media_type, **options): | ||
return yaml.dump(data, encoding=self.charset) | ||
|
||
<!-- | ||
TODO: This needs testing, and probably some more work. | ||
## Setting the character set | ||
By default renderer classes are assumed to be using the `UTF-8` encoding. To use a different encoding, set the `charset` attribute on the renderer. | ||
class PlainTextRenderer(renderers.BaseRenderer): | ||
media_type = 'text/plain' | ||
charset = 'iso-8859-1' | ||
def render(self, data, media_type, **options): | ||
return data.encode(self.charset) | ||
Note that if a renderer class returns a unicode string, then the response content will be coerced into a bytestring, with the `charset` attribute set on the renderer used to determine the encoding. | ||
If the renderer returns a bytestring representing raw binary content, you should set a charset value of `None`, which will ensure the `Content-Type` header of the response will not have a `charset` value set. | ||
--> | ||
|
||
[browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers | ||
[rfc4627]: http://www.ietf.org/rfc/rfc4627.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# Status Codes | ||
|
||
Flask API includes a set of named constants that you can use to make more code more obvious and readable. | ||
|
||
from flaskapi import status | ||
|
||
... | ||
|
||
@app.route('/empty-view/') | ||
def empty_view(self): | ||
content = {'please move along': 'nothing to see here'} | ||
return content, status.HTTP_404_NOT_FOUND | ||
|
||
The full set of HTTP status codes included in the `status` module is listed below. | ||
|
||
The module also includes a set of helper functions for testing if a status code is in a given range. | ||
|
||
from flaskapi import status | ||
import unittest | ||
|
||
... | ||
|
||
class ExampleTestCase(unittest.TestCase): | ||
def test_success(self): | ||
with app.test_client() as client: | ||
response = client.get('/') | ||
self.assertTrue(status.is_success(response.status_code)) | ||
|
||
For more information on proper usage of HTTP status codes see [RFC 2616][rfc2616] | ||
and [RFC 6585][rfc6585]. | ||
|
||
## Informational - 1xx | ||
|
||
This class of status code indicates a provisional response. There are no 1xx status codes used in REST framework by default. | ||
|
||
HTTP_100_CONTINUE | ||
HTTP_101_SWITCHING_PROTOCOLS | ||
|
||
## Successful - 2xx | ||
|
||
This class of status code indicates that the client's request was successfully received, understood, and accepted. | ||
|
||
HTTP_200_OK | ||
HTTP_201_CREATED | ||
HTTP_202_ACCEPTED | ||
HTTP_203_NON_AUTHORITATIVE_INFORMATION | ||
HTTP_204_NO_CONTENT | ||
HTTP_205_RESET_CONTENT | ||
HTTP_206_PARTIAL_CONTENT | ||
|
||
## Redirection - 3xx | ||
|
||
This class of status code indicates that further action needs to be taken by the user agent in order to fulfill the request. | ||
|
||
HTTP_300_MULTIPLE_CHOICES | ||
HTTP_301_MOVED_PERMANENTLY | ||
HTTP_302_FOUND | ||
HTTP_303_SEE_OTHER | ||
HTTP_304_NOT_MODIFIED | ||
HTTP_305_USE_PROXY | ||
HTTP_306_RESERVED | ||
HTTP_307_TEMPORARY_REDIRECT | ||
|
||
## Client Error - 4xx | ||
|
||
The 4xx class of status code is intended for cases in which the client seems to have erred. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition. | ||
|
||
HTTP_400_BAD_REQUEST | ||
HTTP_401_UNAUTHORIZED | ||
HTTP_402_PAYMENT_REQUIRED | ||
HTTP_403_FORBIDDEN | ||
HTTP_404_NOT_FOUND | ||
HTTP_405_METHOD_NOT_ALLOWED | ||
HTTP_406_NOT_ACCEPTABLE | ||
HTTP_407_PROXY_AUTHENTICATION_REQUIRED | ||
HTTP_408_REQUEST_TIMEOUT | ||
HTTP_409_CONFLICT | ||
HTTP_410_GONE | ||
HTTP_411_LENGTH_REQUIRED | ||
HTTP_412_PRECONDITION_FAILED | ||
HTTP_413_REQUEST_ENTITY_TOO_LARGE | ||
HTTP_414_REQUEST_URI_TOO_LONG | ||
HTTP_415_UNSUPPORTED_MEDIA_TYPE | ||
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE | ||
HTTP_417_EXPECTATION_FAILED | ||
HTTP_428_PRECONDITION_REQUIRED | ||
HTTP_429_TOO_MANY_REQUESTS | ||
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE | ||
|
||
## Server Error - 5xx | ||
|
||
Response status codes beginning with the digit "5" indicate cases in which the server is aware that it has erred or is incapable of performing the request. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition. | ||
|
||
HTTP_500_INTERNAL_SERVER_ERROR | ||
HTTP_501_NOT_IMPLEMENTED | ||
HTTP_502_BAD_GATEWAY | ||
HTTP_503_SERVICE_UNAVAILABLE | ||
HTTP_504_GATEWAY_TIMEOUT | ||
HTTP_505_HTTP_VERSION_NOT_SUPPORTED | ||
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED | ||
|
||
## Helper functions | ||
|
||
The following helper functions are available for identifying the category of the response code. | ||
|
||
is_informational() # 1xx | ||
is_success() # 2xx | ||
is_redirect() # 3xx | ||
is_client_error() # 4xx | ||
is_server_error() # 5xx | ||
|
||
[rfc2616]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html | ||
[rfc6585]: http://tools.ietf.org/html/rfc6585 |
Oops, something went wrong.