Skip to content

Commit ca2a739

Browse files
tiangoloJelleZijlstraAlexWaygood
authored
Add Doc from PEP 727: https://peps.python.org/pep-0727/ (#277)
Co-authored-by: Jelle Zijlstra <[email protected]> Co-authored-by: Alex Waygood <[email protected]>
1 parent 13c9484 commit ca2a739

File tree

4 files changed

+105
-0
lines changed

4 files changed

+105
-0
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Release 4.8.0 (???)
22

3+
- Add `typing_extensions.Doc`, as proposed by PEP 727. Patch by
4+
Sebastián Ramírez.
35
- Drop support for Python 3.7 (including PyPy-3.7). Patch by Alex Waygood.
46
- Fix bug where `get_original_bases()` would return incorrect results when
57
called on a concrete subclass of a generic class. Patch by Alex Waygood

doc/index.rst

+31
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,37 @@ Functions
722722

723723
.. versionadded:: 4.1.0
724724

725+
726+
Annotation metadata
727+
~~~~~~~~~~~~~~~~~~~
728+
729+
.. class:: Doc(documentation, /)
730+
731+
Define the documentation of a type annotation using :data:`Annotated`, to be
732+
used in class attributes, function and method parameters, return values,
733+
and variables.
734+
735+
The value should be a positional-only string literal to allow static tools
736+
like editors and documentation generators to use it.
737+
738+
This complements docstrings.
739+
740+
The string value passed is available in the attribute ``documentation``.
741+
742+
Example::
743+
744+
>>> from typing_extensions import Annotated, Doc
745+
>>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ...
746+
747+
.. versionadded:: 4.8.0
748+
749+
See :pep:`727`.
750+
751+
.. attribute:: documentation
752+
753+
The documentation string passed to :class:`Doc`.
754+
755+
725756
Pure aliases
726757
~~~~~~~~~~~~
727758

src/test_typing_extensions.py

+36
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from typing_extensions import clear_overloads, get_overloads, overload
3939
from typing_extensions import NamedTuple
4040
from typing_extensions import override, deprecated, Buffer, TypeAliasType, TypeVar, get_protocol_members, is_protocol
41+
from typing_extensions import Doc
4142
from _typed_dict_test_helper import Foo, FooGeneric, VeryAnnotated
4243

4344
# Flags used to mark tests that only apply after a specific
@@ -5898,5 +5899,40 @@ class MyAlias(TypeAliasType):
58985899
pass
58995900

59005901

5902+
class DocTests(BaseTestCase):
5903+
def test_annotation(self):
5904+
5905+
def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: pass
5906+
5907+
hints = get_type_hints(hi, include_extras=True)
5908+
doc_info = hints["to"].__metadata__[0]
5909+
self.assertEqual(doc_info.documentation, "Who to say hi to")
5910+
self.assertIsInstance(doc_info, Doc)
5911+
5912+
def test_repr(self):
5913+
doc_info = Doc("Who to say hi to")
5914+
self.assertEqual(repr(doc_info), "Doc('Who to say hi to')")
5915+
5916+
def test_hashability(self):
5917+
doc_info = Doc("Who to say hi to")
5918+
self.assertIsInstance(hash(doc_info), int)
5919+
self.assertNotEqual(hash(doc_info), hash(Doc("Who not to say hi to")))
5920+
5921+
def test_equality(self):
5922+
doc_info = Doc("Who to say hi to")
5923+
# Equal to itself
5924+
self.assertEqual(doc_info, doc_info)
5925+
# Equal to another instance with the same string
5926+
self.assertEqual(doc_info, Doc("Who to say hi to"))
5927+
# Not equal to another instance with a different string
5928+
self.assertNotEqual(doc_info, Doc("Who not to say hi to"))
5929+
5930+
def test_pickle(self):
5931+
doc_info = Doc("Who to say hi to")
5932+
for proto in range(pickle.HIGHEST_PROTOCOL):
5933+
pickled = pickle.dumps(doc_info, protocol=proto)
5934+
self.assertEqual(doc_info, pickle.loads(pickled))
5935+
5936+
59015937
if __name__ == '__main__':
59025938
main()

src/typing_extensions.py

+36
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
'clear_overloads',
6161
'dataclass_transform',
6262
'deprecated',
63+
'Doc',
6364
'get_overloads',
6465
'final',
6566
'get_args',
@@ -2813,6 +2814,41 @@ def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]:
28132814
return frozenset(_get_protocol_attrs(tp))
28142815

28152816

2817+
if hasattr(typing, "Doc"):
2818+
Doc = typing.Doc
2819+
else:
2820+
class Doc:
2821+
"""Define the documentation of a type annotation using ``Annotated``, to be
2822+
used in class attributes, function and method parameters, return values,
2823+
and variables.
2824+
2825+
The value should be a positional-only string literal to allow static tools
2826+
like editors and documentation generators to use it.
2827+
2828+
This complements docstrings.
2829+
2830+
The string value passed is available in the attribute ``documentation``.
2831+
2832+
Example::
2833+
2834+
>>> from typing_extensions import Annotated, Doc
2835+
>>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ...
2836+
"""
2837+
def __init__(self, documentation: str, /) -> None:
2838+
self.documentation = documentation
2839+
2840+
def __repr__(self) -> str:
2841+
return f"Doc({self.documentation!r})"
2842+
2843+
def __hash__(self) -> int:
2844+
return hash(self.documentation)
2845+
2846+
def __eq__(self, other: object) -> bool:
2847+
if not isinstance(other, Doc):
2848+
return NotImplemented
2849+
return self.documentation == other.documentation
2850+
2851+
28162852
# Aliases for items that have always been in typing.
28172853
# Explicitly assign these (rather than using `from typing import *` at the top),
28182854
# so that we get a CI error if one of these is deleted from typing.py

0 commit comments

Comments
 (0)