PEP 727: Review (#3316)

* Strip thread name

* Link to Python docs

* Reference PEP 484

* Fix heading levels

* Add missing sections (as a comment)

* Rename EAaOPV to Reference Implementation

* Code block formatting

* Remove leading elipses

* Tighten usage criteria

* Remove non-specification content

* Rewrite the Specification

* Simplify Examples and move DocInfo to the reference implementation

* Improve the Reference Implementation

* Syntax
This commit is contained in:
Adam Turner 2023-09-08 06:48:58 +01:00 committed by GitHub
parent 5ce45eda6d
commit 971a49b67a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 121 additions and 125 deletions

View File

@ -2,22 +2,22 @@ PEP: 727
Title: Documentation Metadata in Typing Title: Documentation Metadata in Typing
Author: Sebastián Ramírez <tiangolo@gmail.com> Author: Sebastián Ramírez <tiangolo@gmail.com>
Sponsor: Jelle Zijlstra <jelle.zijlstra@gmail.com> Sponsor: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Discussions-To: https://discuss.python.org/t/pep-727-documentation-metadata-in-typing/32566 Discussions-To: https://discuss.python.org/t/32566
Status: Draft Status: Draft
Type: Standards Track Type: Standards Track
Topic: Typing Topic: Typing
Content-Type: text/x-rst Content-Type: text/x-rst
Created: 28-Aug-2023 Created: 28-Aug-2023
Python-Version: 3.13 Python-Version: 3.13
Post-History: `30-Aug-2023 <https://discuss.python.org/t/pep-727-documentation-metadata-in-typing/32566>`__ Post-History: `30-Aug-2023 <https://discuss.python.org/t/32566>`__
Abstract Abstract
======== ========
This document proposes a way to complement docstrings to add additional documentation This document proposes a way to complement docstrings to add additional documentation
to Python symbols using type annotations with ``Annotated`` (in class attributes, to Python symbols using type annotations with :py:class:`~typing.Annotated`
function and method parameters, return values, and variables). (in class attributes, function and method parameters, return values, and variables).
Motivation Motivation
@ -60,7 +60,7 @@ documentation in some other way (e.g. an API, a CLI, etc).
Some of these previous formats tried to account for the lack of type annotations Some of these previous formats tried to account for the lack of type annotations
in older Python versions by including typing information in the docstrings, in older Python versions by including typing information in the docstrings,
but now that information doesn't need to be in docstrings as there is now an official but now that information doesn't need to be in docstrings as there is now an official
syntax for type annotations. :pep:`syntax for type annotations <484>`.
Rationale Rationale
@ -84,81 +84,58 @@ like to adopt it.
Specification Specification
============= =============
The main proposal is to introduce a new function, ``typing.doc()``,
to be used when documenting Python objects.
This function MUST only be used within :py:class:`~typing.Annotated` annotations.
The function takes a single string argument, ``documentation``,
and returns an instance of ``typing.DocInfo``,
which stores the input string unchanged.
``typing.doc`` Any tool processing ``typing.DocInfo`` objects SHOULD interpret the string as
-------------- a docstring, and therefore SHOULD normalize whitespace
as if ``inspect.cleandoc()`` were used.
The main proposal is to have a new function ``doc()`` in the ``typing`` module. The string passed to ``typing.doc()`` SHOULD be of the form that would be a valid docstring.
Even though this is not strictly related to the type annotations, it's expected This means that `f-strings`__ and string operations SHOULD NOT be used.
to go in ``Annotated`` type annotations, and to interact with type annotations. As this cannot be enforced by the Python runtime,
tools SHOULD NOT rely on this behaviour,
and SHOULD exit with an error if such a prohibited string is encountered.
There's also the particular benefit that it could be implemented in the __ https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals
``typing_extensions`` package to have support for older versions of Python and
early adopters of this proposal.
This ``doc()`` function would receive one single parameter ``documentation`` with Examples
a documentation string. --------
This string could be a multi-line string, in which case, when extracted by tools, Class attributes may be documented:
should be interpreted cleaning up indentation as if using ``inspect.cleandoc()``,
the same procedure used for docstrings.
This string could probably contain markup, like Markdown or reST. As that could .. code:: python
be highly debated, that decision is left for a future proposal, to focus here
on the main functionality.
This specification targets static analysis tools and editors, and as such, the
value passed to ``doc()`` should allow static evaluation and analysis. If a
developer passes as the value something that requires runtime execution
(e.g. a function call) the behavior of static analysis tools is unspecified
and they could omit it from their process and results. For static analysis
tools to be conformant with this specification they need only to support
statically accessible values.
An example documenting the attributes of a class, or in this case, the keys
of a ``TypedDict``, could look like this:
.. code-block::
from typing import Annotated, TypedDict, NotRequired, doc
class User(TypedDict):
firstname: Annotated[str, doc("The user's first name")]
lastname: Annotated[str, doc("The user's last name")]
An example documenting the parameters of a function could look like this:
.. code-block::
from typing import Annotated, doc from typing import Annotated, doc
class User:
first_name: Annotated[str, doc("The user's first name")]
last_name: Annotated[str, doc("The user's last name")]
...
As can function or method parameters:
.. code:: python
from typing import Annotated, doc
def create_user( def create_user(
lastname: Annotated[str, doc("The **last name** of the newly created user")], first_name: Annotated[str, doc("The user's first name")],
firstname: Annotated[str | None, doc("The user's **first name**")] = None, last_name: Annotated[str, doc("The user's last name")],
cursor: DatabaseConnection | None = None,
) -> Annotated[User, doc("The created user after saving in the database")]: ) -> Annotated[User, doc("The created user after saving in the database")]:
""" """Create a new user in the system.
Create a new user in the system, it needs the database connection to be already
initialized. It needs the database connection to be already initialized.
""" """
pass pass
The return of the ``doc()`` function is an instance of a class that can be checked
and used at runtime, defined similar to:
.. code-block::
class DocInfo:
def __init__(self, documentation: str):
self.documentation = documentation
...where the attribute ``documentation`` contains the same value string passed to
the function ``doc()``.
Additional Scenarios Additional Scenarios
-------------------- --------------------
@ -171,34 +148,30 @@ but implementers are not required to support them.
Type Alias Type Alias
---------- ''''''''''
When creating a type alias, like: When creating a type alias, like:
.. code-block:: .. code:: python
Username = Annotated[str, doc("The name of a user in the system")] Username = Annotated[str, doc("The name of a user in the system")]
...the documentation would be considered to be carried by the parameter annotated The documentation would be considered to be carried by the parameter annotated
with ``Username``. with ``Username``.
So, in a function like: So, in a function like:
.. code-block:: .. code:: python
def hi( def hi(to: Username) -> None: ...
to: Username,
) -> None: ...
...it would be equivalent to: It would be equivalent to:
.. code-block:: .. code:: python
def hi( def hi(to: Annotated[str, doc("The name of a user in the system")]) -> None: ...
to: Annotated[str, doc("The name of a user in the system")],
) -> None: ...
Nevertheless, implementers would not be required to support type aliases outside Nevertheless, implementers would not be required to support type aliases outside
of the final type annotation to be conformant with this specification, as it of the final type annotation to be conformant with this specification, as it
@ -206,17 +179,17 @@ could require more complex dereferencing logic.
Annotating Type Parameters Annotating Type Parameters
-------------------------- ''''''''''''''''''''''''''
When annotating type parameters, as in: When annotating type parameters, as in:
.. code-block:: .. code:: python
def hi( def hi(
to: list[Annotated[str, doc("The name of a user in a list")]], to: list[Annotated[str, doc("The name of a user in a list")]],
) -> None: ... ) -> None: ...
...the documentation in ``doc()`` would refer to what it is annotating, in this The documentation in ``doc()`` would refer to what it is annotating, in this
case, each item in the list, not the list itself. case, each item in the list, not the list itself.
There are currently no practical use cases for documenting type parameters, There are currently no practical use cases for documenting type parameters,
@ -225,17 +198,17 @@ conformant, but it's included for completeness.
Annotating Unions Annotating Unions
----------------- '''''''''''''''''
If used in one of the parameters of a union, as in: If used in one of the parameters of a union, as in:
.. code-block:: .. code:: python
def hi( def hi(
to: str | Annotated[list[str], doc("List of user names")], to: str | Annotated[list[str], doc("List of user names")],
) -> None: ... ) -> None: ...
...again, the documentation in ``doc()`` would refer to what it is annotating, Again, the documentation in ``doc()`` would refer to what it is annotating,
in this case, this documents the list itself, not its items. in this case, this documents the list itself, not its items.
In particular, the documentation would not refer to a single string passed as a In particular, the documentation would not refer to a single string passed as a
@ -247,7 +220,7 @@ included for completeness.
Nested ``Annotated`` Nested ``Annotated``
-------------------- ''''''''''''''''''''
Continuing with the same idea above, if ``Annotated`` was used nested and used Continuing with the same idea above, if ``Annotated`` was used nested and used
multiple times in the same parameter, ``doc()`` would refer to the type it multiple times in the same parameter, ``doc()`` would refer to the type it
@ -255,11 +228,12 @@ is annotating.
So, in an example like: So, in an example like:
.. code-block:: .. code:: python
def hi( def hi(
to: Annotated[ to: Annotated[
Annotated[str, doc("A user name")] | Annotated[list, doc("A list of user names")], Annotated[str, doc("A user name")]
| Annotated[list, doc("A list of user names")],
doc("Who to say hi to"), doc("Who to say hi to"),
], ],
) -> None: ... ) -> None: ...
@ -281,12 +255,12 @@ of the parameter passed is of one type or another, but they are not required to
Duplication Duplication
----------- '''''''''''
If ``doc()`` is used multiple times in a single ``Annotated``, it would be If ``doc()`` is used multiple times in a single ``Annotated``, it would be
considered invalid usage from the developer, for example: considered invalid usage from the developer, for example:
.. code-block:: .. code:: python
def hi( def hi(
to: Annotated[str, doc("A user name"), doc("The current user name")], to: Annotated[str, doc("A user name"), doc("The current user name")],
@ -302,7 +276,7 @@ can opt to support one of the ``doc()`` declarations.
In that case, the suggestion would be to support the last one, just because In that case, the suggestion would be to support the last one, just because
this would support overriding, for example, in: this would support overriding, for example, in:
.. code-block:: .. code:: python
User = Annotated[str, doc("A user name")] User = Annotated[str, doc("A user name")]
@ -311,37 +285,59 @@ this would support overriding, for example, in:
Internally, in Python, ``CurrentUser`` here is equivalent to: Internally, in Python, ``CurrentUser`` here is equivalent to:
.. code-block:: .. code:: python
CurrentUser = Annotated[str, doc("A user name"), doc("The current user name")] CurrentUser = Annotated[str,
doc("A user name"),
doc("The current user name")]
For an implementation that supports the last ``doc()`` appearance, the above For an implementation that supports the last ``doc()`` appearance, the above
example would be equivalent to: example would be equivalent to:
.. code-block:: .. code:: python
def hi( def hi(to: Annotated[str, doc("The current user name")]) -> None: ...
to: Annotated[str, doc("The current user name")],
) -> None: ...
Early Adopters and Older Python Versions .. you need to fill these in:
========================================
For older versions of Python and early adopters of this proposal, ``doc()`` and Backwards Compatibility
``DocInfo`` can be imported from the ``typing_extensions`` package. =======================
.. code-block:: [Describe potential impact and severity on pre-existing code.]
from typing import Annotated
from typing_extensions import doc
def hi( Security Implications
to: Annotated[str, doc("The current user name")], =====================
) -> None: ...
[How could a malicious user take advantage of this new feature?]
How to Teach This
=================
[How to teach users, new and experienced, how to apply the PEP to their work.]
Reference Implementation
========================
``typing.doc`` and ``typing.DocInfo`` are implemented as follows:
.. code:: python
def doc(documentation: str, /) -> DocInfo:
return DocInfo(documentation)
class DocInfo:
def __init__(self, documentation: str, /):
self.documentation = documentation
These have been implemented in the `typing_extensions`__ package.
__ https://pypi.org/project/typing-extensions/
Rejected Ideas Rejected Ideas
@ -407,8 +403,8 @@ to be used by those that are willing to take the extra verbosity in exchange
for the benefits. for the benefits.
Doc is not Typing Documentation is not Typing
----------------- ---------------------------
It could also be argued that documentation is not really part of typing, or that It could also be argued that documentation is not really part of typing, or that
it should live in a different module. Or that this information should not be part it should live in a different module. Or that this information should not be part