Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Dec 11, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 59 additions & 41 deletions peps/pep-0727.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -171,61 +172,38 @@ 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.

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): ...


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.
When :py:class:`~typing.Annotated` is used to declare a type alias, ``typing.Doc``
would then document the type alias symbol.

For example:

.. code:: python

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 create_user(name: UserName): ...
def delete_user(name: Annotated[str, Doc("The user to delete")]): ...

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.

Expand Down Expand Up @@ -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
===========

Expand Down Expand Up @@ -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
Expand Down