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.media
using JSON Schema.This decorator provides standard JSON Schema validation via the
jsonschema
package 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
MediaValidationError
is raised by the decorator. By default, this error is rendered as a 400 (HTTPBadRequest
) response with thetitle
anddescription
attributes explaining the validation failure, but this behavior can be modified by adding a custom errorhandler
forMediaValidationError
.Note
The
jsonschema
package 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:
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.MediaNotFoundError
should be raised. By default, this error will be rendered as a400 Bad Request
response to the client. This exception may be suppressed by passing a value to thedefault_when_empty
argument 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.MediaMalformedError
should 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 Request
response 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
json
library 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.MediaNotFoundError
when attempting to parse an empty body, or afalcon.MediaMalformedError
if 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
dumps
andloads
functions: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.dumps
andjson.loads
, you can wrap them usingfunctools.partial
to provide custom serialization or deserialization parameters supported by thedumps
andloads
functions, 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_ascii
is passed to thejson.dumps
function. If you override thedumps
function, you will need to explicitly setensure_ascii
toFalse
in order to enable the serialization of Unicode characters to UTF-8. This is easily done by usingfunctools.partial
to apply the desired keyword argument. As also demonstrated in the previous paragraph, you can use this same technique to customize any option supported by thedumps
andloads
functions: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
JSONEncoder
by using a custom Encoder and updating the media handlers forapplication/json
type 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
TestClient
is 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/json
content 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
msgpack
module.This handler uses
msgpack.unpackb()
andmsgpack.Packer().pack()
. The MessagePackbin
type is used to distinguish between Unicode strings (of typestr
) and byte strings (of typebytes
).This handler will raise a
falcon.MediaNotFoundError
when attempting to parse an empty body; it will raise afalcon.MediaMalformedError
if an error happens while parsing the body.Note
This handler requires the extra
msgpack
package (version 0.5.2 or higher), which must be installed in addition tofalcon
from 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-data
media 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-urlencoded
HTML 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.MediaMalformedError
if 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 adict
or a sequence of two-elementtuple
’s. If any values in the media object are sequences, each sequence element is converted to a separate parameter.
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.
- 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.
- deserialize(stream: ReadableIO, content_type: str | None, content_length: int | None) object [source]#
Deserialize the
falcon.Request
body.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.
- async deserialize_async(stream: AsyncReadableIO, content_type: str | None, content_length: int | None) object [source]#
Deserialize the
falcon.Request
body.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.
- 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#
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