PEP 727: Specify `Doc` in type aliases documents the type alias symbol, update rejected ideas (#3581)

📝 Update PEP to specify `Doc` in type aliases documents the type alias symbol
This commit is contained in:
Sebastián Ramírez 2023-12-11 23:21:54 +00:00 committed by GitHub
parent 0244d89009
commit d9e47a206b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 61 additions and 43 deletions

View File

@ -136,11 +136,12 @@ conventions:
* Elimination of the possibility of having inconsistencies between the name of a
parameter in the signature and the name in the docstring when it is renamed.
* Reuse of documentation for symbols used in multiple places via type aliases.
* Access to the documentation string for each symbol at runtime, including existing
(older) Python versions.
* A more formalized way to document other symbols, like type aliases, that could
use :py:class:`~typing.Annotated`.
* No microsyntax to learn for newcomers, it's just Python syntax.
* Parameter documentation inheritance for functions captured
@ -171,9 +172,8 @@ For example:
:py:class:`~typing.Annotated` is normally used as a type annotation, in those cases,
any ``typing.Doc`` inside of it would document the symbol being annotated.
When :py:class:`~typing.Annotated` is used to declare a type alias and that type
alias is used in an annotation, ``typing.Doc`` would document the symbol being
annotated instead of exclusively the type alias.
When :py:class:`~typing.Annotated` is used to declare a type alias, ``typing.Doc``
would then document the type alias symbol.
For example:
@ -181,51 +181,29 @@ For example:
from typing import Annotated, Doc, TypeAlias
from external_library import UserResolver
UserName: TypeAlias = Annotated[str, Doc("The user's name")]
CurrentUser: TypeAlias = Annotated[str, Doc("The current system user"), UserResolver()]
def create_user(name: Annotated[str, Doc("The user's name")]): ...
def delete_user(name: Annotated[str, Doc("The user to delete")]): ...
def create_user(name: UserName): ...
def delete_user(name: UserName): ...
When a type alias is put inside of :py:class:`~typing.Annotated` and it has a
``typing.Doc``, the last one used (the top-most) takes precedence, this allows
overriding the documentation.
For example:
.. code:: python
from typing import Annotated, Doc, TypeAlias
UserName: TypeAlias = Annotated[str, Doc("The user's name")]
def create_user(name: UserName): ...
def delete_user(name: Annotated[UserName, Doc("The user to delete")]): ...
In this case, for the ``name`` parameter in ``delete_user()``, the documentation string
would be ``"The user to delete"``.
In this case, if a user imported ``CurrentUser``, tools like editors could provide
a tooltip with the documentation string when a user hovers over that symbol, or
documentation tools could include the type alias with its documentation in their
generated output.
For tools extracting the information at runtime, they would normally use
:py:func:`~typing.get_type_hints` with the parameter ``include_extras=True``,
and as :py:class:`~typing.Annotated` is normalized (even with type aliases), this
would mean they should use the last ``typing.Doc`` available, as that is the last
one used.
would mean they should use the last ``typing.Doc`` available, if more than one is
used, as that is the last one used.
At runtime, ``typing.Doc`` instances have an attribute ``documentation`` with the
string passed to it.
When a type alias is used on its own, without annotating any additional symbol,
``typing.Doc`` documents the type alias itself. This would be useful if the
type alias is included on its own in documentation systems or if it's used directly
in some way, to show tooltips in editors.
When a function's signature is captured by a :py:class:`~typing.ParamSpec`,
any documentation strings associated with the parameters should be retained.
@ -625,6 +603,50 @@ difficult to combine it with :py:class:`~typing.Annotated` for other purposes (
e.g. with FastAPI metadata, Pydantic fields, etc.) or adding additional metadata
apart from the documentation string (e.g. deprecation).
Transferring Documentation from Type aliases
--------------------------------------------
A previous version of this proposal specified that when type aliases declared with
:py:class:`~typing.Annotated` were used, and these type aliases were used in
annotations, the documentation string would be transferred to the annotated symbol.
For example:
.. code:: python
from typing import Annotated, Doc, TypeAlias
UserName: TypeAlias = Annotated[str, Doc("The user's name")]
def create_user(name: UserName): ...
def delete_user(name: UserName): ...
This was rejected after receiving feedback from the maintainer of one of the main
components used to provide editor support.
Shorthand with Slices
---------------------
In the discussion, it was suggested to use a shorthand with slices:
.. code:: python
is_approved: Annotated[str: "The status of a PEP."]
Although this is a very clever idea and would remove the need for a new ``Doc`` class,
runtime executing of current versions of Python don't allow it.
At runtime, :py:class:`~typing.Annotated` requires at least two arguments, and it
requires the first argument to be type, it crashes if it is a slice.
Open Issues
===========
@ -665,10 +687,6 @@ features. But again, as with type annotations, this would be optional and only
to be used by those that are willing to take the extra verbosity in exchange
for the benefits.
Additionally, if type aliases were used, documentation could be put outside of the
signature and the docstring, reducing the total verbosity of the signature and the
function body.
Of course, more advanced users might want to look at the source code of the libraries
and if the authors of those libraries adopted this, those advanced users would end up
having to look at that code with additional signature verbosity instead of docstring