Skip to content

Commit

Permalink
Add Doc from PEP 727: https://peps.python.org/pep-0727/ (#277)
Browse files Browse the repository at this point in the history
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
  • Loading branch information
3 people committed Sep 8, 2023
1 parent 13c9484 commit ca2a739
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Release 4.8.0 (???)

- Add `typing_extensions.Doc`, as proposed by PEP 727. Patch by
Sebastián Ramírez.
- Drop support for Python 3.7 (including PyPy-3.7). Patch by Alex Waygood.
- Fix bug where `get_original_bases()` would return incorrect results when
called on a concrete subclass of a generic class. Patch by Alex Waygood
Expand Down
31 changes: 31 additions & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,37 @@ Functions

.. versionadded:: 4.1.0


Annotation metadata
~~~~~~~~~~~~~~~~~~~

.. class:: Doc(documentation, /)

Define the documentation of a type annotation using :data:`Annotated`, to be
used in class attributes, function and method parameters, return values,
and variables.

The value should be a positional-only string literal to allow static tools
like editors and documentation generators to use it.

This complements docstrings.

The string value passed is available in the attribute ``documentation``.

Example::

>>> from typing_extensions import Annotated, Doc
>>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ...

.. versionadded:: 4.8.0

See :pep:`727`.

.. attribute:: documentation

The documentation string passed to :class:`Doc`.


Pure aliases
~~~~~~~~~~~~

Expand Down
36 changes: 36 additions & 0 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from typing_extensions import clear_overloads, get_overloads, overload
from typing_extensions import NamedTuple
from typing_extensions import override, deprecated, Buffer, TypeAliasType, TypeVar, get_protocol_members, is_protocol
from typing_extensions import Doc
from _typed_dict_test_helper import Foo, FooGeneric, VeryAnnotated

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


class DocTests(BaseTestCase):
def test_annotation(self):

def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: pass

hints = get_type_hints(hi, include_extras=True)
doc_info = hints["to"].__metadata__[0]
self.assertEqual(doc_info.documentation, "Who to say hi to")
self.assertIsInstance(doc_info, Doc)

def test_repr(self):
doc_info = Doc("Who to say hi to")
self.assertEqual(repr(doc_info), "Doc('Who to say hi to')")

def test_hashability(self):
doc_info = Doc("Who to say hi to")
self.assertIsInstance(hash(doc_info), int)
self.assertNotEqual(hash(doc_info), hash(Doc("Who not to say hi to")))

def test_equality(self):
doc_info = Doc("Who to say hi to")
# Equal to itself
self.assertEqual(doc_info, doc_info)
# Equal to another instance with the same string
self.assertEqual(doc_info, Doc("Who to say hi to"))
# Not equal to another instance with a different string
self.assertNotEqual(doc_info, Doc("Who not to say hi to"))

def test_pickle(self):
doc_info = Doc("Who to say hi to")
for proto in range(pickle.HIGHEST_PROTOCOL):
pickled = pickle.dumps(doc_info, protocol=proto)
self.assertEqual(doc_info, pickle.loads(pickled))


if __name__ == '__main__':
main()
36 changes: 36 additions & 0 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
'clear_overloads',
'dataclass_transform',
'deprecated',
'Doc',
'get_overloads',
'final',
'get_args',
Expand Down Expand Up @@ -2813,6 +2814,41 @@ def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]:
return frozenset(_get_protocol_attrs(tp))


if hasattr(typing, "Doc"):
Doc = typing.Doc
else:
class Doc:
"""Define the documentation of a type annotation using ``Annotated``, to be
used in class attributes, function and method parameters, return values,
and variables.
The value should be a positional-only string literal to allow static tools
like editors and documentation generators to use it.
This complements docstrings.
The string value passed is available in the attribute ``documentation``.
Example::
>>> from typing_extensions import Annotated, Doc
>>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ...
"""
def __init__(self, documentation: str, /) -> None:
self.documentation = documentation

def __repr__(self) -> str:
return f"Doc({self.documentation!r})"

def __hash__(self) -> int:
return hash(self.documentation)

def __eq__(self, other: object) -> bool:
if not isinstance(other, Doc):
return NotImplemented
return self.documentation == other.documentation


# Aliases for items that have always been in typing.
# Explicitly assign these (rather than using `from typing import *` at the top),
# so that we get a CI error if one of these is deleted from typing.py
Expand Down

0 comments on commit ca2a739

Please sign in to comment.