Skip to content

Commit 971a49b

Browse files
authored
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
1 parent 5ce45ed commit 971a49b

File tree

1 file changed

+119
-123
lines changed

1 file changed

+119
-123
lines changed

pep-0727.rst

Lines changed: 119 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@ PEP: 727
22
Title: Documentation Metadata in Typing
33
Author: Sebastián Ramírez <[email protected]>
44
Sponsor: Jelle Zijlstra <[email protected]>
5-
Discussions-To: https://discuss.python.org/t/pep-727-documentation-metadata-in-typing/32566
5+
Discussions-To: https://discuss.python.org/t/32566
66
Status: Draft
77
Type: Standards Track
88
Topic: Typing
99
Content-Type: text/x-rst
1010
Created: 28-Aug-2023
1111
Python-Version: 3.13
12-
Post-History: `30-Aug-2023 <https://discuss.python.org/t/pep-727-documentation-metadata-in-typing/32566>`__
12+
Post-History: `30-Aug-2023 <https://discuss.python.org/t/32566>`__
1313

1414

1515
Abstract
1616
========
1717

1818
This document proposes a way to complement docstrings to add additional documentation
19-
to Python symbols using type annotations with ``Annotated`` (in class attributes,
20-
function and method parameters, return values, and variables).
19+
to Python symbols using type annotations with :py:class:`~typing.Annotated`
20+
(in class attributes, function and method parameters, return values, and variables).
2121

2222

2323
Motivation
@@ -60,7 +60,7 @@ documentation in some other way (e.g. an API, a CLI, etc).
6060
Some of these previous formats tried to account for the lack of type annotations
6161
in older Python versions by including typing information in the docstrings,
6262
but now that information doesn't need to be in docstrings as there is now an official
63-
syntax for type annotations.
63+
:pep:`syntax for type annotations <484>`.
6464

6565

6666
Rationale
@@ -84,79 +84,56 @@ like to adopt it.
8484
Specification
8585
=============
8686

87+
The main proposal is to introduce a new function, ``typing.doc()``,
88+
to be used when documenting Python objects.
89+
This function MUST only be used within :py:class:`~typing.Annotated` annotations.
90+
The function takes a single string argument, ``documentation``,
91+
and returns an instance of ``typing.DocInfo``,
92+
which stores the input string unchanged.
8793

88-
``typing.doc``
89-
--------------
94+
Any tool processing ``typing.DocInfo`` objects SHOULD interpret the string as
95+
a docstring, and therefore SHOULD normalize whitespace
96+
as if ``inspect.cleandoc()`` were used.
9097

91-
The main proposal is to have a new function ``doc()`` in the ``typing`` module.
92-
Even though this is not strictly related to the type annotations, it's expected
93-
to go in ``Annotated`` type annotations, and to interact with type annotations.
98+
The string passed to ``typing.doc()`` SHOULD be of the form that would be a valid docstring.
99+
This means that `f-strings`__ and string operations SHOULD NOT be used.
100+
As this cannot be enforced by the Python runtime,
101+
tools SHOULD NOT rely on this behaviour,
102+
and SHOULD exit with an error if such a prohibited string is encountered.
94103

95-
There's also the particular benefit that it could be implemented in the
96-
``typing_extensions`` package to have support for older versions of Python and
97-
early adopters of this proposal.
98-
99-
This ``doc()`` function would receive one single parameter ``documentation`` with
100-
a documentation string.
101-
102-
This string could be a multi-line string, in which case, when extracted by tools,
103-
should be interpreted cleaning up indentation as if using ``inspect.cleandoc()``,
104-
the same procedure used for docstrings.
105-
106-
This string could probably contain markup, like Markdown or reST. As that could
107-
be highly debated, that decision is left for a future proposal, to focus here
108-
on the main functionality.
109-
110-
This specification targets static analysis tools and editors, and as such, the
111-
value passed to ``doc()`` should allow static evaluation and analysis. If a
112-
developer passes as the value something that requires runtime execution
113-
(e.g. a function call) the behavior of static analysis tools is unspecified
114-
and they could omit it from their process and results. For static analysis
115-
tools to be conformant with this specification they need only to support
116-
statically accessible values.
117-
118-
An example documenting the attributes of a class, or in this case, the keys
119-
of a ``TypedDict``, could look like this:
120-
121-
.. code-block::
122-
123-
from typing import Annotated, TypedDict, NotRequired, doc
124-
125-
126-
class User(TypedDict):
127-
firstname: Annotated[str, doc("The user's first name")]
128-
lastname: Annotated[str, doc("The user's last name")]
104+
__ https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals
129105

106+
Examples
107+
--------
130108

131-
An example documenting the parameters of a function could look like this:
109+
Class attributes may be documented:
132110

133-
.. code-block::
111+
.. code:: python
134112
135-
from typing import Annotated, doc
113+
from typing import Annotated, doc
136114
115+
class User:
116+
first_name: Annotated[str, doc("The user's first name")]
117+
last_name: Annotated[str, doc("The user's last name")]
137118
138-
def create_user(
139-
lastname: Annotated[str, doc("The **last name** of the newly created user")],
140-
firstname: Annotated[str | None, doc("The user's **first name**")] = None,
141-
) -> Annotated[User, doc("The created user after saving in the database")]:
142-
"""
143-
Create a new user in the system, it needs the database connection to be already
144-
initialized.
145-
"""
146-
pass
119+
...
147120
121+
As can function or method parameters:
148122

149-
The return of the ``doc()`` function is an instance of a class that can be checked
150-
and used at runtime, defined similar to:
123+
.. code:: python
151124
152-
.. code-block::
125+
from typing import Annotated, doc
153126
154-
class DocInfo:
155-
def __init__(self, documentation: str):
156-
self.documentation = documentation
127+
def create_user(
128+
first_name: Annotated[str, doc("The user's first name")],
129+
last_name: Annotated[str, doc("The user's last name")],
130+
cursor: DatabaseConnection | None = None,
131+
) -> Annotated[User, doc("The created user after saving in the database")]:
132+
"""Create a new user in the system.
157133
158-
...where the attribute ``documentation`` contains the same value string passed to
159-
the function ``doc()``.
134+
It needs the database connection to be already initialized.
135+
"""
136+
pass
160137
161138
162139
Additional Scenarios
@@ -171,52 +148,48 @@ but implementers are not required to support them.
171148

172149

173150
Type Alias
174-
----------
151+
''''''''''
175152

176153
When creating a type alias, like:
177154

178-
.. code-block::
155+
.. code:: python
179156
180-
Username = Annotated[str, doc("The name of a user in the system")]
157+
Username = Annotated[str, doc("The name of a user in the system")]
181158
182159
183-
...the documentation would be considered to be carried by the parameter annotated
160+
The documentation would be considered to be carried by the parameter annotated
184161
with ``Username``.
185162

186163
So, in a function like:
187164

188-
.. code-block::
165+
.. code:: python
189166
190-
def hi(
191-
to: Username,
192-
) -> None: ...
167+
def hi(to: Username) -> None: ...
193168
194169
195-
...it would be equivalent to:
170+
It would be equivalent to:
196171

197-
.. code-block::
172+
.. code:: python
198173
199-
def hi(
200-
to: Annotated[str, doc("The name of a user in the system")],
201-
) -> None: ...
174+
def hi(to: Annotated[str, doc("The name of a user in the system")]) -> None: ...
202175
203176
Nevertheless, implementers would not be required to support type aliases outside
204177
of the final type annotation to be conformant with this specification, as it
205178
could require more complex dereferencing logic.
206179

207180

208181
Annotating Type Parameters
209-
--------------------------
182+
''''''''''''''''''''''''''
210183

211184
When annotating type parameters, as in:
212185

213-
.. code-block::
186+
.. code:: python
214187
215-
def hi(
216-
to: list[Annotated[str, doc("The name of a user in a list")]],
217-
) -> None: ...
188+
def hi(
189+
to: list[Annotated[str, doc("The name of a user in a list")]],
190+
) -> None: ...
218191
219-
...the documentation in ``doc()`` would refer to what it is annotating, in this
192+
The documentation in ``doc()`` would refer to what it is annotating, in this
220193
case, each item in the list, not the list itself.
221194

222195
There are currently no practical use cases for documenting type parameters,
@@ -225,17 +198,17 @@ conformant, but it's included for completeness.
225198

226199

227200
Annotating Unions
228-
-----------------
201+
'''''''''''''''''
229202

230203
If used in one of the parameters of a union, as in:
231204

232-
.. code-block::
205+
.. code:: python
233206
234-
def hi(
235-
to: str | Annotated[list[str], doc("List of user names")],
236-
) -> None: ...
207+
def hi(
208+
to: str | Annotated[list[str], doc("List of user names")],
209+
) -> None: ...
237210
238-
...again, the documentation in ``doc()`` would refer to what it is annotating,
211+
Again, the documentation in ``doc()`` would refer to what it is annotating,
239212
in this case, this documents the list itself, not its items.
240213

241214
In particular, the documentation would not refer to a single string passed as a
@@ -247,22 +220,23 @@ included for completeness.
247220

248221

249222
Nested ``Annotated``
250-
--------------------
223+
''''''''''''''''''''
251224

252225
Continuing with the same idea above, if ``Annotated`` was used nested and used
253226
multiple times in the same parameter, ``doc()`` would refer to the type it
254227
is annotating.
255228

256229
So, in an example like:
257230

258-
.. code-block::
231+
.. code:: python
259232
260-
def hi(
261-
to: Annotated[
262-
Annotated[str, doc("A user name")] | Annotated[list, doc("A list of user names")],
263-
doc("Who to say hi to"),
264-
],
265-
) -> None: ...
233+
def hi(
234+
to: Annotated[
235+
Annotated[str, doc("A user name")]
236+
| Annotated[list, doc("A list of user names")],
237+
doc("Who to say hi to"),
238+
],
239+
) -> None: ...
266240
267241
268242
The documentation for the whole parameter ``to`` would be considered to be
@@ -281,16 +255,16 @@ of the parameter passed is of one type or another, but they are not required to
281255

282256

283257
Duplication
284-
-----------
258+
'''''''''''
285259

286260
If ``doc()`` is used multiple times in a single ``Annotated``, it would be
287261
considered invalid usage from the developer, for example:
288262

289-
.. code-block::
263+
.. code:: python
290264
291-
def hi(
292-
to: Annotated[str, doc("A user name"), doc("The current user name")],
293-
) -> None: ...
265+
def hi(
266+
to: Annotated[str, doc("A user name"), doc("The current user name")],
267+
) -> None: ...
294268
295269
296270
Implementers can consider this invalid and are not required to support this to be
@@ -302,46 +276,68 @@ can opt to support one of the ``doc()`` declarations.
302276
In that case, the suggestion would be to support the last one, just because
303277
this would support overriding, for example, in:
304278

305-
.. code-block::
279+
.. code:: python
306280
307-
User = Annotated[str, doc("A user name")]
281+
User = Annotated[str, doc("A user name")]
308282
309-
CurrentUser = Annotated[User, doc("The current user name")]
283+
CurrentUser = Annotated[User, doc("The current user name")]
310284
311285
312286
Internally, in Python, ``CurrentUser`` here is equivalent to:
313287

314-
.. code-block::
288+
.. code:: python
315289
316-
CurrentUser = Annotated[str, doc("A user name"), doc("The current user name")]
290+
CurrentUser = Annotated[str,
291+
doc("A user name"),
292+
doc("The current user name")]
317293
318294
319295
For an implementation that supports the last ``doc()`` appearance, the above
320296
example would be equivalent to:
321297

322-
.. code-block::
298+
.. code:: python
299+
300+
def hi(to: Annotated[str, doc("The current user name")]) -> None: ...
301+
302+
303+
.. you need to fill these in:
304+
305+
Backwards Compatibility
306+
=======================
307+
308+
[Describe potential impact and severity on pre-existing code.]
309+
310+
311+
Security Implications
312+
=====================
313+
314+
[How could a malicious user take advantage of this new feature?]
315+
316+
317+
How to Teach This
318+
=================
319+
320+
[How to teach users, new and experienced, how to apply the PEP to their work.]
323321
324-
def hi(
325-
to: Annotated[str, doc("The current user name")],
326-
) -> None: ...
327322
323+
Reference Implementation
324+
========================
328325

329-
Early Adopters and Older Python Versions
330-
========================================
326+
``typing.doc`` and ``typing.DocInfo`` are implemented as follows:
331327

332-
For older versions of Python and early adopters of this proposal, ``doc()`` and
333-
``DocInfo`` can be imported from the ``typing_extensions`` package.
328+
.. code:: python
334329
335-
.. code-block::
330+
def doc(documentation: str, /) -> DocInfo:
331+
return DocInfo(documentation)
336332
337-
from typing import Annotated
333+
class DocInfo:
334+
def __init__(self, documentation: str, /):
335+
self.documentation = documentation
338336
339-
from typing_extensions import doc
340337
338+
These have been implemented in the `typing_extensions`__ package.
341339

342-
def hi(
343-
to: Annotated[str, doc("The current user name")],
344-
) -> None: ...
340+
__ https://pypi.org/project/typing-extensions/
345341

346342

347343
Rejected Ideas
@@ -407,8 +403,8 @@ to be used by those that are willing to take the extra verbosity in exchange
407403
for the benefits.
408404

409405

410-
Doc is not Typing
411-
-----------------
406+
Documentation is not Typing
407+
---------------------------
412408

413409
It could also be argued that documentation is not really part of typing, or that
414410
it should live in a different module. Or that this information should not be part

0 commit comments

Comments
 (0)