Skip to content
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

gh-101688: Implement types.get_original_bases #101827

Merged
merged 45 commits into from
Apr 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
50707ba
Implement typing.get_orig_class and get_orig_bases
Gobot1234 Feb 11, 2023
4fef9f8
📜🤖 Added by blurb_it.
blurb-it[bot] Feb 11, 2023
4cb193d
Apparently my lsp wasn't working
Gobot1234 Feb 11, 2023
44d3ae9
Removing trailing whitespace
Gobot1234 Feb 11, 2023
59e9ac2
Fix test failure
Gobot1234 Feb 11, 2023
20024e9
Merge branch 'main' into orig_class-and-bases
Gobot1234 Feb 11, 2023
769fdbd
Fix typo
Gobot1234 Feb 11, 2023
7194d3e
Respond to initial review
Gobot1234 Feb 12, 2023
eeb4475
Remove trailing ws
Gobot1234 Feb 17, 2023
198dc08
Respond to second round of review
Gobot1234 Feb 25, 2023
695cf93
Fix typo
Gobot1234 Feb 25, 2023
cc11033
Fix last few comments
Gobot1234 Apr 5, 2023
a66282b
Merge remote-tracking branch 'upstream/main' into orig_class-and-bases
Gobot1234 Apr 5, 2023
d3768ba
Merge branch 'main' into orig_class-and-bases
Gobot1234 Apr 5, 2023
581a91a
Apply suggestions from code review
Gobot1234 Apr 6, 2023
fb15427
Remove typing.get_orig_class
Gobot1234 Apr 6, 2023
96b9536
Merge remote-tracking branch 'origin/orig_class-and-bases' into orig_…
Gobot1234 Apr 6, 2023
6c38e4d
Remove old test
Gobot1234 Apr 6, 2023
372bd6b
Rename to get_original_bases
Gobot1234 Apr 6, 2023
a6fe8ac
Autoformatting is fun
Gobot1234 Apr 6, 2023
2b71b74
Fix more suggestions
Gobot1234 Apr 6, 2023
e7635c6
Remove rawstring
Gobot1234 Apr 6, 2023
42e1668
Apply suggestions from code review
Gobot1234 Apr 7, 2023
1040478
Fix `test_types` failures
AlexWaygood Apr 8, 2023
8085258
Apply suggestions from code review
Gobot1234 Apr 8, 2023
b9bf4fd
Add a whatsnew entry
Gobot1234 Apr 8, 2023
ee59cbd
Update Lib/types.py
Gobot1234 Apr 8, 2023
e5a91a5
Update Doc/library/types.rst
Gobot1234 Apr 8, 2023
7bad429
Merge branch 'main' into orig_class-and-bases
AlexWaygood Apr 8, 2023
2a39055
Merge branch 'main' into orig_class-and-bases
Gobot1234 Apr 9, 2023
1ea82be
Remove trailing WS
Gobot1234 Apr 9, 2023
1cb14c2
Fix more trailing WS
Gobot1234 Apr 9, 2023
6f06c52
Fallback to __bases__
Gobot1234 Apr 9, 2023
689267a
Update missing docs
Gobot1234 Apr 9, 2023
1ae16ea
Apply suggestions from code review
Gobot1234 Apr 9, 2023
3a8619a
Update Doc/library/types.rst
Gobot1234 Apr 9, 2023
e1d55d6
Merge remote-tracking branch 'upstream/main' into orig_class-and-bases
AlexWaygood Apr 11, 2023
bab8cb3
Small tweaks to docs
AlexWaygood Apr 11, 2023
fb9ef70
Use `assert` in docs examples
AlexWaygood Apr 12, 2023
dc433c4
Document type.__orig_bases__
Gobot1234 Apr 12, 2023
4268b74
Revert "Document type.__orig_bases__"
Gobot1234 Apr 12, 2023
9f54ac1
Update Doc/library/types.rst
Gobot1234 Apr 19, 2023
c122b23
Hone docs more
AlexWaygood Apr 22, 2023
2134053
Merge remote-tracking branch 'upstream/main' into orig_class-and-bases
AlexWaygood Apr 23, 2023
cb21a65
More tests and docs following #103698
AlexWaygood Apr 23, 2023
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
40 changes: 40 additions & 0 deletions Doc/library/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,46 @@ Dynamic Type Creation

.. versionadded:: 3.7

.. function:: get_original_bases(cls, /)

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 <user-defined-generics>`.

For classes that have an ``__orig_bases__`` attribute, this
function returns the value of ``cls.__orig_bases__``.
For classes without the ``__orig_bases__`` attribute, ``cls.__bases__`` is
returned.

Examples::

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)

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,)

.. versionadded:: 3.12

.. seealso::

:pep:`560` - Core support for typing module and generic types
Expand Down
4 changes: 4 additions & 0 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
an exception with a ':keyword:`try`...\ :keyword:`except`' statement may keep
objects alive.

Some objects contain references to "external" resources such as open files or

Check warning on line 87 in Doc/reference/datamodel.rst

View workflow job for this annotation

GitHub Actions / Docs

py:meth reference target not found: close
windows. It is understood that these resources are freed when the object is
garbage-collected, but since garbage collection is not guaranteed to happen,
such objects also provide an explicit way to release the external resource,
Expand Down Expand Up @@ -404,7 +404,7 @@
Sets
.. index:: object: set

These represent a mutable set. They are created by the built-in :func:`set`

Check warning on line 407 in Doc/reference/datamodel.rst

View workflow job for this annotation

GitHub Actions / Docs

py:meth reference target not found: set.add
constructor and can be modified afterwards by several methods, such as
:meth:`~set.add`.

Expand Down Expand Up @@ -505,7 +505,7 @@
| Attribute | Meaning | |
+=========================+===============================+===========+
| :attr:`__doc__` | The function's documentation | Writable |
| | string, or ``None`` if | |

Check warning on line 508 in Doc/reference/datamodel.rst

View workflow job for this annotation

GitHub Actions / Docs

py:attr reference target not found: __doc__
| | unavailable; not inherited by | |
| | subclasses. | |
+-------------------------+-------------------------------+-----------+
Expand All @@ -518,20 +518,20 @@
| | .. versionadded:: 3.3 | |
+-------------------------+-------------------------------+-----------+
| :attr:`__module__` | The name of the module the | Writable |
| | function was defined in, or | |

Check warning on line 521 in Doc/reference/datamodel.rst

View workflow job for this annotation

GitHub Actions / Docs

py:attr reference target not found: __module__
| | ``None`` if unavailable. | |
+-------------------------+-------------------------------+-----------+
| :attr:`__defaults__` | A tuple containing default | Writable |
| | argument values for those | |

Check warning on line 525 in Doc/reference/datamodel.rst

View workflow job for this annotation

GitHub Actions / Docs

py:attr reference target not found: __defaults__
| | arguments that have defaults, | |
| | or ``None`` if no arguments | |
| | have a default value. | |
+-------------------------+-------------------------------+-----------+
| :attr:`__code__` | The code object representing | Writable |
| | the compiled function body. | |

Check warning on line 531 in Doc/reference/datamodel.rst

View workflow job for this annotation

GitHub Actions / Docs

py:attr reference target not found: __code__
+-------------------------+-------------------------------+-----------+
| :attr:`__globals__` | A reference to the dictionary | Read-only |
| | that holds the function's | |

Check warning on line 534 in Doc/reference/datamodel.rst

View workflow job for this annotation

GitHub Actions / Docs

py:attr reference target not found: __globals__
| | global variables --- the | |
| | global namespace of the | |
| | module in which the function | |
Expand All @@ -542,14 +542,14 @@
| | attributes. | |
+-------------------------+-------------------------------+-----------+
| :attr:`__closure__` | ``None`` or a tuple of cells | Read-only |
| | that contain bindings for the | |

Check warning on line 545 in Doc/reference/datamodel.rst

View workflow job for this annotation

GitHub Actions / Docs

py:attr reference target not found: __closure__
| | function's free variables. | |
| | See below for information on | |
| | the ``cell_contents`` | |
| | attribute. | |
+-------------------------+-------------------------------+-----------+
| :attr:`__annotations__` | A dict containing annotations | Writable |
| | of parameters. The keys of | |

Check warning on line 552 in Doc/reference/datamodel.rst

View workflow job for this annotation

GitHub Actions / Docs

py:attr reference target not found: __annotations__
| | the dict are the parameter | |
| | names, and ``'return'`` for | |
| | the return annotation, if | |
Expand All @@ -559,7 +559,7 @@
| | :ref:`annotations-howto`. | |
+-------------------------+-------------------------------+-----------+
| :attr:`__kwdefaults__` | A dict containing defaults | Writable |
| | for keyword-only parameters. | |

Check warning on line 562 in Doc/reference/datamodel.rst

View workflow job for this annotation

GitHub Actions / Docs

py:attr reference target not found: __kwdefaults__
+-------------------------+-------------------------------+-----------+

Most of the attributes labelled "Writable" check the type of the assigned value.
Expand Down Expand Up @@ -2102,6 +2102,10 @@
: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.

Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,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
-----------

Expand Down
61 changes: 61 additions & 0 deletions Lib/test/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,67 @@ class C: pass
D = types.new_class('D', (A(), C, B()), {})
self.assertEqual(D.__bases__, (A1, A2, A3, C, B1, B2))

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.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.assertEqual(types.get_original_bases(int), (object,))
self.assertEqual(types.get_original_bases(D), (B[str], float))

class E(list[T]): pass
class F(list[int]): pass

self.assertEqual(types.get_original_bases(E), (list[T],))
self.assertEqual(types.get_original_bases(F), (list[int],))

Gobot1234 marked this conversation as resolved.
Show resolved Hide resolved
class ClassBasedNamedTuple(typing.NamedTuple):
x: int

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())

# Many of the following tests are derived from test_descr.py
def test_prepare_class(self):
# Basic test of metaclass derivation
Expand Down
32 changes: 32 additions & 0 deletions Lib/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,38 @@ def _calculate_meta(meta, bases):
"of the metaclasses of all its bases")
return winner


def get_original_bases(cls, /):
"""Return the class's "original" bases prior to modification by `__mro_entries__`.

Examples::

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:
return cls.__orig_bases__
except AttributeError:
try:
return cls.__bases__
except AttributeError:
raise TypeError(
f'Expected an instance of type, not {type(cls).__name__!r}'
) from None


class DynamicClassAttribute:
"""Route attribute access on a class to __getattr__.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Implement :func:`types.get_original_bases` to provide further introspection
for types.
Loading