Skip to content

Update PEP 649/749 implementation #596

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

Merged
merged 5 commits into from
May 10, 2025
Merged
Show file tree
Hide file tree
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
- Drop support for Python 3.8 (including PyPy-3.8). Patch by [Victorien Plot](https://github.com/Viicos).
- Do not attempt to re-export names that have been removed from `typing`,
anticipating the removal of `typing.no_type_check_decorator` in Python 3.15.
Patch by Jelle Zijlstra.
- Update `typing_extensions.Format` and `typing_extensions.evaluate_forward_ref` to align
with changes in Python 3.14. Patch by Jelle Zijlstra.
- Fix tests for Python 3.14. Patch by Jelle Zijlstra.

New features:

- Add support for inline typed dictionaries ([PEP 764](https://peps.python.org/pep-0764/)).
Patch by [Victorien Plot](https://github.com/Viicos).
- Add `typing_extensions.Reader` and `typing_extensions.Writer`. Patch by
Sebastian Rittau.
- Fix tests for Python 3.14. Patch by Jelle Zijlstra.

# Release 4.13.2 (April 10, 2025)

Expand Down
18 changes: 14 additions & 4 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ Functions

.. versionadded:: 4.2.0

.. function:: evaluate_forward_ref(forward_ref, *, owner=None, globals=None, locals=None, type_params=None, format=Format.VALUE)
.. function:: evaluate_forward_ref(forward_ref, *, owner=None, globals=None, locals=None, type_params=None, format=None)

Evaluate an :py:class:`typing.ForwardRef` as a :py:term:`type hint`.

Expand All @@ -796,7 +796,7 @@ Functions
This parameter must be provided (though it may be an empty tuple) if *owner*
is not given and the forward reference does not already have an owner set.
*format* specifies the format of the annotation and is a member of
the :class:`Format` enum.
the :class:`Format` enum, defaulting to :attr:`Format.VALUE`.

.. versionadded:: 4.13.0

Expand Down Expand Up @@ -952,9 +952,19 @@ Enums
for the annotations. This format is identical to the return value for
the function under earlier versions of Python.

.. attribute:: VALUE_WITH_FAKE_GLOBALS

Equal to 2. Special value used to signal that an annotate function is being
evaluated in a special environment with fake globals. When passed this
value, annotate functions should either return the same value as for
the :attr:`Format.VALUE` format, or raise :exc:`NotImplementedError`
to signal that they do not support execution in this environment.
This format is only used internally and should not be passed to
the functions in this module.

.. attribute:: FORWARDREF

Equal to 2. When :pep:`649` is implemented, this format will attempt to return the
Equal to 3. When :pep:`649` is implemented, this format will attempt to return the
conventional Python values for the annotations. However, if it encounters
an undefined name, it dynamically creates a proxy object (a ForwardRef)
that substitutes for that value in the expression.
Expand All @@ -964,7 +974,7 @@ Enums

.. attribute:: STRING

Equal to 3. When :pep:`649` is implemented, this format will produce an annotation
Equal to 4. When :pep:`649` is implemented, this format will produce an annotation
dictionary where the values have been replaced by strings containing
an approximation of the original source code for the annotation expressions.

Expand Down
5 changes: 2 additions & 3 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
from _typed_dict_test_helper import Foo, FooGeneric, VeryAnnotated
from typing_extensions import (
_FORWARD_REF_HAS_CLASS,
_PEP_649_OR_749_IMPLEMENTED,
Annotated,
Any,
AnyStr,
Expand Down Expand Up @@ -8533,7 +8532,7 @@ def test_stock_annotations_in_module(self):
get_annotations(isa.MyClass, format=Format.STRING),
{"a": "int", "b": "str"},
)
mycls = "MyClass" if _PEP_649_OR_749_IMPLEMENTED else "inspect_stock_annotations.MyClass"
mycls = "MyClass" if sys.version_info >= (3, 14) else "inspect_stock_annotations.MyClass"
self.assertEqual(
get_annotations(isa.function, format=Format.STRING),
{"a": "int", "b": "str", "return": mycls},
Expand Down Expand Up @@ -8581,7 +8580,7 @@ def test_stock_annotations_on_wrapper(self):
get_annotations(wrapped, format=Format.FORWARDREF),
{"a": int, "b": str, "return": isa.MyClass},
)
mycls = "MyClass" if _PEP_649_OR_749_IMPLEMENTED else "inspect_stock_annotations.MyClass"
mycls = "MyClass" if sys.version_info >= (3, 14) else "inspect_stock_annotations.MyClass"
self.assertEqual(
get_annotations(wrapped, format=Format.STRING),
{"a": "int", "b": "str", "return": mycls},
Expand Down
30 changes: 9 additions & 21 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3821,27 +3821,15 @@ def __eq__(self, other: object) -> bool:
__all__.append("CapsuleType")


# Using this convoluted approach so that this keeps working
# whether we end up using PEP 649 as written, PEP 749, or
# some other variation: in any case, inspect.get_annotations
# will continue to exist and will gain a `format` parameter.
_PEP_649_OR_749_IMPLEMENTED = (
hasattr(inspect, 'get_annotations')
and inspect.get_annotations.__kwdefaults__ is not None
and "format" in inspect.get_annotations.__kwdefaults__
)


class Format(enum.IntEnum):
VALUE = 1
VALUE_WITH_FAKE_GLOBALS = 2
FORWARDREF = 3
STRING = 4


if _PEP_649_OR_749_IMPLEMENTED:
get_annotations = inspect.get_annotations
if sys.version_info >= (3,14):
from annotationlib import Format, get_annotations
else:
class Format(enum.IntEnum):
VALUE = 1
VALUE_WITH_FAKE_GLOBALS = 2
FORWARDREF = 3
STRING = 4

def get_annotations(obj, *, globals=None, locals=None, eval_str=False,
format=Format.VALUE):
"""Compute the annotations dict for an object.
Expand Down Expand Up @@ -4122,7 +4110,7 @@ def evaluate_forward_ref(
globals=None,
locals=None,
type_params=None,
format=Format.VALUE,
format=None,
_recursive_guard=frozenset(),
):
"""Evaluate a forward reference as a type hint.
Expand Down
Loading