This is a first pass at a non-breaking change that'll allow for a customizable media handling system. This approach combines many of the suggestions brought up by the community in #145. One the thing that is left out of this PR is handling full content negotiation (i.e. connecting the request's accept header to the response's content-type). Unfortunately, this is a harder problem to solve in a backwards compatible fashion that doesn't affect performance. However, especially as we move towards v2, I think that would be a great opportunity to revisit full negotiation. In the meantime, there are several easy workarounds for people needing this functionality. Closes #145
3.7 KiB
Media
Falcon allows for easy and customizable internet media type handling.
By default Falcon only enables a single JSON handler. However,
additional handlers can be configured through the falcon.RequestOptions and
falcon.ResponseOptions
objects specified on your falcon.API.
Note
To avoid unnecessary overhead, Falcon will only process request media the first time the media property is referenced. Once it has been referenced, it'll use the cached result for subsequent interactions.
Usage
Zero configuration is needed if you're creating a JSON API. Just
access or set the media attribute as appropriate and let
Falcon do the heavy lifting for you.
import falcon
class EchoResource(object):
def on_post(self, req, resp):
message = req.media.get('message')
resp.media = {'message': message}
resp.status = falcon.HTTP_200Warning
Once media is called on a request, it'll consume the request's stream.
Validating Media
Falcon currently only provides a JSON Schema media validator; however, JSON Schema is very versatile and can be used to validate any deserialized media type that JSON also supports (i.e. dicts, lists, etc).
falcon.media.validators.jsonschema.validate
Content-Type Negotiation
Falcon currently only supports partial negotiation out of the box. By
default, when the media attribute is used it attempts to
de/serialize based on the Content-Type header value. The
missing link that Falcon doesn't provide is the connection between the
falcon.Request
Accept header provided by a user and the falcon.Response
Content-Type header.
If you do need full negotiation, it is very easy to bridge the gap using middleware. Here is an example of how this can be done:
class NegotiationMiddleware(object):
def process_request(self, req, resp):
resp.content_type = req.acceptReplacing the Default Handlers
When creating your API object you can either add or completely
replace all of the handlers. For example, lets say you want to write an
API that sends and receives MessagePack. We can easily do this by
telling our Falcon API that we want a default media-type of
application/msgpack and then create a new Handlers object specifying
the desired media type and a handler that can process that data.
import falcon
from falcon import media
handlers = media.Handlers({
'application/msgpack': media.MessagePackHandler(),
})
api = falcon.API(media_type='application/msgpack')
api.req_options.media_handlers = handlers
api.resp_options.media_handlers = handlersAlternatively, if you would like to add an additional handler such as MessagePack, this can be easily done in the following manner:
import falcon
from falcon import media
extra_handlers = {
'application/msgpack': media.MessagePackHandler(),
}
api = falcon.API()
api.req_options.media_handlers.update(extra_handlers)
api.resp_options.media_handlers.update(extra_handlers)Supported Handler Types
falcon.media.JSONHandler
falcon.media.MessagePackHandler
Custom Handler Type
If Falcon doesn't have an internet media type handler that supports your use case, you can easily implement your own using the abstract base class provided by Falcon:
falcon.media.BaseHandler
Handlers
falcon.media.Handlers