diff --git a/docs/guides/libraries.rst b/docs/guides/libraries.rst index 4b90da49..d5edb137 100644 --- a/docs/guides/libraries.rst +++ b/docs/guides/libraries.rst @@ -412,6 +412,8 @@ specified only by name, use the keyword-only separator (``*``). def create_user(age: int, *, dob: Optional[date] = None): ... +.. _annotating-decorators: + Annotating Decorators --------------------- @@ -448,6 +450,66 @@ original signature, thus blinding type checkers and other tools that provide signature assistance. As such, library authors are discouraged from creating decorators that mutate function signatures in this manner. +.. _aliasing-decorators: + +Aliasing Decorators +------------------- + +When writing a library with a couple of decorator factories +(i.e. functions returning decorators, like ``complex_decorator`` from the +:ref:`annotating-decorators` section) it may be tempting to create a shortcut +for a decorator. + +Different type checkers handle :data:`TypeAlias ` involving +:class:`Callable ` in a +different manner, so the most portable and easy way to create a shortcut +is to define a callable :class:`Protocol ` as described in the +:ref:`callback-protocols` section of the Typing Specification. + +There is already a :class:`Protocol ` called +``IdentityFunction`` defined in +`_typeshed `_: + +.. code:: python + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from _typeshed import IdentityFunction + + def decorator_factory(*, mode: str) -> "IdentityFunction": + """ + Decorator factory is invoked with arguments like this: + @decorator_factory(mode="easy") + def my_function(): ... + """ + ... + +For non-trivial decorators with custom logic, it is still possible +to define a custom protocol using :class:`ParamSpec ` +and :data:`Concatenate ` mechanisms: + +.. code:: python + + class Client: ... + + P = ParamSpec("P") + R = TypeVar("R") + + class PClientInjector(Protocol): + def __call__(self, _: Callable[Concatenate[Client, P], R], /) -> Callable[P, R]: + ... + + def inject_client(service: str) -> PClientInjector: + """ + Decorator factory is invoked with arguments like this: + @inject_client("testing") + def my_function(client: Client, value: int): ... + + my_function then takes only value + """ + + Generic Classes and Functions -----------------------------