diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 20221e8d0..837ab8dcb 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -13,8 +13,11 @@ build: # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub formats: - - pdf + - pdf + # We recommend specifying your dependencies to enable reproducible builds: # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: diff --git a/docs/_newsfragments/2365.misc.rst b/docs/_newsfragments/2365.misc.rst new file mode 100644 index 000000000..e379cb63a --- /dev/null +++ b/docs/_newsfragments/2365.misc.rst @@ -0,0 +1 @@ +The printable PDF version of our documentation was enabled on Read the Docs. diff --git a/docs/api/index.rst b/docs/api/index.rst index fdeaadfe3..80ca97517 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -20,3 +20,4 @@ Framework Reference inspect util testing + typing diff --git a/docs/api/typing.rst b/docs/api/typing.rst new file mode 100644 index 000000000..5bca16c44 --- /dev/null +++ b/docs/api/typing.rst @@ -0,0 +1,75 @@ +Typing +====== + +Type checking support was introduced in version 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. + +.. role:: python(code) + :language: python + +.. 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 :mod:`falcon.typing`, e.g.: + + .. code-block:: python + + 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 :python:`if TYPE_CHECKING:` blocks in order + to avoid possible runtime errors after an update. + Also, make sure to :ref:`let us know ` which essential aliases are + missing from the public interface! + + +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 :class:`~falcon.Request` and/or +:class:`~falcon.Response` types in callbacks that are passed back +to the framework, such as when adding an +:meth:`error handler `. + +For instance, the following application might unexpectedly not pass type +checking: + +.. code-block:: python + + from typing import Any + + from falcon import App, HTTPInternalServerError, Request, Response + + + class MyRequest(Request): + ... + + + def handle_os_error(req: MyRequest, resp: Response, ex: Exception, + params: dict[str, Any]) -> None: + raise HTTPInternalServerError(title='OS error!') from ex + + + app = App(request_type=MyRequest) + app.add_error_handler(OSError, handle_os_error) + +(Please also see the following GitHub issue: +`#2372 `__.) + +.. important:: + This is only a typing limitation that has no effect outside of type + checking -- the above ``app`` will run just fine! + + +Public Type Aliases +------------------- + +.. automodule:: falcon.typing + :members: diff --git a/docs/api/util.rst b/docs/api/util.rst index 254b4cb60..93fa163a4 100644 --- a/docs/api/util.rst +++ b/docs/api/util.rst @@ -89,9 +89,3 @@ Other .. autoclass:: falcon.ETag :members: - -Type Aliases ------------- - -.. automodule:: falcon.typing - :members: diff --git a/falcon/typing.py b/falcon/typing.py index 1eadeee9f..dd4aa8212 100644 --- a/falcon/typing.py +++ b/falcon/typing.py @@ -26,7 +26,7 @@ # WSGI class ReadableIO(Protocol): - """File like protocol that defines only a read method.""" + """File-like protocol that defines only a read method.""" def read(self, n: Optional[int] = ..., /) -> bytes: ...