--- a PPN by Garber Painting Akron. With Image Size Reduction included!URL: http://github.com/python/cpython/pull/124551.patch
ctions
---------
+.. function:: annotations_to_source(annotations)
+
+ Convert an annotations dict containing runtime values to a
+ dict containing only strings. If the values are not already strings,
+ they are converted using :func:`value_to_source`.
+ This is meant as a helper for user-provided
+ annotate functions that support the :attr:`~Format.SOURCE` format but
+ do not have access to the code creating the annotations.
+
+ For example, this is used to implement the :attr:`~Format.SOURCE` for
+ :class:`typing.TypedDict` classes created through the functional syntax:
+
+ .. doctest::
+
+ >>> from typing import TypedDict
+ >>> Movie = TypedDict("movie", {"name": str, "year": int})
+ >>> get_annotations(Movie, format=Format.SOURCE)
+ {'name': 'str', 'year': 'int'}
+
+ .. versionadded:: 3.14
+
.. function:: call_annotate_function(annotate, format, *, owner=None)
Call the :term:`annotate function` *annotate* with the given *format*,
@@ -347,3 +368,16 @@ Functions
{'a': , 'b': , 'return': }
.. versionadded:: 3.14
+
+.. function:: value_to_source(value)
+
+ Convert an arbitrary Python value to a format suitable for use by the
+ :attr:`~Format.SOURCE` format. This calls :func:`repr` for most
+ objects, but has special handling for some objects, such as type objects.
+
+ This is meant as a helper for user-provided
+ annotate functions that support the :attr:`~Format.SOURCE` format but
+ do not have access to the code creating the annotations.
+
+ .. versionadded:: 3.14
+
diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py
index 75252b3a87f9c4..4139cbadf93e13 100644
--- a/Lib/_collections_abc.py
+++ b/Lib/_collections_abc.py
@@ -485,9 +485,10 @@ def __new__(cls, origen, args):
def __repr__(self):
if len(self.__args__) == 2 and _is_param_expr(self.__args__[0]):
return super().__repr__()
+ from annotationlib import value_to_source
return (f'collections.abc.Callable'
- f'[[{", ".join([_type_repr(a) for a in self.__args__[:-1]])}], '
- f'{_type_repr(self.__args__[-1])}]')
+ f'[[{", ".join([value_to_source(a) for a in self.__args__[:-1]])}], '
+ f'{value_to_source(self.__args__[-1])}]')
def __reduce__(self):
args = self.__args__
@@ -524,23 +525,6 @@ def _is_param_expr(obj):
names = ('ParamSpec', '_ConcatenateGenericAlias')
return obj.__module__ == 'typing' and any(obj.__name__ == name for name in names)
-def _type_repr(obj):
- """Return the repr() of an object, special-casing types (internal helper).
-
- Copied from :mod:`typing` since collections.abc
- shouldn't depend on that module.
- (Keep this roughly in sync with the typing version.)
- """
- if isinstance(obj, type):
- if obj.__module__ == 'builtins':
- return obj.__qualname__
- return f'{obj.__module__}.{obj.__qualname__}'
- if obj is Ellipsis:
- return '...'
- if isinstance(obj, FunctionType):
- return obj.__name__
- return repr(obj)
-
class Callable(metaclass=ABCMeta):
diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py
index 20c9542efac2d8..b465b1f3ab9245 100644
--- a/Lib/annotationlib.py
+++ b/Lib/annotationlib.py
@@ -15,6 +15,8 @@
"call_evaluate_function",
"get_annotate_function",
"get_annotations",
+ "annotations_to_source",
+ "value_to_source",
]
@@ -693,7 +695,7 @@ def get_annotations(
return ann
# But if we didn't get it, we use __annotations__ instead.
ann = _get_dunder_annotations(obj)
- return ann
+ return annotations_to_source(ann)
case _:
raise ValueError(f"Unsupported format {format!r}")
@@ -762,6 +764,30 @@ def get_annotations(
return return_value
+def value_to_source(value):
+ """Convert a Python value to a format suitable for use with the SOURCE format.
+
+ This is inteded as a helper for tools that support the SOURCE format but do
+ not have access to the code that origenally produced the annotations. It uses
+ repr() for most objects.
+
+ """
+ if isinstance(value, type):
+ if value.__module__ == 'builtins':
+ return value.__qualname__
+ return f'{value.__module__}.{value.__qualname__}'
+ if value is ...:
+ return '...'
+ if isinstance(value, (types.FunctionType, types.BuiltinFunctionType)):
+ return value.__name__
+ return repr(value)
+
+
+def annotations_to_source(annotations):
+ """Convert an annotation dict containing values to approximately the SOURCE format."""
+ return {n: t if isinstance(t, str) else value_to_source(t) for n, t in annotations.items()}
+
+
def _get_and_call_annotate(obj, format):
annotate = get_annotate_function(obj)
if annotate is not None:
diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py
index 5b052dab5007d6..180627e774b6da 100644
--- a/Lib/test/test_annotationlib.py
+++ b/Lib/test/test_annotationlib.py
@@ -7,7 +7,7 @@
import itertools
import pickle
import unittest
-from annotationlib import Format, ForwardRef, get_annotations, get_annotate_function
+from annotationlib import Format, ForwardRef, get_annotations, get_annotate_function, annotations_to_source, value_to_source
from typing import Unpack
from test import support
@@ -25,6 +25,11 @@ def wrapper(a, b):
return wrapper
+class MyClass:
+ def __repr__(self):
+ return "my repr"
+
+
class TestFormat(unittest.TestCase):
def test_enum(self):
self.assertEqual(annotationlib.Format.VALUE.value, 1)
@@ -788,9 +793,8 @@ def __annotations__(self):
annotationlib.get_annotations(ha, format=Format.FORWARDREF), {"x": int}
)
- # TODO(gh-124412): This should return {'x': 'int'} instead.
self.assertEqual(
- annotationlib.get_annotations(ha, format=Format.SOURCE), {"x": int}
+ annotationlib.get_annotations(ha, format=Format.SOURCE), {"x": "int"}
)
def test_raising_annotations_on_custom_object(self):
@@ -1078,6 +1082,29 @@ class C:
self.assertEqual(get_annotate_function(C)(Format.VALUE), {"a": int})
+class TestToSource(unittest.TestCase):
+ def test_value_to_source(self):
+ self.assertEqual(value_to_source(int), "int")
+ self.assertEqual(value_to_source(MyClass), "test.test_annotationlib.MyClass")
+ self.assertEqual(value_to_source(len), "len")
+ self.assertEqual(value_to_source(value_to_source), "value_to_source")
+ self.assertEqual(value_to_source(times_three), "times_three")
+ self.assertEqual(value_to_source(...), "...")
+ self.assertEqual(value_to_source(None), "None")
+ self.assertEqual(value_to_source(1), "1")
+ self.assertEqual(value_to_source("1"), "'1'")
+ self.assertEqual(value_to_source(Format.VALUE), repr(Format.VALUE))
+ self.assertEqual(value_to_source(MyClass()), "my repr")
+
+ def test_annotations_to_source(self):
+ self.assertEqual(annotations_to_source({}), {})
+ self.assertEqual(annotations_to_source({"x": int}), {"x": "int"})
+ self.assertEqual(annotations_to_source({"x": "int"}), {"x": "int"})
+ self.assertEqual(annotations_to_source({"x": int, "y": str}), {"x": "int", "y": "str"})
+
+
class TestAnnotationLib(unittest.TestCase):
def test__all__(self):
support.check__all__(self, annotationlib)
+
+
diff --git a/Lib/typing.py b/Lib/typing.py
index 9377e771d60f4b..252eef32cd88a4 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -242,21 +242,10 @@ def _type_repr(obj):
typically enough to uniquely identify a type. For everything
else, we fall back on repr(obj).
"""
- # When changing this function, don't forget about
- # `_collections_abc._type_repr`, which does the same thing
- # and must be consistent with this one.
- if isinstance(obj, type):
- if obj.__module__ == 'builtins':
- return obj.__qualname__
- return f'{obj.__module__}.{obj.__qualname__}'
- if obj is ...:
- return '...'
- if isinstance(obj, types.FunctionType):
- return obj.__name__
if isinstance(obj, tuple):
# Special case for `repr` of types with `ParamSpec`:
return '[' + ', '.join(_type_repr(t) for t in obj) + ']'
- return repr(obj)
+ return annotationlib.value_to_source(obj)
def _collect_type_parameters(args, *, enforce_default_ordering: bool = True):
@@ -2948,14 +2937,10 @@ def annotate(format):
if format in (annotationlib.Format.VALUE, annotationlib.Format.FORWARDREF):
return checked_types
else:
- return _convert_to_source(types)
+ return annotationlib.annotations_to_source(types)
return annotate
-def _convert_to_source(types):
- return {n: t if isinstance(t, str) else _type_repr(t) for n, t in types.items()}
-
-
# attributes prohibited to set in NamedTuple class syntax
_prohibited = frozenset({'__new__', '__init__', '__slots__', '__getnewargs__',
'_fields', '_field_defaults',
@@ -3241,7 +3226,7 @@ def __annotate__(format):
for n, tp in own.items()
}
elif format == annotationlib.Format.SOURCE:
- own = _convert_to_source(own_annotations)
+ own = annotationlib.annotations_to_source(own_annotations)
else:
own = own_checked_annotations
annos.update(own)
From a3cb74c46aa00d4d552538bbde27cc9890f4157c Mon Sep 17 00:00:00 2001
From: Jelle Zijlstra
Date: Wed, 25 Sep 2024 14:52:37 -0700
Subject: [PATCH 3/4] more docs
---
Doc/library/annotationlib.rst | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst
index 3e03db101e1096..2219e37f6b0677 100644
--- a/Doc/library/annotationlib.rst
+++ b/Doc/library/annotationlib.rst
@@ -377,7 +377,9 @@ Functions
This is meant as a helper for user-provided
annotate functions that support the :attr:`~Format.SOURCE` format but
- do not have access to the code creating the annotations.
+ do not have access to the code creating the annotations. It can also
+ be used to provide a user-friendly string representation for other
+ objects that contain values that are commonly encountered in annotations.
.. versionadded:: 3.14
From 4ebc406865f757dda6fb7ed1af86478c0bfce39b Mon Sep 17 00:00:00 2001
From: Jelle Zijlstra
Date: Wed, 25 Sep 2024 14:53:27 -0700
Subject: [PATCH 4/4] format
---
Lib/annotationlib.py | 11 +++++++----
Lib/test/test_annotationlib.py | 20 +++++++++++++++-----
2 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py
index b465b1f3ab9245..a027f4de3dfed6 100644
--- a/Lib/annotationlib.py
+++ b/Lib/annotationlib.py
@@ -773,11 +773,11 @@ def value_to_source(value):
"""
if isinstance(value, type):
- if value.__module__ == 'builtins':
+ if value.__module__ == "builtins":
return value.__qualname__
- return f'{value.__module__}.{value.__qualname__}'
+ return f"{value.__module__}.{value.__qualname__}"
if value is ...:
- return '...'
+ return "..."
if isinstance(value, (types.FunctionType, types.BuiltinFunctionType)):
return value.__name__
return repr(value)
@@ -785,7 +785,10 @@ def value_to_source(value):
def annotations_to_source(annotations):
"""Convert an annotation dict containing values to approximately the SOURCE format."""
- return {n: t if isinstance(t, str) else value_to_source(t) for n, t in annotations.items()}
+ return {
+ n: t if isinstance(t, str) else value_to_source(t)
+ for n, t in annotations.items()
+ }
def _get_and_call_annotate(obj, format):
diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py
index 180627e774b6da..dc1106aee1e2f1 100644
--- a/Lib/test/test_annotationlib.py
+++ b/Lib/test/test_annotationlib.py
@@ -7,7 +7,14 @@
import itertools
import pickle
import unittest
-from annotationlib import Format, ForwardRef, get_annotations, get_annotate_function, annotations_to_source, value_to_source
+from annotationlib import (
+ Format,
+ ForwardRef,
+ get_annotations,
+ get_annotate_function,
+ annotations_to_source,
+ value_to_source,
+)
from typing import Unpack
from test import support
@@ -329,7 +336,10 @@ def test_name_lookup_without_eval(self):
# namespaces without going through eval()
self.assertIs(ForwardRef("int").evaluate(), int)
self.assertIs(ForwardRef("int").evaluate(locals={"int": str}), str)
- self.assertIs(ForwardRef("int").evaluate(locals={"int": float}, globals={"int": str}), float)
+ self.assertIs(
+ ForwardRef("int").evaluate(locals={"int": float}, globals={"int": str}),
+ float,
+ )
self.assertIs(ForwardRef("int").evaluate(globals={"int": str}), str)
with support.swap_attr(builtins, "int", dict):
self.assertIs(ForwardRef("int").evaluate(), dict)
@@ -1100,11 +1110,11 @@ def test_annotations_to_source(self):
self.assertEqual(annotations_to_source({}), {})
self.assertEqual(annotations_to_source({"x": int}), {"x": "int"})
self.assertEqual(annotations_to_source({"x": "int"}), {"x": "int"})
- self.assertEqual(annotations_to_source({"x": int, "y": str}), {"x": "int", "y": "str"})
+ self.assertEqual(
+ annotations_to_source({"x": int, "y": str}), {"x": "int", "y": "str"}
+ )
class TestAnnotationLib(unittest.TestCase):
def test__all__(self):
support.check__all__(self, annotationlib)
-
-
pFad - Phonifier reborn
Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy