Typing#

Type checking support was introduced in Falcon 4.0. While most of the library is now typed, further type annotations may be added throughout the 4.x release cycle. To improve them, we may introduce changes to the typing that do not affect runtime behavior, but may surface new or different errors with type checkers.

Note

All undocumented type aliases coming from falcon._typing are considered private to the framework itself, and not meant for annotating applications using Falcon. To that end, it is advisable to only use classes from the public interface, and public aliases from falcon.typing, e.g.:

class MyResource:
    def on_get(self, req: falcon.Request, resp: falcon.Response) -> None:
        resp.media = {'message': 'Hello, World!'}

If you still decide to reuse the private aliases anyway, they should preferably be imported inside if TYPE_CHECKING: blocks in order to avoid possible runtime errors after an update. Also, make sure to let us know which essential aliases are missing from the public interface!

App Types#

Falcon’s App (and asgi.App) is a generic type parametrized by its request and response types. Consequently, static type checkers (such as Mypy or Pyright) can correctly infer the specialized App type from the request_type and/or response_type arguments supplied to the initializer.

The use of generics should in most cases require no explicit effort on your side. However, if you annotate your variables or return types as falcon.App, the type checker may require you to provide the explicit type parameters when running in the strict mode (Mypy calls the option --disallow-any-generics, also part of the --strict mode flag).

For instance, the following mini-application will not pass type checking with Mypy in the --strict mode:

import falcon


class HelloResource:
    def on_get(self, req: falcon.Request, resp: falcon.Response) -> None:
        resp.media = {'message': 'Hello, typing!'}


def create_app() -> falcon.App:
    app = falcon.App()
    app.add_route('/', HelloResource())
    return app

In order to address this type-arg issue, we could explicitly specify which variant of App our create_app() is expected to instantiate:

import falcon


class HelloResource:
    def on_get(self, req: falcon.Request, resp: falcon.Response) -> None:
        resp.media = {'message': 'Hello, typing!'}


def create_app() -> falcon.App[falcon.Request, falcon.Response]:
    app = falcon.App()
    app.add_route('/', HelloResource())
    return app

Alternatively, we could ask ourselves what the purpose of create_app() is. If we want to instantiate a WSGI application suitable for a PEP-3333 compliant app server, we could type it accordingly:

import wsgiref.simple_server
import wsgiref.types

import falcon


class HelloResource:
    def on_get(self, req: falcon.Request, resp: falcon.Response) -> None:
        resp.media = {'message': 'Hello, typing!'}


def create_app() -> wsgiref.types.WSGIApplication:
    app = falcon.App()
    app.add_route('/', HelloResource())
    return app


if __name__ == '__main__':
    with wsgiref.simple_server.make_server('', 8000, create_app()) as httpd:
        httpd.serve_forever()

Both alternatives should now pass type checking in the --strict mode.

Attention

For illustration purposes, we also included a wsgiref.simple_server-based server in the second revised example, allowing you to run the file directly with Python 3.11+. However, for a real deployment you should always install a production-ready WSGI or ASGI server.

Changed in version 4.2: falcon.App and falcon.asgi.App are now annotated as generic types parametrized by the request and response classes.

Known Limitations#

Falcon’s emphasis on flexibility and performance presents certain challenges when it comes to adding type annotations to the existing code base.

One notable limitation involves using custom Request and/or Response types together with a custom context type:

from falcon import Request


class MyRichContext:
    """My fancy context type with well annotated attributes."""

    ...


class MyRequest(Request):
    context_type = MyRichContext

Although a code base employing the above pattern may pass type checking without any warnings even under --strict settings, the problem here is that MyRequest.context is still annotated as Context, allowing arbitrary attribute access. As a result, this would mask any potential typing issues in the use of MyRichContext.

If you make extensive use of a custom context type, and do want to perform type checking against its interface, you can explicitly redefine context as having the desired type. In order to convince the type checker, this will require at least one strategically placed # type: ignore:

from falcon import Request


class MyRichContext:
    """My fancy context type with well annotated attributes."""

    ...


class MyRequest(Request):
    context_type = MyRichContext

    context: MyRichContext  # type: ignore[assignment]

Our efforts to work around this issue have so far hit the wall of PEP 526, which states that a ClassVar parameter cannot include any type variables, regardless of the level of nesting.

If you come up with an elegant solution to this problem, let us know!

Another known inconsistency is the typing of the converter interface, where certain subclasses (such as PathConverter) declare a different input type than the base convert() method. (See also the discussions and possible solutions on the GitHub issue #2396.)

Important

The above issues are only typing limitations that have no effect outside of type checking – applications will work just fine at runtime!

Public Type Aliases#

Public Falcon type alias definitions.

class falcon.typing.AsyncReadableIO(*args, **kwargs)[source]#

Async file-like protocol that defines only a read method, and is iterable.

Added in version 4.0.

falcon.typing.Headers#

Headers dictionary returned by the framework.

Added in version 4.0.

class falcon.typing.ReadableIO(*args, **kwargs)[source]#

File-like protocol that defines only a read method.

Added in version 4.0.

falcon.typing.SSEEmitter#

Async generator or iterator over Server-Sent Events (instances of falcon.asgi.SSEvent).

Added in version 4.0.