Media#
Falcon allows for easy and customizable internet media type handling. By
default Falcon only enables handlers for JSON and HTML (URL-encoded and
multipart) forms. However, additional handlers can be configured through the
falcon.RequestOptions and falcon.ResponseOptions objects
specified on your falcon.App.
Note
WebSocket media is handled differently from regular HTTP requests. For information regarding WebSocket media handlers, please see: Media Handlers in the WebSocket section.
Usage#
Zero configuration is needed if you’re creating a JSON API. Simply use
get_media() and media (WSGI)
, or get_media() and
media (ASGI) to let Falcon
do the heavy lifting for you.
import falcon
class EchoResource:
def on_post(self, req, resp):
# Deserialize the request body based on the Content-Type
# header in the request, or the default media type
# when the Content-Type header is generic ('*/*') or
# missing.
obj = req.get_media()
message = obj.get('message')
# The framework will look for a media handler that matches
# the response's Content-Type header, or fall back to the
# default media type (typically JSON) when the app does
# not explicitly set the Content-Type header.
resp.media = {'message': message}
resp.status = falcon.HTTP_200
import falcon
class EchoResource:
async def on_post(self, req, resp):
# Deserialize the request body. Note that the ASGI version
# of this method must be awaited.
obj = await req.get_media()
message = obj.get('message')
# The framework will look for a media handler that matches
# the response's Content-Type header, or fall back to the
# default media type (typically JSON) when the app does
# not explicitly set the Content-Type header.
resp.media = {'message': message}
resp.status = falcon.HTTP_200
Warning
Once falcon.Request.get_media() or
falcon.asgi.Request.get_media() is called on a request, it will
consume the request’s body stream. To avoid unnecessary overhead, Falcon
will only process request media the first time it is referenced. Subsequent
interactions will use a cached object.
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(req_schema: Dict[str, Any] | None = None, resp_schema: Dict[str, Any] | None = None) Callable[[Callable[[...], Any]], Callable[[...], Any]][source]#
Validate
req.mediausing JSON Schema.This decorator provides standard JSON Schema validation via the
jsonschemapackage available from PyPI. Semantic validation via the format keyword is enabled for the default checkers implemented byjsonschema.FormatChecker.In the case of failed request media validation, an instance of
MediaValidationErroris raised by the decorator. By default, this error is rendered as a 400 (HTTPBadRequest) response with thetitleanddescriptionattributes explaining the validation failure, but this behavior can be modified by adding a custom errorhandlerforMediaValidationError.Note
The
jsonschemapackage must be installed separately in order to use this decorator, as Falcon does not install it by default.See json-schema.org for more information on defining a compatible dictionary.
- Keyword Arguments:
req_schema (dict) – A dictionary that follows the JSON Schema specification. The request will be validated against this schema.
resp_schema (dict) – A dictionary that follows the JSON Schema specification. The response will be validated against this schema.
Example
from falcon.media.validators import jsonschema # -- snip -- @jsonschema.validate(my_post_schema) def on_post(self, req, resp): # -- snip --
from falcon.media.validators import jsonschema # -- snip -- @jsonschema.validate(my_post_schema) async def on_post(self, req, resp): # -- snip --
If JSON Schema does not meet your needs, a custom validator may be implemented in a similar manner to the one above.
Content-Type Negotiation#
Falcon currently only supports partial negotiation out of the box. By default,
when the get_media() method or the media attribute is used, the
framework attempts to (de)serialize based on the Content-Type header value.
The missing link that Falcon doesn’t provide is the connection between the
Accept header provided by a user and the Content-Type header set on the
response.
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:
from falcon import Request, Response
class NegotiationMiddleware:
def process_request(self, req: Request, resp: Response) -> None:
resp.content_type = req.accept
from falcon.asgi import Request, Response
class NegotiationMiddleware:
async def process_request(self, req: Request, resp: Response) -> None:
resp.content_type = req.accept
Exception Handling#
Version 3 of Falcon updated how the handling of exceptions raised by handlers behaves:
Falcon lets the media handler try to deserialized an empty body. For the media types that don’t allow empty bodies as a valid value, such as
JSON, an instance offalcon.MediaNotFoundErrorshould be raised. By default, this error will be rendered as a400 Bad Requestresponse to the client. This exception may be suppressed by passing a value to thedefault_when_emptyargument when callingRequest.get_media(). In this case, this value will be returned by the call.If a handler encounters an error while parsing a non-empty body, an instance of
falcon.MediaMalformedErrorshould be raised. The original exception, if any, is stored in the__cause__attribute of the raised instance. By default, this error will be rendered as a400 Bad Requestresponse to the client.
If any exception was raised by the handler while parsing the body, all subsequent invocations
of Request.get_media() or Request.media will result in a re-raise of the same
exception, unless the exception was a falcon.MediaNotFoundError and a default value
is passed to the default_when_empty attribute of the current invocation.
External handlers should update their logic to align to the internal Falcon handlers.
Replacing the Default Handlers#
By default, the framework installs falcon.media.JSONHandler,
falcon.media.URLEncodedFormHandler, and
falcon.media.MultipartFormHandler for the application/json,
application/x-www-form-urlencoded, and multipart/form-data media types,
respectively.
When creating your App object you can either add or completely replace all of
the handlers. For example, let’s 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 creating a new Handlers object to map that media
type to an appropriate handler.
The following example demonstrates how to replace the default handlers. Because
Falcon provides a MessagePackHandler that is not enabled
by default, we use it in our examples below. However, you can always substitute
a custom media handler as needed.
import falcon
from falcon import media
handlers = media.Handlers({
falcon.MEDIA_MSGPACK: media.MessagePackHandler(),
})
app = falcon.App(media_type=falcon.MEDIA_MSGPACK)
app.req_options.media_handlers = handlers
app.resp_options.media_handlers = handlers
Alternatively, you can simply update the existing
Handlers object to retain the default handlers:
import falcon
from falcon import media
extra_handlers = {
falcon.MEDIA_MSGPACK: media.MessagePackHandler(),
}
app = falcon.App()
app.req_options.media_handlers.update(extra_handlers)
app.resp_options.media_handlers.update(extra_handlers)
The falcon module provides a number of constants for common media types.
See also: Media Type Constants.
Note
The configured falcon.Response JSON handler is also used to serialize
falcon.HTTPError and the json attribute of falcon.asgi.SSEvent.
The JSON handler configured in falcon.Request is used by
falcon.Request.get_param_as_json() to deserialize query params.
Therefore, when implementing a custom handler for the JSON media type, it is required
that the sync interface methods, meaning
falcon.media.BaseHandler.serialize() and falcon.media.BaseHandler.deserialize(),
are implemented even in ASGI applications. The default JSON handler,
falcon.media.JSONHandler, already implements the methods required to
work with both types of applications.
Supported Handler Types#
- class falcon.media.JSONHandler(dumps: Callable[[Any], str | bytes] | None = None, loads: Callable[[str], Any] | None = None)[source]#
JSON media handler.
This handler uses Python’s standard
jsonlibrary by default, but can be easily configured to use any of a number of third-party JSON libraries, depending on your needs. For example, you can often realize a significant performance boost under CPython by using an alternative library. Good options in this respect include orjson, python-rapidjson, and mujson.This handler will raise a
falcon.MediaNotFoundErrorwhen attempting to parse an empty body, or afalcon.MediaMalformedErrorif an error happens while parsing the body.Note
If you are deploying to PyPy, we recommend sticking with the standard library’s JSON implementation, since it will be faster in most cases as compared to a third-party library.
Custom JSON library
You can replace the default JSON handler by using a custom JSON library (see also: Replacing the Default Handlers). Overriding the default JSON implementation is simply a matter of specifying the desired
dumpsandloadsfunctions:import falcon from falcon import media import rapidjson json_handler = media.JSONHandler( dumps=rapidjson.dumps, loads=rapidjson.loads, ) extra_handlers = { 'application/json': json_handler, } app = falcon.App() app.req_options.media_handlers.update(extra_handlers) app.resp_options.media_handlers.update(extra_handlers)
Custom serialization parameters
Even if you decide to stick with the stdlib’s
json.dumpsandjson.loads, you can wrap them usingfunctools.partialto provide custom serialization or deserialization parameters supported by thedumpsandloadsfunctions, respectively (see also: Prettifying JSON Responses):import falcon from falcon import media from functools import partial json_handler = media.JSONHandler( dumps=partial( json.dumps, default=str, sort_keys=True, ), ) extra_handlers = { 'application/json': json_handler, } app = falcon.App() app.req_options.media_handlers.update(extra_handlers) app.resp_options.media_handlers.update(extra_handlers)
By default,
ensure_asciiis passed to thejson.dumpsfunction. If you override thedumpsfunction, you will need to explicitly setensure_asciitoFalsein order to enable the serialization of Unicode characters to UTF-8. This is easily done by usingfunctools.partialto apply the desired keyword argument. As also demonstrated in the previous paragraph, you can use this same technique to customize any option supported by thedumpsandloadsfunctions:from functools import partial from falcon import media import rapidjson json_handler = media.JSONHandler( dumps=partial( rapidjson.dumps, ensure_ascii=False, sort_keys=True ), )
Custom JSON encoder
You can also override the default
JSONEncoderby using a custom Encoder and updating the media handlers forapplication/jsontype to use that:import json from datetime import datetime from functools import partial import falcon from falcon import media class DatetimeEncoder(json.JSONEncoder): """Json Encoder that supports datetime objects.""" def default(self, obj): if isinstance(obj, datetime): return obj.isoformat() return super().default(obj) app = falcon.App() json_handler = media.JSONHandler( dumps=partial(json.dumps, cls=DatetimeEncoder), ) extra_handlers = { 'application/json': json_handler, } app.req_options.media_handlers.update(extra_handlers) app.resp_options.media_handlers.update(extra_handlers)
Note
When testing an application employing a custom JSON encoder, bear in mind that
TestClientis decoupled from the app, and it simulates requests as if they were performed by a third-party client (just sans network). Therefore, passing the json parameter to simulate_* methods will effectively use the stdlib’sjson.dumps(). If you want to serialize custom objects for testing, you will need to dump them into a string yourself, and pass it using the body parameter instead (accompanied by theapplication/jsoncontent type header).- Keyword Arguments:
dumps (func) – Function to use when serializing JSON responses.
loads (func) – Function to use when deserializing JSON requests.
- class falcon.media.MessagePackHandler[source]#
Handler built using the
msgpackmodule.This handler uses
msgpack.unpackb()andmsgpack.Packer().pack(). The MessagePackbintype is used to distinguish between Unicode strings (of typestr) and byte strings (of typebytes).This handler will raise a
falcon.MediaNotFoundErrorwhen attempting to parse an empty body; it will raise afalcon.MediaMalformedErrorif an error happens while parsing the body.Note
This handler requires the extra
msgpackpackage (version 0.5.2 or higher), which must be installed in addition tofalconfrom PyPI:$ pip install msgpack
- class falcon.media.MultipartFormHandler(parse_options: MultipartParseOptions | None = None)[source]#
Multipart form (content type
multipart/form-data) media handler.The
multipart/form-datamedia type for HTML5 forms is defined in RFC 7578.The multipart media type itself is defined in RFC 2046 section 5.1.
Note
Unlike many form parsing implementations in other frameworks, this handler does not consume the stream immediately. Rather, the stream is consumed on-demand and parsed into individual body parts while iterating over the media object.
For examples on parsing the request form, see also: Multipart Forms.
- class falcon.media.URLEncodedFormHandler(keep_blank: bool = True, csv: bool = False)[source]#
URL-encoded form data handler.
This handler parses
application/x-www-form-urlencodedHTML forms to adict, similar to how URL query parameters are parsed. An empty body will be parsed as an empty dict.When deserializing, this handler will raise
falcon.MediaMalformedErrorif the request payload cannot be parsed as ASCII or if any of the URL-encoded strings in the payload are not valid UTF-8.As documented for
urllib.parse.urlencode, when serializing, the media object must either be adictor a sequence of two-elementtuple’s. If any values in the media object are sequences, each sequence element is converted to a separate parameter.- Keyword Arguments:
keep_blank (bool) – Whether to keep empty-string values from the form when deserializing.
csv (bool) – Whether to split comma-separated form values into list when deserializing.
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 and documented below.
In general WSGI applications only use the sync methods, while
ASGI applications only use the async one.
The JSON handled is an exception to this, since it’s used also by
other parts of the framework, not only in the media handling.
See the note above for more details.
- class falcon.media.BaseHandler[source]#
Abstract Base Class for an internet media type handler.
- serialize(media: object, content_type: str) bytes[source]#
Serialize the media object on a
falcon.Response.By default, this method raises an instance of
NotImplementedError. Therefore, it must be overridden in order to work with WSGI apps. Child classes can ignore this method if they are only to be used with ASGI apps, as long as they overrideserialize_async().Note
The JSON media handler is an exception in requiring the implementation of the sync version also for ASGI apps. See the this section for more details.
- Parameters:
media (object) – A serializable object.
content_type (str) – Type of response content.
- Returns:
The resulting serialized bytes from the input object.
- Return type:
bytes
- async serialize_async(media: object, content_type: str) bytes[source]#
Serialize the media object on a
falcon.Response.This method is similar to
serialize()except that it is asynchronous. The default implementation simply callsserialize(). If the media object may be awaitable, or is otherwise something that should be read asynchronously, subclasses must override the default implementation in order to handle that case.Note
By default, the
serialize()method raises an instance ofNotImplementedError. Therefore, child classes must either overrideserialize()orserialize_async()in order to be compatible with ASGI apps.- Parameters:
media (object) – A serializable object.
content_type (str) – Type of response content.
- Returns:
The resulting serialized bytes from the input object.
- Return type:
bytes
- deserialize(stream: ReadableIO, content_type: str | None, content_length: int | None) object[source]#
Deserialize the
falcon.Requestbody.By default, this method raises an instance of
NotImplementedError. Therefore, it must be overridden in order to work with WSGI apps. Child classes can ignore this method if they are only to be used with ASGI apps, as long as they overridedeserialize_async().Note
The JSON media handler is an exception in requiring the implementation of the sync version also for ASGI apps. See the this section for more details.
- Parameters:
stream (object) – Readable file-like object to deserialize.
content_type (str) – Type of request content.
content_length (int) – Length of request content.
- Returns:
A deserialized object.
- Return type:
object
- async deserialize_async(stream: AsyncReadableIO, content_type: str | None, content_length: int | None) object[source]#
Deserialize the
falcon.Requestbody.This method is similar to
deserialize()except that it is asynchronous. The default implementation adapts the synchronousdeserialize()method viaio.BytesIO. For improved performance, media handlers should override this method.Note
By default, the
deserialize()method raises an instance ofNotImplementedError. Therefore, child classes must either overridedeserialize()ordeserialize_async()in order to be compatible with ASGI apps.- Parameters:
stream (object) – Asynchronous file-like object to deserialize.
content_type (str) – Type of request content.
content_length (int) – Length of request content, or
Noneif the Content-Length header is missing.
- Returns:
A deserialized object.
- Return type:
object
- exhaust_stream = False#
Whether to exhaust the input stream upon finishing deserialization.
Exhausting the stream may be useful for handlers that do not necessarily consume the whole stream, but the deserialized media object is complete and does not involve further streaming.
Tip
In order to use your custom media handler in a Falcon app,
you’ll have to add an instance of your class to the app’s media handlers
(specified in RequestOptions
and ResponseOptions,
respectively).
See also: Replacing the Default Handlers.
Handlers Mapping#
- class falcon.media.Handlers(initial: Mapping[str, BaseHandler] | None = None)[source]#
A
dict-like object that manages Internet media type handlers.
Media Type Constants#
The falcon module provides a number of constants for
common media type strings, including the following:
falcon.MEDIA_JSON
falcon.MEDIA_MSGPACK
falcon.MEDIA_MULTIPART
falcon.MEDIA_URLENCODED
falcon.MEDIA_YAML
falcon.MEDIA_XML
falcon.MEDIA_HTML
falcon.MEDIA_JS
falcon.MEDIA_TEXT
falcon.MEDIA_JPEG
falcon.MEDIA_PNG
falcon.MEDIA_GIF