From 50707baa74fd36b1bf02dc05d2510b9ecc5d8a93 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sat, 11 Feb 2023 14:54:17 +0000 Subject: [PATCH 01/37] Implement typing.get_orig_class and get_orig_bases --- Doc/library/typing.rst | 36 ++++++++++++++++++++++++++++++++ Lib/test/test_typing.py | 17 +++++++++++++++ Lib/typing.py | 46 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 0c23a233c0d7dd..9aa51827580d8a 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2328,6 +2328,42 @@ Introspection helpers .. versionadded:: 3.8 +.. function:: get_orig_bases(tp, /) + + Returns the objects in the bases list in the class's definition before + they were modified by ``__mro_entries__``. This is useful for + introspecting ``Generic``\s. + + Examples:: + + T = TypeVar("T") + + class Foo(Generic[T]): ... + + class Bar(Foo[int], float): ... + + get_orig_bases(Foo) == (Generic[T],) + get_orig_bases(Bar) == (Foo[int], float) + get_orig_bases(int) == None + +.. function:: get_orig_class(tp, /) + + Returns the ``GenericAlias`` object that was instanciated to create ``tp``. + + Examples:: + + T = TypeVar("T") + + class Foo(Generic[T]): ... + + get_orig_class(Foo[int]()) == Foo[int] + get_orig_class(list[int]()) == None + get_orig_class(int) == None + + .. warning:: + + This function will not work inside of the class initalisation process. + .. function:: is_typeddict(tp) Check if a type is a :class:`TypedDict`. diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b212b523048809..806565bc55f7d1 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4250,6 +4250,23 @@ class C(Generic[T]): pass (Concatenate[int, P], int)) self.assertEqual(get_args(list | str), (list, str)) + def test_get_orig_class(self): + T = TypeVar('T') + class C(Generic[T]): pass + self.assertEqual(get_orig_class(C[int]()), C[int]) + self.assertIsNone(get_orig_class(list[int]())) + self.assertIsNone(get_orig_class(int)) + + def test_get_orig_bases(self): + T = TypeVar('T') + class C(Generic[T]): pass + class D(C[int]): pass + class E(C[str], float): pass + self.assertEqual(get_orig_bases(C), (Generic[T],)) + self.assertEqual(get_orig_bases(D), (C[int],)) + self.assertEqual(get_orig_bases(int), None) + self.assertEqual(get_orig_bases(E), (C[str], float)) + class CollectionsAbcTests(BaseTestCase): diff --git a/Lib/typing.py b/Lib/typing.py index dd68e71db1558c..31d5e2d3a36d1d 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2273,6 +2273,52 @@ def get_args(tp): return () +@overload +def get_orig_bases(tp: type[object], /) -> tuple[type[Any], ...] | None: ... +@overload +def get_orig_bases(tp: Any, /) -> None: ... +def get_orig_bases(tp: Any, /) -> tuple[type[Any], ...] | None: + """Get the __orig_bases__ (see PEP 560) of a class. + + Examples:: + + class Foo(Generic[T]): ... + + class Bar(Foo[int]): ... + + get_orig_bases(Foo) == Generic[T] + get_orig_bases(Bar) == Foo[int] + get_orig_bases(int) == None + """ + if isisinstance(cls, type): + try: + return cls.__orig_bases__ + except AttributeError: + pass + return None + + +def get_orig_class(tp: Any, /) -> GenericAlias | None: + """Get the __orig_class__ for an instance of a Generic subclass. + + Examples:: + + class Foo(Generic[T]): ... + + get_orig_class(Foo[int]()) == Foo[int] + get_orig_class(list[int]()) == None + get_orig_class(int) == None + + Warning + ------- + This will always None in the __init__/__new__ methods of the class. + """ + try: + return cls.__orig_class__ + except AttributeError: + return None + + def is_typeddict(tp): """Check if an annotation is a TypedDict class From 4fef9f841c8483160a3f2bc4f1343f2763762b51 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 11 Feb 2023 15:01:34 +0000 Subject: [PATCH 02/37] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst diff --git a/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst b/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst new file mode 100644 index 00000000000000..a8aea700b8c3ee --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst @@ -0,0 +1 @@ +Implement :func:`typing.get_orig_bases` and :func:`typing.get_orig_class` to provide further introspection for types From 4cb193dded83f7869f9ae472e1d6915b3380d36d Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sat, 11 Feb 2023 19:51:15 +0000 Subject: [PATCH 03/37] Apparently my lsp wasn't working --- Lib/typing.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 31d5e2d3a36d1d..ff66e56f918ad5 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2273,11 +2273,7 @@ def get_args(tp): return () -@overload -def get_orig_bases(tp: type[object], /) -> tuple[type[Any], ...] | None: ... -@overload -def get_orig_bases(tp: Any, /) -> None: ... -def get_orig_bases(tp: Any, /) -> tuple[type[Any], ...] | None: +def get_orig_bases(tp: "Any", /) -> tuple[type["Any"], ...] | None: """Get the __orig_bases__ (see PEP 560) of a class. Examples:: @@ -2290,15 +2286,15 @@ class Bar(Foo[int]): ... get_orig_bases(Bar) == Foo[int] get_orig_bases(int) == None """ - if isisinstance(cls, type): + if isinstance(tp, type): try: - return cls.__orig_bases__ + return tp.__orig_bases__ except AttributeError: pass return None -def get_orig_class(tp: Any, /) -> GenericAlias | None: +def get_orig_class(tp: "Any", /) -> GenericAlias | None: """Get the __orig_class__ for an instance of a Generic subclass. Examples:: @@ -2314,7 +2310,7 @@ class Foo(Generic[T]): ... This will always None in the __init__/__new__ methods of the class. """ try: - return cls.__orig_class__ + return tp.__orig_class__ except AttributeError: return None From 44d3ae9694d34bbddf647437affee90fe401822f Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sat, 11 Feb 2023 19:55:35 +0000 Subject: [PATCH 04/37] Removing trailing whitespace --- Doc/library/typing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 9aa51827580d8a..ad778955624b1a 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2330,8 +2330,8 @@ Introspection helpers .. function:: get_orig_bases(tp, /) - Returns the objects in the bases list in the class's definition before - they were modified by ``__mro_entries__``. This is useful for + Returns the objects in the bases list in the class's definition before + they were modified by ``__mro_entries__``. This is useful for introspecting ``Generic``\s. Examples:: From 59e9ac263c253f44e12292d8c372f77fcc329198 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sat, 11 Feb 2023 20:13:03 +0000 Subject: [PATCH 05/37] Fix test failure --- Lib/test/test_typing.py | 2 +- Lib/typing.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 806565bc55f7d1..d8c016daab7b17 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -18,7 +18,7 @@ from typing import Generic, ClassVar, Final, final, Protocol from typing import cast, runtime_checkable from typing import get_type_hints -from typing import get_origin, get_args +from typing import get_origin, get_args, get_orig_bases, get_orig_class from typing import is_typeddict from typing import reveal_type from typing import no_type_check, no_type_check_decorator diff --git a/Lib/typing.py b/Lib/typing.py index ff66e56f918ad5..9d16b608725f1a 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -123,6 +123,8 @@ def _idfunc(_, x): 'final', 'get_args', 'get_origin', + 'get_orig_bases', + 'get_orig_class', 'get_type_hints', 'is_typeddict', 'Never', From 769fdbd3b80c74e544a318c8aa74459a916938a8 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sat, 11 Feb 2023 20:48:22 +0000 Subject: [PATCH 06/37] Fix typo --- Doc/library/typing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index fd553289c6bc39..d924184f79bbda 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2824,7 +2824,7 @@ Introspection helpers .. function:: get_orig_class(tp, /) - Returns the ``GenericAlias`` object that was instanciated to create ``tp``. + Returns the ``GenericAlias`` object that was instantiated to create ``tp``. Examples:: From 7194d3e2b53ead8ee23c811aa5db1d28772241e0 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sun, 12 Feb 2023 11:38:14 +0000 Subject: [PATCH 07/37] Respond to initial review --- Doc/library/typing.rst | 7 ++++++- Lib/test/test_typing.py | 11 ++++++++++- Lib/typing.py | 3 ++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index d924184f79bbda..ef51f494e6785d 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2822,6 +2822,8 @@ Introspection helpers get_orig_bases(Bar) == (Foo[int], float) get_orig_bases(int) == None + .. versionadded:: 3.12 + .. function:: get_orig_class(tp, /) Returns the ``GenericAlias`` object that was instantiated to create ``tp``. @@ -2838,7 +2840,10 @@ Introspection helpers .. warning:: - This function will not work inside of the class initalisation process. + This function will always return ``None`` inside of the class initalisation + process. + + .. versionadded:: 3.12 .. function:: is_typeddict(tp) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 221838124e36e0..4475655807439f 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -5358,18 +5358,27 @@ class C(Generic[T]): pass def test_get_orig_class(self): T = TypeVar('T') class C(Generic[T]): pass + class D(C[int]): ... + class E(C[str], float): pass self.assertEqual(get_orig_class(C[int]()), C[int]) + self.assertIsNone(get_orig_class(C())) + self.assertIsNone(get_orig_class(D())) + self.assertIsNone(get_orig_class(E())) self.assertIsNone(get_orig_class(list[int]())) self.assertIsNone(get_orig_class(int)) def test_get_orig_bases(self): T = TypeVar('T') + class A: pass + class B(object): pass class C(Generic[T]): pass class D(C[int]): pass class E(C[str], float): pass + self.assertIsNone(get_orig_bases(A)) + self.assertIsNone(get_orig_bases(B)) self.assertEqual(get_orig_bases(C), (Generic[T],)) self.assertEqual(get_orig_bases(D), (C[int],)) - self.assertEqual(get_orig_bases(int), None) + self.assertIsNone(get_orig_bases(int)) self.assertEqual(get_orig_bases(E), (C[str], float)) diff --git a/Lib/typing.py b/Lib/typing.py index 17750d26210a2b..4be2d06cded707 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2482,7 +2482,8 @@ class Foo(Generic[T]): ... Warning ------- - This will always None in the __init__/__new__ methods of the class. + This will always return None in the inside of the class initalisation + process. """ try: return tp.__orig_class__ From eeb4475c191f1c12fa3c0d3a2b3e8bf44b6afd88 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Fri, 17 Feb 2023 23:55:31 +0000 Subject: [PATCH 08/37] Remove trailing ws --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index 4be2d06cded707..cdc1d4f59527e8 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2482,7 +2482,7 @@ class Foo(Generic[T]): ... Warning ------- - This will always return None in the inside of the class initalisation + This will always return None in the inside of the class initalisation process. """ try: From 198dc08a9e557dcc9225c54cd176b4199a399e0f Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sat, 25 Feb 2023 16:43:40 +0000 Subject: [PATCH 09/37] Respond to second round of review --- Doc/library/types.rst | 22 +++++++++++++++++ Doc/library/typing.rst | 24 ++----------------- Lib/test/test_types.py | 14 +++++++++++ Lib/test/test_typing.py | 16 +------------ Lib/types.py | 22 +++++++++++++++++ Lib/typing.py | 22 ----------------- ...-02-11-15-01-32.gh-issue-101688.kwXmfM.rst | 2 +- 7 files changed, 62 insertions(+), 60 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index cce0ad960edf97..93539a8d5aef16 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -86,6 +86,28 @@ Dynamic Type Creation :pep:`560` - Core support for typing module and generic types +.. function:: get_orig_bases(tp, /) + + Returns the objects in the bases list in the class's definition before + they were modified by ``__mro_entries__``. This is useful for + introspecting ``Generic``\s. + + Examples:: + + from typing import TypeVar, Generic + + T = TypeVar("T") + + class Foo(Generic[T]): ... + + class Bar(Foo[int], float): ... + + get_orig_bases(Foo) == (Generic[T],) + get_orig_bases(Bar) == (Foo[int], float) + get_orig_bases(int) == None + + .. versionadded:: 3.12 + Standard Interpreter Types -------------------------- diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index ef51f494e6785d..28147d3ea04150 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2804,26 +2804,6 @@ Introspection helpers .. versionadded:: 3.8 -.. function:: get_orig_bases(tp, /) - - Returns the objects in the bases list in the class's definition before - they were modified by ``__mro_entries__``. This is useful for - introspecting ``Generic``\s. - - Examples:: - - T = TypeVar("T") - - class Foo(Generic[T]): ... - - class Bar(Foo[int], float): ... - - get_orig_bases(Foo) == (Generic[T],) - get_orig_bases(Bar) == (Foo[int], float) - get_orig_bases(int) == None - - .. versionadded:: 3.12 - .. function:: get_orig_class(tp, /) Returns the ``GenericAlias`` object that was instantiated to create ``tp``. @@ -2840,8 +2820,8 @@ Introspection helpers .. warning:: - This function will always return ``None`` inside of the class initalisation - process. + This function will always return ``None`` inside of the class's + instancation process. .. versionadded:: 3.12 diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index af095632a36fcb..d7dc635bba41b6 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1360,6 +1360,20 @@ class C: pass D = types.new_class('D', (A(), C, B()), {}) self.assertEqual(D.__bases__, (A1, A2, A3, C, B1, B2)) + def test_get_orig_bases(self): + T = typing.TypeVar('T') + class A: pass + class B(object): pass + class C(typing.Generic[T]): pass + class D(C[int]): pass + class E(C[str], float): pass + self.assertIsNone(types.get_orig_bases(A)) + self.assertIsNone(types.get_orig_bases(B)) + self.assertEqual(types.get_orig_bases(C), (typing.Generic[T],)) + self.assertEqual(types.get_orig_bases(D), (C[int],)) + self.assertIsNone(types.get_orig_bases(int)) + self.assertEqual(types.get_orig_bases(E), (C[str], float)) + # Many of the following tests are derived from test_descr.py def test_prepare_class(self): # Basic test of metaclass derivation diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 4475655807439f..ef87669e8931a6 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -22,7 +22,7 @@ from typing import Generic, ClassVar, Final, final, Protocol from typing import assert_type, cast, runtime_checkable from typing import get_type_hints -from typing import get_origin, get_args, get_orig_bases, get_orig_class +from typing import get_origin, get_args, get_orig_class from typing import is_typeddict from typing import reveal_type from typing import dataclass_transform @@ -5367,20 +5367,6 @@ class E(C[str], float): pass self.assertIsNone(get_orig_class(list[int]())) self.assertIsNone(get_orig_class(int)) - def test_get_orig_bases(self): - T = TypeVar('T') - class A: pass - class B(object): pass - class C(Generic[T]): pass - class D(C[int]): pass - class E(C[str], float): pass - self.assertIsNone(get_orig_bases(A)) - self.assertIsNone(get_orig_bases(B)) - self.assertEqual(get_orig_bases(C), (Generic[T],)) - self.assertEqual(get_orig_bases(D), (C[int],)) - self.assertIsNone(get_orig_bases(int)) - self.assertEqual(get_orig_bases(E), (C[str], float)) - class CollectionsAbcTests(BaseTestCase): diff --git a/Lib/types.py b/Lib/types.py index aa8a1c84722399..dec666cad613b0 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -143,6 +143,28 @@ def _calculate_meta(meta, bases): "of the metaclasses of all its bases") return winner + +def get_orig_bases(tp, /) -> tuple[type, ...] | None: + """Get the __orig_bases__ (see PEP 560) of a class. + + Examples:: + + class Foo(Generic[T]): ... + + class Bar(Foo[int]): ... + + get_orig_bases(Foo) == Generic[T] + get_orig_bases(Bar) == Foo[int] + get_orig_bases(int) == None + """ + if isinstance(tp, type): + try: + return tp.__orig_bases__ + except AttributeError: + pass + return None + + class DynamicClassAttribute: """Route attribute access on a class to __getattr__. diff --git a/Lib/typing.py b/Lib/typing.py index cdc1d4f59527e8..654e8e55d6335c 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -127,7 +127,6 @@ def _idfunc(_, x): 'final', 'get_args', 'get_origin', - 'get_orig_bases', 'get_orig_class', 'get_overloads', 'get_type_hints', @@ -2448,27 +2447,6 @@ def get_args(tp): return () -def get_orig_bases(tp: "Any", /) -> tuple[type["Any"], ...] | None: - """Get the __orig_bases__ (see PEP 560) of a class. - - Examples:: - - class Foo(Generic[T]): ... - - class Bar(Foo[int]): ... - - get_orig_bases(Foo) == Generic[T] - get_orig_bases(Bar) == Foo[int] - get_orig_bases(int) == None - """ - if isinstance(tp, type): - try: - return tp.__orig_bases__ - except AttributeError: - pass - return None - - def get_orig_class(tp: "Any", /) -> GenericAlias | None: """Get the __orig_class__ for an instance of a Generic subclass. diff --git a/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst b/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst index a8aea700b8c3ee..0d8c91d4ed702e 100644 --- a/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst +++ b/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst @@ -1 +1 @@ -Implement :func:`typing.get_orig_bases` and :func:`typing.get_orig_class` to provide further introspection for types +Implement :func:`types.get_orig_bases` and :func:`typing.get_orig_class` to provide further introspection for types. From 695cf93529a7c763c791f1029ab7446578215362 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sat, 25 Feb 2023 16:46:45 +0000 Subject: [PATCH 10/37] Fix typo --- Doc/library/typing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 28147d3ea04150..65a9cd5ecf8729 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2821,7 +2821,7 @@ Introspection helpers .. warning:: This function will always return ``None`` inside of the class's - instancation process. + instantiation process. .. versionadded:: 3.12 From cc110333b60bf1fc559656fd1713190a5aa86706 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Wed, 5 Apr 2023 11:14:24 +0100 Subject: [PATCH 11/37] Fix last few comments --- Doc/library/typing.rst | 5 +++-- Lib/typing.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 65a9cd5ecf8729..6d82a32c1798aa 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2804,7 +2804,7 @@ Introspection helpers .. versionadded:: 3.8 -.. function:: get_orig_class(tp, /) +.. function:: get_orig_class(obj, /) Returns the ``GenericAlias`` object that was instantiated to create ``tp``. @@ -2821,7 +2821,8 @@ Introspection helpers .. warning:: This function will always return ``None`` inside of the class's - instantiation process. + instantiation methods (e.g. ``__new__``, ``__init__`` and any + methods called from either of these) .. versionadded:: 3.12 diff --git a/Lib/typing.py b/Lib/typing.py index 654e8e55d6335c..f23687269cdd24 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2447,7 +2447,7 @@ def get_args(tp): return () -def get_orig_class(tp: "Any", /) -> GenericAlias | None: +def get_orig_class(obj: "Any", /) -> GenericAlias | None: """Get the __orig_class__ for an instance of a Generic subclass. Examples:: @@ -2464,7 +2464,7 @@ class Foo(Generic[T]): ... process. """ try: - return tp.__orig_class__ + return obj.__orig_class__ except AttributeError: return None From 581a91a62799fed168206452993a61095949bf66 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Thu, 6 Apr 2023 11:21:12 +0100 Subject: [PATCH 12/37] Apply suggestions from code review Co-authored-by: Alex Waygood --- Doc/library/types.rst | 2 +- .../Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index dee8c52c9679c0..819d07f7bbb886 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -88,7 +88,7 @@ Dynamic Type Creation .. function:: get_orig_bases(tp, /) - Returns the objects in the bases list in the class's definition before + Return the objects in the bases list in the class's definition before they were modified by ``__mro_entries__``. This is useful for introspecting ``Generic``\s. diff --git a/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst b/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst index 0d8c91d4ed702e..de0923c6c494d7 100644 --- a/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst +++ b/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst @@ -1 +1,2 @@ -Implement :func:`types.get_orig_bases` and :func:`typing.get_orig_class` to provide further introspection for types. +Implement :func:`types.get_orig_bases` and :func:`typing.get_orig_class` to +provide further introspection for types. From fb154279343ba8db82e07d27a0d4ed4ee974043e Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Thu, 6 Apr 2023 12:28:09 +0100 Subject: [PATCH 13/37] Remove typing.get_orig_class --- Doc/library/types.rst | 2 +- Doc/library/typing.rst | 22 ------------------ Lib/test/test_types.py | 19 +++++++++------ Lib/test/test_typing.py | 14 +---------- Lib/types.py | 15 ++++++++---- Lib/typing.py | 23 ------------------- ...-02-11-15-01-32.gh-issue-101688.kwXmfM.rst | 3 ++- 7 files changed, 27 insertions(+), 71 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index dee8c52c9679c0..06488a82546748 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -89,7 +89,7 @@ Dynamic Type Creation .. function:: get_orig_bases(tp, /) Returns the objects in the bases list in the class's definition before - they were modified by ``__mro_entries__``. This is useful for + they could have been modified by ``__mro_entries__``. This is useful for introspecting ``Generic``\s. Examples:: diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 82933012ba7a95..8728ca7b6b358c 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2874,28 +2874,6 @@ Introspection helpers .. versionadded:: 3.8 -.. function:: get_orig_class(obj, /) - - Returns the ``GenericAlias`` object that was instantiated to create ``tp``. - - Examples:: - - T = TypeVar("T") - - class Foo(Generic[T]): ... - - get_orig_class(Foo[int]()) == Foo[int] - get_orig_class(list[int]()) == None - get_orig_class(int) == None - - .. warning:: - - This function will always return ``None`` inside of the class's - instantiation methods (e.g. ``__new__``, ``__init__`` and any - methods called from either of these) - - .. versionadded:: 3.12 - .. function:: is_typeddict(tp) Check if a type is a :class:`TypedDict`. diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index d7dc635bba41b6..41e9d03bdb7504 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1363,16 +1363,21 @@ class C: pass def test_get_orig_bases(self): T = typing.TypeVar('T') class A: pass - class B(object): pass - class C(typing.Generic[T]): pass - class D(C[int]): pass - class E(C[str], float): pass + class B(typing.Generic[T]): pass + class C(B[int]): pass + class D(B[str], float): pass self.assertIsNone(types.get_orig_bases(A)) self.assertIsNone(types.get_orig_bases(B)) - self.assertEqual(types.get_orig_bases(C), (typing.Generic[T],)) - self.assertEqual(types.get_orig_bases(D), (C[int],)) + self.assertEqual(types.get_orig_bases(B), (typing.Generic[T],)) + self.assertEqual(types.get_orig_bases(C), (B[int],)) self.assertIsNone(types.get_orig_bases(int)) - self.assertEqual(types.get_orig_bases(E), (C[str], float)) + self.assertEqual(types.get_orig_bases(D), (B[str], float)) + + class E(list[T]): pass + class F(list[int]): pass + + self.assertEqual(types.get_orig_bases(E), (list[T],)) + self.assertEqual(types.get_orig_bases(F), (list[int],)) # Many of the following tests are derived from test_descr.py def test_prepare_class(self): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index da947082a30922..db026d48f4f49a 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -24,7 +24,7 @@ from typing import get_type_hints from typing import get_origin, get_args from typing import override -from typing import get_origin, get_args, get_orig_class +from typing import get_origin, get_args from typing import is_typeddict from typing import reveal_type from typing import dataclass_transform @@ -5768,18 +5768,6 @@ class C(Generic[T]): pass self.assertEqual(get_args((*tuple[*Ts],)[0]), (*Ts,)) self.assertEqual(get_args(Unpack[tuple[Unpack[Ts]]]), (tuple[Unpack[Ts]],)) - def test_get_orig_class(self): - T = TypeVar('T') - class C(Generic[T]): pass - class D(C[int]): ... - class E(C[str], float): pass - self.assertEqual(get_orig_class(C[int]()), C[int]) - self.assertIsNone(get_orig_class(C())) - self.assertIsNone(get_orig_class(D())) - self.assertIsNone(get_orig_class(E())) - self.assertIsNone(get_orig_class(list[int]())) - self.assertIsNone(get_orig_class(int)) - class CollectionsAbcTests(BaseTestCase): diff --git a/Lib/types.py b/Lib/types.py index dec666cad613b0..6909921a57466e 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -145,16 +145,23 @@ def _calculate_meta(meta, bases): def get_orig_bases(tp, /) -> tuple[type, ...] | None: - """Get the __orig_bases__ (see PEP 560) of a class. + r""" + Returns the objects in the bases list in the class's definition before + they could have been modified by ``__mro_entries__``. This is useful for + introspecting ``Generic``\s. Examples:: + from typing import TypeVar, Generic + + T = TypeVar("T") + class Foo(Generic[T]): ... - class Bar(Foo[int]): ... + class Bar(Foo[int], float): ... - get_orig_bases(Foo) == Generic[T] - get_orig_bases(Bar) == Foo[int] + get_orig_bases(Foo) == (Generic[T],) + get_orig_bases(Bar) == (Foo[int], float) get_orig_bases(int) == None """ if isinstance(tp, type): diff --git a/Lib/typing.py b/Lib/typing.py index 7f0fb1be29042b..442d684d14c928 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -127,7 +127,6 @@ def _idfunc(_, x): 'final', 'get_args', 'get_origin', - 'get_orig_class', 'get_overloads', 'get_type_hints', 'is_typeddict', @@ -2502,28 +2501,6 @@ def get_args(tp): return () -def get_orig_class(obj: "Any", /) -> GenericAlias | None: - """Get the __orig_class__ for an instance of a Generic subclass. - - Examples:: - - class Foo(Generic[T]): ... - - get_orig_class(Foo[int]()) == Foo[int] - get_orig_class(list[int]()) == None - get_orig_class(int) == None - - Warning - ------- - This will always return None in the inside of the class initalisation - process. - """ - try: - return obj.__orig_class__ - except AttributeError: - return None - - def is_typeddict(tp): """Check if an annotation is a TypedDict class diff --git a/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst b/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst index 0d8c91d4ed702e..3917656f7c0e19 100644 --- a/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst +++ b/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst @@ -1 +1,2 @@ -Implement :func:`types.get_orig_bases` and :func:`typing.get_orig_class` to provide further introspection for types. +Implement :func:`types.get_orig_bases` to provide further introspection for +types. From 6c38e4da8a33b08c1112ff55190b7c529749711b Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Thu, 6 Apr 2023 13:00:06 +0100 Subject: [PATCH 14/37] Remove old test --- Lib/test/test_types.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 41e9d03bdb7504..ecffed52404e3e 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1367,7 +1367,6 @@ class B(typing.Generic[T]): pass class C(B[int]): pass class D(B[str], float): pass self.assertIsNone(types.get_orig_bases(A)) - self.assertIsNone(types.get_orig_bases(B)) self.assertEqual(types.get_orig_bases(B), (typing.Generic[T],)) self.assertEqual(types.get_orig_bases(C), (B[int],)) self.assertIsNone(types.get_orig_bases(int)) From 372bd6b37faa4871389907d3d75adc13b2d9dadf Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Thu, 6 Apr 2023 15:30:08 +0100 Subject: [PATCH 15/37] Rename to get_original_bases --- Doc/library/types.rst | 8 +++--- Lib/test/test_types.py | 25 +++++++++---------- Lib/types.py | 9 ++++--- ...-02-11-15-01-32.gh-issue-101688.kwXmfM.rst | 4 +-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 19fb911c21d525..83db6d60e92113 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -86,7 +86,7 @@ Dynamic Type Creation :pep:`560` - Core support for typing module and generic types -.. function:: get_orig_bases(tp, /) +.. function:: get_original_bases(tp, /) Return the objects in the bases list in the class's definition before they could have been modified by ``__mro_entries__``. This is useful for @@ -102,9 +102,9 @@ Dynamic Type Creation class Bar(Foo[int], float): ... - get_orig_bases(Foo) == (Generic[T],) - get_orig_bases(Bar) == (Foo[int], float) - get_orig_bases(int) == None + get_original_bases(Foo) == (Generic[T],) + get_original_bases(Bar) == (Foo[int], float) + get_original_bases(int) == None .. versionadded:: 3.12 diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index ecffed52404e3e..f2485dcbbcbe29 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1,19 +1,18 @@ # Python test set -- part 6, built-in types -from test.support import run_with_locale, cpython_only import collections.abc -from collections import namedtuple import copy import gc import inspect -import pickle import locale +import pickle import sys import types +import typing import unittest.mock import weakref -import typing - +from collections import namedtuple +from test.support import cpython_only, run_with_locale T = typing.TypeVar("T") @@ -1360,23 +1359,23 @@ class C: pass D = types.new_class('D', (A(), C, B()), {}) self.assertEqual(D.__bases__, (A1, A2, A3, C, B1, B2)) - def test_get_orig_bases(self): + def test_get_original_bases(self): T = typing.TypeVar('T') class A: pass class B(typing.Generic[T]): pass class C(B[int]): pass class D(B[str], float): pass - self.assertIsNone(types.get_orig_bases(A)) - self.assertEqual(types.get_orig_bases(B), (typing.Generic[T],)) - self.assertEqual(types.get_orig_bases(C), (B[int],)) - self.assertIsNone(types.get_orig_bases(int)) - self.assertEqual(types.get_orig_bases(D), (B[str], float)) + self.assertIsNone(types.get_original_bases(A)) + self.assertEqual(types.get_original_bases(B), (typing.Generic[T],)) + self.assertEqual(types.get_original_bases(C), (B[int],)) + self.assertIsNone(types.get_original_bases(int)) + self.assertEqual(types.get_original_bases(D), (B[str], float)) class E(list[T]): pass class F(list[int]): pass - self.assertEqual(types.get_orig_bases(E), (list[T],)) - self.assertEqual(types.get_orig_bases(F), (list[int],)) + self.assertEqual(types.get_original_bases(E), (list[T],)) + self.assertEqual(types.get_original_bases(F), (list[int],)) # Many of the following tests are derived from test_descr.py def test_prepare_class(self): diff --git a/Lib/types.py b/Lib/types.py index 6909921a57466e..20c98532d816db 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -144,7 +144,7 @@ def _calculate_meta(meta, bases): return winner -def get_orig_bases(tp, /) -> tuple[type, ...] | None: +def get_original_bases(tp, /) -> tuple[type, ...] | None: r""" Returns the objects in the bases list in the class's definition before they could have been modified by ``__mro_entries__``. This is useful for @@ -160,9 +160,9 @@ class Foo(Generic[T]): ... class Bar(Foo[int], float): ... - get_orig_bases(Foo) == (Generic[T],) - get_orig_bases(Bar) == (Foo[int], float) - get_orig_bases(int) == None + get_original_bases(Foo) == (Generic[T],) + get_original_bases(Bar) == (Foo[int], float) + get_original_bases(int) == None """ if isinstance(tp, type): try: @@ -303,6 +303,7 @@ def coroutine(func): # Delay functools and _collections_abc import for speeding up types import. import functools + import _collections_abc @functools.wraps(func) def wrapped(*args, **kwargs): diff --git a/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst b/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst index 3917656f7c0e19..6df69463931494 100644 --- a/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst +++ b/Misc/NEWS.d/next/Library/2023-02-11-15-01-32.gh-issue-101688.kwXmfM.rst @@ -1,2 +1,2 @@ -Implement :func:`types.get_orig_bases` to provide further introspection for -types. +Implement :func:`types.get_original_bases` to provide further introspection +for types. From a6fe8ac6f17cca3a2a1a7efbfbcd7c1e9ac25588 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Thu, 6 Apr 2023 15:57:40 +0100 Subject: [PATCH 16/37] Autoformatting is fun --- Lib/test/test_types.py | 8 ++++---- Lib/test/test_typing.py | 1 - Lib/types.py | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index f2485dcbbcbe29..9c790edc078681 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1,18 +1,18 @@ # Python test set -- part 6, built-in types +from test.support import run_with_locale, cpython_only import collections.abc +from collections import namedtuple import copy import gc import inspect -import locale import pickle +import locale import sys import types -import typing import unittest.mock import weakref -from collections import namedtuple -from test.support import cpython_only, run_with_locale +import typing T = typing.TypeVar("T") diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index db026d48f4f49a..7d2e6a6a9f6287 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -24,7 +24,6 @@ from typing import get_type_hints from typing import get_origin, get_args from typing import override -from typing import get_origin, get_args from typing import is_typeddict from typing import reveal_type from typing import dataclass_transform diff --git a/Lib/types.py b/Lib/types.py index 20c98532d816db..dff70989732f2c 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -303,7 +303,6 @@ def coroutine(func): # Delay functools and _collections_abc import for speeding up types import. import functools - import _collections_abc @functools.wraps(func) def wrapped(*args, **kwargs): From 2b71b7494408f0bd4f4aff697e754843bad09c2b Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Thu, 6 Apr 2023 16:27:49 +0100 Subject: [PATCH 17/37] Fix more suggestions --- Doc/library/types.rst | 2 +- Lib/test/test_types.py | 1 + Lib/types.py | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 83db6d60e92113..d480dbe0f13959 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -86,7 +86,7 @@ Dynamic Type Creation :pep:`560` - Core support for typing module and generic types -.. function:: get_original_bases(tp, /) +.. function:: get_original_bases(cls, /) Return the objects in the bases list in the class's definition before they could have been modified by ``__mro_entries__``. This is useful for diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 9c790edc078681..28f8be890494d2 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -14,6 +14,7 @@ import weakref import typing + T = typing.TypeVar("T") class Example: diff --git a/Lib/types.py b/Lib/types.py index dff70989732f2c..7d212e23f4268a 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -144,9 +144,9 @@ def _calculate_meta(meta, bases): return winner -def get_original_bases(tp, /) -> tuple[type, ...] | None: +def get_original_bases(cls, /) -> tuple[type, ...] | None: r""" - Returns the objects in the bases list in the class's definition before + Return the objects in the bases list in the class's definition before they could have been modified by ``__mro_entries__``. This is useful for introspecting ``Generic``\s. @@ -164,9 +164,9 @@ class Bar(Foo[int], float): ... get_original_bases(Bar) == (Foo[int], float) get_original_bases(int) == None """ - if isinstance(tp, type): + if isinstance(cls, type): try: - return tp.__orig_bases__ + return cls.__orig_bases__ except AttributeError: pass return None From e7635c6f6539c675ccac802e9cb8dba54364f4ef Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Thu, 6 Apr 2023 16:35:18 +0100 Subject: [PATCH 18/37] Remove rawstring --- Lib/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/types.py b/Lib/types.py index 7d212e23f4268a..cef164510958d1 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -145,10 +145,10 @@ def _calculate_meta(meta, bases): def get_original_bases(cls, /) -> tuple[type, ...] | None: - r""" + """ Return the objects in the bases list in the class's definition before they could have been modified by ``__mro_entries__``. This is useful for - introspecting ``Generic``\s. + introspecting Generics. Examples:: From 42e1668ca401c7380421684f60c369b3979232bb Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Fri, 7 Apr 2023 18:57:45 +0100 Subject: [PATCH 19/37] Apply suggestions from code review Co-authored-by: Alex Waygood --- Doc/library/types.rst | 12 +++++++----- Lib/test/test_types.py | 5 +++++ Lib/types.py | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index d480dbe0f13959..13a8dee823e748 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -88,22 +88,24 @@ Dynamic Type Creation .. function:: get_original_bases(cls, /) - Return the objects in the bases list in the class's definition before - they could have been modified by ``__mro_entries__``. This is useful for - introspecting ``Generic``\s. + Return the objects in the bases list of the class's definition as they + existed prior to any modification by :meth:`~object.__mro_entries__`. This + is useful for introspecting :ref:`Generics `. Examples:: from typing import TypeVar, Generic T = TypeVar("T") - class Foo(Generic[T]): ... - class Bar(Foo[int], float): ... + Foo.__bases__ == (Generic,) get_original_bases(Foo) == (Generic[T],) + + Bar.__bases__ == (Foo, float) get_original_bases(Bar) == (Foo[int], float) + get_original_bases(int) == None .. versionadded:: 3.12 diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 28f8be890494d2..7258092b7dab55 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1378,6 +1378,11 @@ class F(list[int]): pass self.assertEqual(types.get_original_bases(E), (list[T],)) self.assertEqual(types.get_original_bases(F), (list[int],)) +class G(typing.NamedTuple): + x: int + +self.assertIs(types.get_original_bases(G)[0], typing.NamedTuple) + # Many of the following tests are derived from test_descr.py def test_prepare_class(self): # Basic test of metaclass derivation diff --git a/Lib/types.py b/Lib/types.py index cef164510958d1..a43476a767c856 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -144,7 +144,7 @@ def _calculate_meta(meta, bases): return winner -def get_original_bases(cls, /) -> tuple[type, ...] | None: +def get_original_bases(cls, /): """ Return the objects in the bases list in the class's definition before they could have been modified by ``__mro_entries__``. This is useful for From 1040478ebea9cdb0cbf3453ea49c41839733793b Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sat, 8 Apr 2023 12:58:31 +0100 Subject: [PATCH 20/37] Fix `test_types` failures --- Lib/test/test_types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 7258092b7dab55..e4d5d1c9155a39 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1378,10 +1378,10 @@ class F(list[int]): pass self.assertEqual(types.get_original_bases(E), (list[T],)) self.assertEqual(types.get_original_bases(F), (list[int],)) -class G(typing.NamedTuple): - x: int + class G(typing.NamedTuple): + x: int -self.assertIs(types.get_original_bases(G)[0], typing.NamedTuple) + self.assertIs(types.get_original_bases(G)[0], typing.NamedTuple) # Many of the following tests are derived from test_descr.py def test_prepare_class(self): From 8085258078c39c1171038940b7977d10593a12c3 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sat, 8 Apr 2023 13:52:23 +0100 Subject: [PATCH 21/37] Apply suggestions from code review Co-authored-by: Alex Waygood --- Lib/types.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Lib/types.py b/Lib/types.py index a43476a767c856..4a3b1d4695259d 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -145,19 +145,16 @@ def _calculate_meta(meta, bases): def get_original_bases(cls, /): - """ - Return the objects in the bases list in the class's definition before - they could have been modified by ``__mro_entries__``. This is useful for - introspecting Generics. + """Return the class's "original" bases prior to modification by `__mro_entries__`. + + This is useful for introspecting Generics. Examples:: from typing import TypeVar, Generic T = TypeVar("T") - class Foo(Generic[T]): ... - class Bar(Foo[int], float): ... get_original_bases(Foo) == (Generic[T],) From b9bf4fde3b94d93f3b861c5e242a010fd6555c43 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sat, 8 Apr 2023 14:17:41 +0100 Subject: [PATCH 22/37] Add a whatsnew entry --- Doc/whatsnew/3.12.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 23524ec5d7d452..4a91a3351531a0 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -379,6 +379,13 @@ threading profiling functions in all running threads in addition to the calling one. (Contributed by Pablo Galindo in :gh:`93503`.) +types +----- + +* Add :func:`types.get_original_bases` to allow for further introspection of + :ref:`user-defined-generics` when subclassed. (Contributed by + James Hilton-Balfe and Alex Waygood in :gh:`101827`.) + unicodedata ----------- From ee59cbdccb77acae867009f22ae7fa43dfa586a1 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sat, 8 Apr 2023 14:17:58 +0100 Subject: [PATCH 23/37] Update Lib/types.py Co-authored-by: Alex Waygood --- Lib/types.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/types.py b/Lib/types.py index 4a3b1d4695259d..34da95631495dc 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -156,9 +156,11 @@ def get_original_bases(cls, /): T = TypeVar("T") class Foo(Generic[T]): ... class Bar(Foo[int], float): ... + class Baz(list[str]): ... get_original_bases(Foo) == (Generic[T],) get_original_bases(Bar) == (Foo[int], float) + get_original_bases(Baz) == (list[str],) get_original_bases(int) == None """ if isinstance(cls, type): From e5a91a51cd5e757653027d19665f304ac55c40a2 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sat, 8 Apr 2023 14:18:47 +0100 Subject: [PATCH 24/37] Update Doc/library/types.rst Co-authored-by: Alex Waygood --- Doc/library/types.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 13a8dee823e748..783cdfcdcd2518 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -99,12 +99,16 @@ Dynamic Type Creation T = TypeVar("T") class Foo(Generic[T]): ... class Bar(Foo[int], float): ... + class Baz(list[str]): ... Foo.__bases__ == (Generic,) get_original_bases(Foo) == (Generic[T],) Bar.__bases__ == (Foo, float) get_original_bases(Bar) == (Foo[int], float) + + Baz.__bases__ == (list,) + get_original_bases(Baz) == (list[str],) get_original_bases(int) == None From 1ea82be4a643930dcf3ec0d9a1f833fb7a3f8fb1 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sun, 9 Apr 2023 11:01:50 +0100 Subject: [PATCH 25/37] Remove trailing WS --- Doc/library/types.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 783cdfcdcd2518..93748b8ab101f1 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -106,7 +106,7 @@ Dynamic Type Creation Bar.__bases__ == (Foo, float) get_original_bases(Bar) == (Foo[int], float) - + Baz.__bases__ == (list,) get_original_bases(Baz) == (list[str],) From 1cb14c2e767871453158c8b3079105f80ae64d77 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sun, 9 Apr 2023 12:10:48 +0100 Subject: [PATCH 26/37] Fix more trailing WS --- Lib/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/types.py b/Lib/types.py index 34da95631495dc..84b4dd0b73b97e 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -146,7 +146,7 @@ def _calculate_meta(meta, bases): def get_original_bases(cls, /): """Return the class's "original" bases prior to modification by `__mro_entries__`. - + This is useful for introspecting Generics. Examples:: From 6f06c526e8546df29a98e2c20033d14066835823 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sun, 9 Apr 2023 19:37:16 +0100 Subject: [PATCH 27/37] Fallback to __bases__ --- Lib/test/test_types.py | 7 +++++-- Lib/types.py | 11 +++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index e4d5d1c9155a39..0a0417f6989eae 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1366,10 +1366,10 @@ class A: pass class B(typing.Generic[T]): pass class C(B[int]): pass class D(B[str], float): pass - self.assertIsNone(types.get_original_bases(A)) + self.assertEqual(types.get_original_bases(A), (object,)) self.assertEqual(types.get_original_bases(B), (typing.Generic[T],)) self.assertEqual(types.get_original_bases(C), (B[int],)) - self.assertIsNone(types.get_original_bases(int)) + self.assertEqual(types.get_original_bases(int), (object,)) self.assertEqual(types.get_original_bases(D), (B[str], float)) class E(list[T]): pass @@ -1383,6 +1383,9 @@ class G(typing.NamedTuple): self.assertIs(types.get_original_bases(G)[0], typing.NamedTuple) + with self.assertRaises(TypeError): + types.get_original_bases(object()) + # Many of the following tests are derived from test_descr.py def test_prepare_class(self): # Basic test of metaclass derivation diff --git a/Lib/types.py b/Lib/types.py index 84b4dd0b73b97e..c2baa216495dae 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -163,12 +163,15 @@ class Baz(list[str]): ... get_original_bases(Baz) == (list[str],) get_original_bases(int) == None """ - if isinstance(cls, type): + try: + return cls.__orig_bases__ + except AttributeError: try: - return cls.__orig_bases__ + return cls.__bases__ except AttributeError: - pass - return None + raise TypeError( + f'Expected an instance of type, not {type(cls).__name__!r}' + ) from None class DynamicClassAttribute: From 689267a9b57ca9d6488b528e26e32dd60f4da33e Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sun, 9 Apr 2023 19:40:36 +0100 Subject: [PATCH 28/37] Update missing docs --- Doc/library/types.rst | 2 +- Lib/types.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 93748b8ab101f1..3b029860f62533 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -110,7 +110,7 @@ Dynamic Type Creation Baz.__bases__ == (list,) get_original_bases(Baz) == (list[str],) - get_original_bases(int) == None + get_original_bases(int) == (object,) .. versionadded:: 3.12 diff --git a/Lib/types.py b/Lib/types.py index c2baa216495dae..0ad1bbfd954954 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -161,7 +161,7 @@ class Baz(list[str]): ... get_original_bases(Foo) == (Generic[T],) get_original_bases(Bar) == (Foo[int], float) get_original_bases(Baz) == (list[str],) - get_original_bases(int) == None + get_original_bases(int) == (object,) """ try: return cls.__orig_bases__ From 1ae16eaefd3edb2baaadae4d7841cb264436a601 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sun, 9 Apr 2023 23:12:19 +0100 Subject: [PATCH 29/37] Apply suggestions from code review Co-authored-by: Alex Waygood --- Lib/test/test_types.py | 2 +- Lib/types.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 0a0417f6989eae..9a0e1e41ef8ec9 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1383,7 +1383,7 @@ class G(typing.NamedTuple): self.assertIs(types.get_original_bases(G)[0], typing.NamedTuple) - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "Expected an instance of type"): types.get_original_bases(object()) # Many of the following tests are derived from test_descr.py diff --git a/Lib/types.py b/Lib/types.py index 0ad1bbfd954954..c559a216fa6520 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -147,8 +147,6 @@ def _calculate_meta(meta, bases): def get_original_bases(cls, /): """Return the class's "original" bases prior to modification by `__mro_entries__`. - This is useful for introspecting Generics. - Examples:: from typing import TypeVar, Generic From 3a8619a08705ad166ca1f4b1497df92c19f0e701 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sun, 9 Apr 2023 23:17:57 +0100 Subject: [PATCH 30/37] Update Doc/library/types.rst Co-authored-by: Alex Waygood --- Doc/library/types.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 3b029860f62533..38b67ed1849a2e 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -110,6 +110,7 @@ Dynamic Type Creation Baz.__bases__ == (list,) get_original_bases(Baz) == (list[str],) + int.__bases__ == (object,) get_original_bases(int) == (object,) .. versionadded:: 3.12 From bab8cb3132093d738edb42936c30506028af4ed7 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Tue, 11 Apr 2023 12:31:58 +0100 Subject: [PATCH 31/37] Small tweaks to docs --- Doc/library/types.rst | 11 ++++------- Doc/reference/datamodel.rst | 4 ++++ Lib/types.py | 1 - 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 4feebbf6c472c2..913c6c97f9bd66 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -82,10 +82,6 @@ Dynamic Type Creation .. versionadded:: 3.7 -.. seealso:: - - :pep:`560` - Core support for typing module and generic types - .. function:: get_original_bases(cls, /) Return the objects in the bases list of the class's definition as they @@ -101,9 +97,6 @@ Dynamic Type Creation class Bar(Foo[int], float): ... class Baz(list[str]): ... - Foo.__bases__ == (Generic,) - get_original_bases(Foo) == (Generic[T],) - Bar.__bases__ == (Foo, float) get_original_bases(Bar) == (Foo[int], float) @@ -115,6 +108,10 @@ Dynamic Type Creation .. versionadded:: 3.12 +.. seealso:: + + :pep:`560` - Core support for typing module and generic types + Standard Interpreter Types -------------------------- diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 9f91ade35e50dc..55431f1951e50d 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2102,6 +2102,10 @@ Resolving MRO entries :func:`types.resolve_bases` Dynamically resolve bases that are not instances of :class:`type`. + :func:`types.get_original_bases` + Retrieve a class's "original bases" prior to modifications by + :meth:`~object.__mro_entries__`. + :pep:`560` Core support for typing module and generic types. diff --git a/Lib/types.py b/Lib/types.py index c559a216fa6520..77588466fef78f 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -156,7 +156,6 @@ class Foo(Generic[T]): ... class Bar(Foo[int], float): ... class Baz(list[str]): ... - get_original_bases(Foo) == (Generic[T],) get_original_bases(Bar) == (Foo[int], float) get_original_bases(Baz) == (list[str],) get_original_bases(int) == (object,) From fb9ef702b777ea78d6c14fcc0b44e5ab4ba85a06 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Wed, 12 Apr 2023 16:47:35 +0100 Subject: [PATCH 32/37] Use `assert` in docs examples --- Doc/library/types.rst | 12 ++++++------ Lib/types.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 913c6c97f9bd66..02f32a9b62fecb 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -97,14 +97,14 @@ Dynamic Type Creation class Bar(Foo[int], float): ... class Baz(list[str]): ... - Bar.__bases__ == (Foo, float) - get_original_bases(Bar) == (Foo[int], float) + assert Bar.__bases__ == (Foo, float) + assert get_original_bases(Bar) == (Foo[int], float) - Baz.__bases__ == (list,) - get_original_bases(Baz) == (list[str],) + assert Baz.__bases__ == (list,) + assert get_original_bases(Baz) == (list[str],) - int.__bases__ == (object,) - get_original_bases(int) == (object,) + assert int.__bases__ == (object,) + assert get_original_bases(int) == (object,) .. versionadded:: 3.12 diff --git a/Lib/types.py b/Lib/types.py index 77588466fef78f..407bdf7c7c53bd 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -156,9 +156,9 @@ class Foo(Generic[T]): ... class Bar(Foo[int], float): ... class Baz(list[str]): ... - get_original_bases(Bar) == (Foo[int], float) - get_original_bases(Baz) == (list[str],) - get_original_bases(int) == (object,) + assert get_original_bases(Bar) == (Foo[int], float) + assert get_original_bases(Baz) == (list[str],) + assert get_original_bases(int) == (object,) """ try: return cls.__orig_bases__ From dc433c44764927e49392a21125627f63b58f6db8 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Wed, 12 Apr 2023 20:45:09 +0100 Subject: [PATCH 33/37] Document type.__orig_bases__ --- Doc/reference/datamodel.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 55431f1951e50d..7e9e08ad38a2cf 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2097,6 +2097,12 @@ Resolving MRO entries of classes that will be used instead of the base. The returned tuple may be empty: in these cases, the original base is ignored. + +.. attribute:: type.__orig_bases__ + + If the class originally had bases modified by :meth:`~object.__mro_entries__`, + this the *bases* tuple. + .. seealso:: :func:`types.resolve_bases` From 4268b74d913cab443233f411328e55c79cbdd2e9 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Wed, 12 Apr 2023 22:04:52 +0100 Subject: [PATCH 34/37] Revert "Document type.__orig_bases__" This reverts commit dc433c44764927e49392a21125627f63b58f6db8. --- Doc/reference/datamodel.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 7e9e08ad38a2cf..55431f1951e50d 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2097,12 +2097,6 @@ Resolving MRO entries of classes that will be used instead of the base. The returned tuple may be empty: in these cases, the original base is ignored. - -.. attribute:: type.__orig_bases__ - - If the class originally had bases modified by :meth:`~object.__mro_entries__`, - this the *bases* tuple. - .. seealso:: :func:`types.resolve_bases` From 9f54ac1f66bf6807b48dd34cd729611f8557d380 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Thu, 20 Apr 2023 00:45:00 +0100 Subject: [PATCH 35/37] Update Doc/library/types.rst Co-authored-by: Alex Waygood --- Doc/library/types.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 02f32a9b62fecb..41e8670c83dd46 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -84,9 +84,13 @@ Dynamic Type Creation .. function:: get_original_bases(cls, /) - Return the objects in the bases list of the class's definition as they - existed prior to any modification by :meth:`~object.__mro_entries__`. This - is useful for introspecting :ref:`Generics `. + Return the tuple of objects originally given as the bases of *cls* before + the :meth:`~object.__mro_entries__` method has been called on any bases + (following the mechanisms laid out in :pep:`560`). This is useful for + introspecting :ref:`Generics `. + + For classes that have an ``__orig_bases__`` attribute, this + function simply returns the value of ``cls.__orig_bases__``. Examples:: From c122b2361f3c5ba459e879ce342ed8cd02ee6229 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sat, 22 Apr 2023 17:41:12 -0600 Subject: [PATCH 36/37] Hone docs more --- Doc/library/types.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 41e8670c83dd46..385ee85d1592ac 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -90,7 +90,9 @@ Dynamic Type Creation introspecting :ref:`Generics `. For classes that have an ``__orig_bases__`` attribute, this - function simply returns the value of ``cls.__orig_bases__``. + function returns the value of ``cls.__orig_bases__``. + For classes without the ``__orig_bases__`` attribute, ``cls.__bases__`` is + returned. Examples:: From cb21a657ebfdb965594dc4b820a7bed9abfd1b77 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sun, 23 Apr 2023 12:22:39 -0600 Subject: [PATCH 37/37] More tests and docs following #103698 --- Doc/library/types.rst | 10 +++++++++- Lib/test/test_types.py | 39 +++++++++++++++++++++++++++++++++++++-- Lib/types.py | 6 +++++- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 385ee85d1592ac..54887f4c51983a 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -96,12 +96,14 @@ Dynamic Type Creation Examples:: - from typing import TypeVar, Generic + from typing import TypeVar, Generic, NamedTuple, TypedDict T = TypeVar("T") class Foo(Generic[T]): ... class Bar(Foo[int], float): ... class Baz(list[str]): ... + Eggs = NamedTuple("Eggs", [("a", int), ("b", str)]) + Spam = TypedDict("Spam", {"a": int, "b": str}) assert Bar.__bases__ == (Foo, float) assert get_original_bases(Bar) == (Foo[int], float) @@ -109,6 +111,12 @@ Dynamic Type Creation assert Baz.__bases__ == (list,) assert get_original_bases(Baz) == (list[str],) + assert Eggs.__bases__ == (tuple,) + assert get_original_bases(Eggs) == (NamedTuple,) + + assert Spam.__bases__ == (dict,) + assert get_original_bases(Spam) == (TypedDict,) + assert int.__bases__ == (object,) assert get_original_bases(int) == (object,) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 9a0e1e41ef8ec9..9fe5812a14e15d 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1378,10 +1378,45 @@ class F(list[int]): pass self.assertEqual(types.get_original_bases(E), (list[T],)) self.assertEqual(types.get_original_bases(F), (list[int],)) - class G(typing.NamedTuple): + class ClassBasedNamedTuple(typing.NamedTuple): x: int - self.assertIs(types.get_original_bases(G)[0], typing.NamedTuple) + class GenericNamedTuple(typing.NamedTuple, typing.Generic[T]): + x: T + + CallBasedNamedTuple = typing.NamedTuple("CallBasedNamedTuple", [("x", int)]) + + self.assertIs( + types.get_original_bases(ClassBasedNamedTuple)[0], typing.NamedTuple + ) + self.assertEqual( + types.get_original_bases(GenericNamedTuple), + (typing.NamedTuple, typing.Generic[T]) + ) + self.assertIs( + types.get_original_bases(CallBasedNamedTuple)[0], typing.NamedTuple + ) + + class ClassBasedTypedDict(typing.TypedDict): + x: int + + class GenericTypedDict(typing.TypedDict, typing.Generic[T]): + x: T + + CallBasedTypedDict = typing.TypedDict("CallBasedTypedDict", {"x": int}) + + self.assertIs( + types.get_original_bases(ClassBasedTypedDict)[0], + typing.TypedDict + ) + self.assertEqual( + types.get_original_bases(GenericTypedDict), + (typing.TypedDict, typing.Generic[T]) + ) + self.assertIs( + types.get_original_bases(CallBasedTypedDict)[0], + typing.TypedDict + ) with self.assertRaisesRegex(TypeError, "Expected an instance of type"): types.get_original_bases(object()) diff --git a/Lib/types.py b/Lib/types.py index 407bdf7c7c53bd..6110e6e1de7249 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -149,15 +149,19 @@ def get_original_bases(cls, /): Examples:: - from typing import TypeVar, Generic + from typing import TypeVar, Generic, NamedTuple, TypedDict T = TypeVar("T") class Foo(Generic[T]): ... class Bar(Foo[int], float): ... class Baz(list[str]): ... + Eggs = NamedTuple("Eggs", [("a", int), ("b", str)]) + Spam = TypedDict("Spam", {"a": int, "b": str}) assert get_original_bases(Bar) == (Foo[int], float) assert get_original_bases(Baz) == (list[str],) + assert get_original_bases(Eggs) == (NamedTuple,) + assert get_original_bases(Spam) == (TypedDict,) assert get_original_bases(int) == (object,) """ try: