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 by jsonschema.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 the title and description attributes explaining the validation failure, but this behavior can be modified by adding a custom error handler for MediaValidationError.

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:
  • 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 of falcon.MediaNotFoundError should be raised. By default, this error will be rendered as a 400 Bad Request response to the client. This exception may be suppressed by passing a value to the default_when_empty argument when calling Request.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 a 400 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 a falcon.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 and loads 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 and json.loads, you can wrap them using functools.partial to provide custom serialization or deserialization parameters supported by the dumps and loads 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 the json.dumps function. If you override the dumps function, you will need to explicitly set ensure_ascii to False in order to enable the serialization of Unicode characters to UTF-8. This is easily done by using functools.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 the dumps and loads 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 for application/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’s json.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 the application/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() and msgpack.Packer().pack(). The MessagePack bin type is used to distinguish between Unicode strings (of type str) and byte strings (of type bytes).

This handler will raise a falcon.MediaNotFoundError when attempting to parse an empty body; it will raise a falcon.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 to falcon 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 a dict, 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 a dict or a sequence of two-element tuple’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 override serialize_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 calls serialize(). 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 of NotImplementedError. Therefore, child classes must either override serialize() or serialize_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.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 override deserialize_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.Request body.

This method is similar to deserialize() except that it is asynchronous. The default implementation adapts the synchronous deserialize() method via io.BytesIO. For improved performance, media handlers should override this method.

Note

By default, the deserialize() method raises an instance of NotImplementedError. Therefore, child classes must either override deserialize() or deserialize_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 None if 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.

copy() Handlers[source]#

Create a shallow copy of this instance of handlers.

The resulting copy contains the same keys and values, but it can be customized separately without affecting the original object.

Returns:

A shallow copy of handlers.

Added in version 4.0.

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