Package: src:python-beartype Version: 0.22.9-2 User: [email protected] Usertags: python3.15 Tags: patch, ftbfs, forky, sid Severity: important
Hi! While rebuilding the python related packages against the Python 3.15rc2 version we found that python-beartype fails to build from source [1]. To fix this, I had to apply two backported upstream patches: - commit a1b45d6 [2]: Python 3.15 x 1. - commit 4a9fa1d [3]: _colorize blacklist. I've applied these fixes in the sandbox [4] to verify that it builds successfully, please consider applying the patch to support the upcoming 3.15 version. Setting the severity to important for now. Once Python 3.15 is released, it will be added to python3-defaults and this bug will become release critical. Happy hacking, [1]: https://debusine.debian.net/debian/r-python-python3.15/artifact/4720235/ [2]: https://github.com/beartype/beartype/commit/a1b45d695319a2686469c0ea8f9ae0eef76d5da0 [3]: https://github.com/beartype/beartype/commit/4a9fa1dcb61cd21d2b100d6c53bd8642d13f650d -- "Can you imagine what I would do if I could do all I can?" -- Sun Tzu Saludos /\/\ /\ >< `/
From: leycec <[email protected]> Date: Thu, 5 Mar 2026 03:22:54 -0400 Subject: Python 3.15 x 1. This commit is the first in a commit chain generalizing @beartype to officially support Python 3.15, en-route to resolving feature request kindly submitted by the positively ingenious @posita (Matt Bogosian). In theory, this commit succeeds in generalizing @beartype to officially support Python 3.15. In practice, we still need to reference Python 3.15 in our GitHub Actions-based continuous integration (CI) workflow. (*Esteemed steam!*) --- beartype/_cave/_cavemap.py | 9 +- beartype/_conf/_confoverrides.py | 4 +- beartype/_conf/confmain.py | 15 +- beartype/_util/module/utilmoddeprecate.py | 13 +- beartype/_util/py/utilpyversion.py | 29 +- beartype/_util/text/utiltextversion.py | 5 +- beartype/claw/_importlib/_clawimpload.py | 47 ++- beartype/typing/__init__.py | 435 +++++++++------------ beartype_test/a00_unit/a00_core/test_a90_typing.py | 85 ++-- 9 files changed, 313 insertions(+), 329 deletions(-) diff --git a/beartype/_cave/_cavemap.py b/beartype/_cave/_cavemap.py index d34647d..06d6ff6 100644 --- a/beartype/_cave/_cavemap.py +++ b/beartype/_cave/_cavemap.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # --------------------( LICENSE )-------------------- -# Copyright (c) 2014-2025 Beartype authors. +# Copyright (c) 2014-2026 Beartype authors. # See "LICENSE" for further details. ''' @@ -27,14 +27,13 @@ # ....................{ IMPORTS }.................... from beartype.roar import BeartypeCaveNoneTypeOrKeyException -from beartype.typing import ( +from typing import ( Any, - Tuple, Union, ) # ....................{ HINTS }.................... -_TypeTuple = Tuple[Union[type, str], ...] +_TypeTuple = tuple[Union[type, str], ...] ''' PEP-compliant type hint matching a **type tuple** (i.e., tuple containing only types and forward references to deferred types specified as the fully-qualified @@ -242,7 +241,7 @@ submodule to prevent cyclic import dependencies. ''' -_NoneTypes: Tuple[type, ...] = (_NoneType,) +_NoneTypes: tuple[type, ...] = (_NoneType,) ''' Tuple of only the type of the :data:`None` singleton. ''' diff --git a/beartype/_conf/_confoverrides.py b/beartype/_conf/_confoverrides.py index cd24917..8464081 100644 --- a/beartype/_conf/_confoverrides.py +++ b/beartype/_conf/_confoverrides.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # --------------------( LICENSE )-------------------- -# Copyright (c) 2014-2025 Beartype authors. +# Copyright (c) 2014-2026 Beartype authors. # See "LICENSE" for further details. ''' @@ -11,7 +11,6 @@ parameter accepted by the :class:`beartype.BeartypeConf.__init__` method). # ....................{ IMPORTS }.................... from beartype.roar import BeartypeConfParamException -from beartype.typing import Optional from beartype._data.typing.datatyping import ( DictStrToAny, Pep484TowerComplex, @@ -19,6 +18,7 @@ from beartype._data.typing.datatyping import ( ) from beartype._util.cache.utilcachecall import callable_cached from beartype._util.kind.maplike.utilmapfrozen import FrozenDict +from typing import Optional # ....................{ GETTERS }.................... def sanify_conf_kwargs_is_pep484_tower(conf_kwargs: DictStrToAny) -> None: diff --git a/beartype/_conf/confmain.py b/beartype/_conf/confmain.py index 937ae9a..5c4c83f 100644 --- a/beartype/_conf/confmain.py +++ b/beartype/_conf/confmain.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # --------------------( LICENSE )-------------------- -# Copyright (c) 2014-2025 Beartype authors. +# Copyright (c) 2014-2026 Beartype authors. # See "LICENSE" for further details. ''' @@ -37,11 +37,6 @@ callers. # ....................{ IMPORTS }.................... from beartype.roar._roarwarn import ( _BeartypeConfReduceDecoratorExceptionToWarningDefault) -from beartype.typing import ( - TYPE_CHECKING, - Dict, - Optional, -) from beartype._conf.confenum import ( BeartypeStrategy, BeartypeViolationVerbosity, @@ -67,6 +62,10 @@ from beartype._data.kind.datakindmap import FROZENDICT_EMPTY from beartype._util.kind.maplike.utilmapfrozen import FrozenDict from beartype._util.utilobject import get_object_type_basename from threading import Lock +from typing import ( + TYPE_CHECKING, + Optional, +) # ....................{ DATACLASSES }.................... class BeartypeConf(object): @@ -103,7 +102,7 @@ class BeartypeConf(object): _conf_args : tuple Tuple of the values of *all* possible keyword parameters (in arbitrary order) configuring this configuration. - _conf_kwargs : Dict[str, object] + _conf_kwargs : dict[str, object] Dictionary mapping from the names to values of *all* possible keyword parameters configuring this configuration. _hash : int @@ -1467,7 +1466,7 @@ global for non-reentrant reuse elsewhere as a context manager). ''' -_beartype_conf_args_to_conf: Dict[tuple, BeartypeConf] = {} +_beartype_conf_args_to_conf: dict[tuple, BeartypeConf] = {} ''' Non-thread-safe **beartype configuration parameter cache** (i.e., dictionary mapping from the hash of each set of parameters accepted by a prior call of the diff --git a/beartype/_util/module/utilmoddeprecate.py b/beartype/_util/module/utilmoddeprecate.py index 6b146f0..ce2365c 100644 --- a/beartype/_util/module/utilmoddeprecate.py +++ b/beartype/_util/module/utilmoddeprecate.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # --------------------( LICENSE )-------------------- -# Copyright (c) 2014-2025 Beartype authors. +# Copyright (c) 2014-2026 Beartype authors. # See "LICENSE" for further details. ''' @@ -17,13 +17,10 @@ This private submodule is *not* intended for importation by downstream callers. # by this submodule. This submodule is typically called from the "__init__" # submodules of public subpackages. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -from beartype.typing import ( - Any, - Mapping, -) +from typing import Any from beartype._data.kind.datakindiota import SENTINEL from beartype._data.kind.datakindmap import FROZENDICT_EMPTY -from collections.abc import Mapping as MappingABC +from collections.abc import Mapping from warnings import warn # ....................{ IMPORTERS }.................... @@ -95,9 +92,9 @@ def deprecate_module_attr( ''' assert isinstance(attr_deprecated_name, str), ( f'{repr(attr_deprecated_name)} not string.') - assert isinstance(attr_deprecated_name_to_nondeprecated_name, MappingABC), ( + assert isinstance(attr_deprecated_name_to_nondeprecated_name, Mapping), ( f'{repr(attr_deprecated_name_to_nondeprecated_name)} not mapping.') - assert isinstance(attr_nondeprecated_name_to_value, MappingABC), ( + assert isinstance(attr_nondeprecated_name_to_value, Mapping), ( f'{repr(attr_nondeprecated_name_to_value)} not mapping.') # Fully-qualified name of the caller's submodule. Since all physical diff --git a/beartype/_util/py/utilpyversion.py b/beartype/_util/py/utilpyversion.py index 1a7942e..989f8ab 100644 --- a/beartype/_util/py/utilpyversion.py +++ b/beartype/_util/py/utilpyversion.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # --------------------( LICENSE )-------------------- -# Copyright (c) 2014-2025 Beartype authors. +# Copyright (c) 2014-2026 Beartype authors. # See "LICENSE" for further details. ''' @@ -81,15 +81,14 @@ IS_PYTHON_AT_LEAST_3_15 = IS_PYTHON_AT_LEAST_3_16 or version_info >= (3, 15) ''' -#FIXME: Preserved if we ever require this. *shrug* -# #FIXME: After dropping Python 3.14 support: -# #* Remove all code conditionally testing this global. -# #* Remove this global. -# IS_PYTHON_AT_MOST_3_14 = not IS_PYTHON_AT_LEAST_3_15 -# ''' -# :data:`True` only if the active Python interpreter targets at most Python -# 3.14.x. -# ''' +#FIXME: After dropping Python 3.14 support: +#* Remove all code conditionally testing this global. +#* Remove this global. +IS_PYTHON_AT_MOST_3_14 = not IS_PYTHON_AT_LEAST_3_15 +''' +:data:`True` only if the active Python interpreter targets at most Python +3.14.x. +''' #FIXME: After dropping Python 3.13 support: @@ -126,6 +125,16 @@ IS_PYTHON_AT_LEAST_3_13 = IS_PYTHON_AT_LEAST_3_14 or version_info >= (3, 13) ''' +#FIXME: After dropping Python 3.12 support: +#* Remove all code conditionally testing this global. +#* Remove this global. +IS_PYTHON_AT_MOST_3_12 = not IS_PYTHON_AT_LEAST_3_13 +''' +:data:`True` only if the active Python interpreter targets at most Python +3.12.x. +''' + + #FIXME: After dropping Python 3.11 support: #* Refactor all code conditionally testing this global to be unconditional. #* Remove this global. diff --git a/beartype/_util/text/utiltextversion.py b/beartype/_util/text/utiltextversion.py index d772113..4b95804 100644 --- a/beartype/_util/text/utiltextversion.py +++ b/beartype/_util/text/utiltextversion.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # --------------------( LICENSE )-------------------- -# Copyright (c) 2014-2025 Beartype authors. +# Copyright (c) 2014-2026 Beartype authors. # See "LICENSE" for further details. ''' @@ -12,11 +12,10 @@ This private submodule is *not* intended for importation by downstream callers. # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilTextVersionException -from beartype.typing import Tuple from re import compile as re_compile # ....................{ CONVERTERS }.................... -def convert_str_version_to_tuple(version: str) -> Tuple[int, ...]: +def convert_str_version_to_tuple(version: str) -> tuple[int, ...]: ''' Convert the passed human-readable ``.``-delimited version string into a machine-readable version tuple of corresponding integers, suitable for diff --git a/beartype/claw/_importlib/_clawimpload.py b/beartype/claw/_importlib/_clawimpload.py index 044687b..c8a746c 100644 --- a/beartype/claw/_importlib/_clawimpload.py +++ b/beartype/claw/_importlib/_clawimpload.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # --------------------( LICENSE )-------------------- -# Copyright (c) 2014-2025 Beartype authors. +# Copyright (c) 2014-2026 Beartype authors. # See "LICENSE" for further details. ''' @@ -24,6 +24,7 @@ from beartype.roar import BeartypeClawImportAstException from beartype.typing import Optional from beartype._conf.confmain import BeartypeConf from beartype._util.ast.utilastget import get_node_repr_indented +from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_15 from beartype._util.text.utiltextlabel import label_exception from importlib import ( # type: ignore[attr-defined] _bootstrap_external, # pyright: ignore @@ -211,11 +212,11 @@ class BeartypeSourceFileLoader(SourceFileLoader): #. Temporarily monkey-patches (i.e., replaces) the private :func:`importlib._bootstrap_external.cache_from_source` function with our beartype-specific - :func:`cache_from_source_beartype` variant. - #. Calls the superclass :meth:`SourceLoader.get_code` method, which: + :func:`.cache_from_source_beartype` variant. + #. Calls the superclass :meth:`.SourceLoader.get_code` method, which: #. Calls our override of the lower-level superclass - :meth:`SourceLoader.source_to_code` method. + :meth:`.SourceLoader.source_to_code` method. #. Restores the :func:`importlib._bootstrap_external.cache_from_source` function to @@ -244,7 +245,7 @@ class BeartypeSourceFileLoader(SourceFileLoader): including the name and version of the active Python interpreter). This monkey-patch suffixes ``{optimization_markers}`` by - :data:`.BEARTYPE_OPTIMIZATION_MARKER`, which additionally uniquifies the + :data:`.OPTIMIZATION_MARKER_BEARTYPE`, which additionally uniquifies the filename of this bytecode file to the abstract syntax tree (AST) transformation applied by this version of :mod:`beartype`. Why? Because external callers can trivially enable and disable that transformation @@ -401,9 +402,15 @@ class BeartypeSourceFileLoader(SourceFileLoader): data: bytes, path: str, + # Optional parameters. + # + # Note that the optional "fullname" parameter is accepted by the + # superclass method *ONLY* under Python >= 3.15. + fullname: Optional[str] = None, + # Optional keyword-only parameters. *, - _optimize: int =-1, + _optimize: int = -1, ) -> CodeType: ''' Code object dynamically compiled from the **sourceful Python package or @@ -423,7 +430,15 @@ class BeartypeSourceFileLoader(SourceFileLoader): or module to be decoded and dynamically compiled into a code object. path : str Absolute or relative filename of that Python package or module. - _optimize : int, optional + fullname : Optional[str], default: None, + Fully-qualified name of that Python package or module. In theory, + this name *should* be equal to the :mod:`beartype`-specific + :attr:`._module_name` instance variable and thus ignorable for + :mod:`beartype` purposes. In practice, it's best to assume nothing. + Defaults to :data:`None`, presumably for compatibility with + third-party packages targeting older Python versions that failed to + define this parameter. + _optimize : int, default: -1 **Optimization level** (i.e., numeric integer signifying increasing levels of optimization under which to compile that Python package or module). Defaults to -1, implying the current interpreter-wide @@ -453,8 +468,22 @@ class BeartypeSourceFileLoader(SourceFileLoader): # If that module has *NOT* been registered for type-checking, preserve # that module as is by simply deferring to the superclass method. if self._module_conf is None: - return super().source_to_code( # type: ignore[call-arg] - data=data, path=path, _optimize=_optimize) # pyright: ignore + # If the active Python interpreter targets Python >= 3.15, the + # superclass method accepts the additional optional "fullname" + # parameter. In this case, pass this parameter. + if IS_PYTHON_AT_LEAST_3_15: + return super().source_to_code( # type: ignore[call-arg] + data=data, + path=path, + fullname=fullname, # pyright: ignore + _optimize=_optimize, + ) + # Else, the active Python interpreter targets Python <= 3.14. In + # this case, the superclass method accepts *NO* additional optional + # "fullname" parameter. Avoid passing this parameter. + else: + return super().source_to_code( # type: ignore[call-arg] + data=data, path=path, _optimize=_optimize) # pyright: ignore # Else, that module has been registered for type-checking. # Plaintext decoded contents of that module. diff --git a/beartype/typing/__init__.py b/beartype/typing/__init__.py index 7efc49d..bb513d4 100644 --- a/beartype/typing/__init__.py +++ b/beartype/typing/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # --------------------( LICENSE )-------------------- -# Copyright (c) 2014-2025 Beartype authors. +# Copyright (c) 2014-2026 Beartype authors. # See "LICENSE" for further details. ''' @@ -99,8 +99,13 @@ this submodule rather than from :mod:`typing` directly: e.g., # # dependency. This is the only rule in @beartype's Rule of Law. # else: # #FIXME: Unfortunately, to avoid circular import dependencies, these -# #imports will need to be copy-and-pasted into equivalent condensed -# #submodules of a new "beartype.typing._util" subpackage. +# #imports will need to be: +# #* Moved into equivalent condensed submodules of a new +# # "beartype.typing._util" subpackage. +# #* The original attributes in the "beartype._util" subpackage should +# # then quietly alias their new location in the "beartype.typing._util" +# # subpackage. +# # # Import the requisite machinery that will make the magic happen. # from beartype._util.hint.utilhintfactory import TypeHintTypeFactory # from beartype._util.api.standard.utiltyping import ( @@ -120,28 +125,6 @@ this submodule rather than from :mod:`typing` directly: e.g., # Annotated = _import_typing_attr_or_fallback('Annotated', bool) # # #FIXME: Repeat the above logic for *ALL* existing "typing" attributes. -#FIXME: Actually, ain't nobody got time for that at the moment. The low-hanging -#fruit here is to just trivially alias this submodule to the "typing" module -#like so: -# from typing import * -# -#Then: -#* Remove almost *ALL* of this submodule. -#* Conditionally redefine various attributes (e.g., deprecated PEP 484 stuff, -# slow "Protocol" superclass) as needed. -# -#This has the dramatic improvement of innately synchronizing this submodule, -#which has become a *NIGHTMARE* to maintain, against the official -#implementation. Python 3.14 @beartype users hit this when early release -#candidates of Python 3.14 removed "typing.ByteString", only for the final -#release of Python 3.14 to add it back. @beartype then began raising exceptions -#on importation. Since "typing" is a moving target nightmare, exposing ourselves -#to that nightmare no longer makes sense at all. -# -#The only possible issue might be mypy and pyright. If either complain, we'll -#have no choice but to eventually *DEPRECATE* this entire submodule -- probably -#in the run up to @beartype 1.0.0. Ugly stuff, but inviting synchronization woes -#is even uglier. # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -155,182 +138,30 @@ this submodule rather than from :mod:`typing` directly: e.g., # "from typing import Annotated" rather than # "import_typing_attr_or_none('Annotated')"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -from beartype._util.py.utilpyversion import ( - IS_PYTHON_AT_MOST_3_16 as _IS_PYTHON_AT_MOST_3_16, - IS_PYTHON_AT_MOST_3_15 as _IS_PYTHON_AT_MOST_3_15, - IS_PYTHON_AT_MOST_3_13 as _IS_PYTHON_AT_MOST_3_13, - IS_PYTHON_AT_LEAST_3_14 as _IS_PYTHON_AT_LEAST_3_14, - IS_PYTHON_AT_LEAST_3_13 as _IS_PYTHON_AT_LEAST_3_13, - IS_PYTHON_AT_LEAST_3_12 as _IS_PYTHON_AT_LEAST_3_12, - IS_PYTHON_AT_LEAST_3_11 as _IS_PYTHON_AT_LEAST_3_11, -) - -# ....................{ IMPORTS ~ all }.................... -#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -# WARNING: To prevent "mypy --no-implicit-reexport" from raising literally -# hundreds of errors at static analysis time, *ALL* public attributes *MUST* be -# explicitly reimported under the same names with "{exception_name} as -# {exception_name}" syntax rather than merely "{exception_name}". Yes, this is -# ludicrous. Yes, this is mypy. For posterity, these failures resemble: -# beartype/_cave/_cavefast.py:47: error: Module "beartype.roar" does not -# explicitly export attribute "BeartypeCallUnavailableTypeException"; -# implicit reexport disabled [attr-defined] -#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -# Import all public attributes of the "typing" module both available under all -# supported Python versions and *NOT* deprecated by a subsequent Python version -# under their original names. -from typing import ( - TYPE_CHECKING as TYPE_CHECKING, - Any as Any, - Annotated as Annotated, - BinaryIO as BinaryIO, - ClassVar as ClassVar, - Concatenate as Concatenate, # pyright: ignore - Final as Final, # pyright: ignore - ForwardRef as ForwardRef, - Generic as Generic, - IO as IO, - Literal as Literal, # pyright: ignore - NewType as NewType, - NamedTuple as NamedTuple, - NoReturn as NoReturn, - Optional as Optional, - ParamSpec as ParamSpec, # pyright: ignore - ParamSpecArgs as ParamSpecArgs, # pyright: ignore - ParamSpecKwargs as ParamSpecKwargs, # pyright: ignore - Reversible as Reversible, # pyright: ignore - TypedDict as TypedDict, # pyright: ignore - Text as Text, - TextIO as TextIO, - TypeAlias as TypeAlias, # pyright: ignore - TypeGuard as TypeGuard, # pyright: ignore - TypeVar as TypeVar, - Union as Union, - cast as cast, - final as final, # pyright: ignore - get_args as get_args, # pyright: ignore - get_origin as get_origin, # pyright: ignore - get_type_hints as get_type_hints, - is_typeddict as is_typeddict, # pyright: ignore - no_type_check as no_type_check, - no_type_check_decorator as no_type_check_decorator, - overload as overload, -) -# ....................{ IMPORTS ~ version : at least }.................... -# Import all public attributes of the "typing" module both available under at -# least some Python interpreter version *AN* not yet deprecated by a subsequent -# Python interpreter version under their original names. +# Effectively alias this third-party "beartype.typing" submodule to the standard +# "typing" module by unconditionally importing all explicitly exported public +# attributes from the standard "typing" module *BEFORE* conditionally overriding +# a proper subset of these attributes with non-standard alternatives below. +from typing import * # pyright: ignore -#FIXME: mypy is now emitting non-fatal warnings about our failing to import from -#"typing_extensions", which is both an overly strongly opinionated position for -#mypy to stake out *AND* a bad opinion at that, because "typing_extensions" is -#a third-party package. Ideally, mypy shouldn't be pushing *ANY* third-party -#packages. These warnings resemble: -# beartype/typing/__init__.py:145: note: Use `from typing_extensions import Final` instead -# beartype/typing/__init__.py:145: note: See https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-new-additions-to-the-typing-module -# beartype/typing/__init__.py:145: note: Use `from typing_extensions import Literal` instead -# -#That's not the worst, however. mypy is erroneously ignoring our intentional -#"# type: ignore[attr-defined]" pragmas here. It's likely that the ultimate -#culprit is our use of beartype-specific "IS_PYTHON_AT_LEAST_*" boolean globals. -#Instead, mypy appears to only support hard-coded tests against the -#"sys.version_info" tuple: e.g., -# if sys.version_info >= (3, 8): -# -#To resolve this, we should consider: -#* Abandoning our usage of beartype-specific "IS_PYTHON_AT_LEAST_*" boolean -# globals for hard-coded tests against the "sys.version_info" tuple (above). -#* Submitting an upstream issue requesting that mypy respect the -# "# type: ignore[attr-defined]" pragma rather than emitting warnings here. - -# If the active Python interpreter targets Python >= 3.11... -if _IS_PYTHON_AT_LEAST_3_11: - from typing import ( # type: ignore[attr-defined] - LiteralString as LiteralString, # pyright: ignore - Never as Never, # pyright: ignore - NotRequired as NotRequired, # pyright: ignore - Required as Required, # pyright: ignore - Self as Self, # pyright: ignore - TypeVarTuple as TypeVarTuple, # pyright: ignore - Unpack as Unpack, # pyright: ignore - assert_never as assert_never, # pyright: ignore - assert_type as assert_type, # pyright: ignore - clear_overloads as clear_overloads, # pyright: ignore - dataclass_transform as dataclass_transform, # pyright: ignore - get_overloads as get_overloads, # pyright: ignore - reveal_type as reveal_type, # pyright: ignore +# ....................{ ALIAS }.................... +# If the active Python interpreter is *NOT* performing static type-checking +# (e.g., mypy, pyright), override the default implementations of a proper subset +# of public attributes explicitly exported by the standard "typing" module with +# semantically equivalent runtime-friendly alternatives. Specifically... +if not TYPE_CHECKING: + # ....................{ IMPORTS }.................... + # Defer runtime-specific imports. + from beartype._util.py.utilpyversion import ( + IS_PYTHON_AT_MOST_3_14 as _IS_PYTHON_AT_MOST_3_14, ) - # If the active Python interpreter targets Python >= 3.12... - if _IS_PYTHON_AT_LEAST_3_12: - from typing import ( # type: ignore[attr-defined] - TypeAliasType as TypeAliasType, # pyright: ignore - override as override, # pyright: ignore - ) - - # If the active Python interpreter targets Python >= 3.13... - if _IS_PYTHON_AT_LEAST_3_13: - from typing import ( # type: ignore[attr-defined] - NoDefault as NoDefault, # pyright: ignore - ReadOnly as ReadOnly, # pyright: ignore - TypeIs as TypeIs, # pyright: ignore - get_protocol_members as get_protocol_members, # pyright: ignore - is_protocol as is_protocol, # pyright: ignore - ) - - # If the active Python interpreter targets Python >= 3.14... - if _IS_PYTHON_AT_LEAST_3_14: - from typing import ( # type: ignore[attr-defined] - evaluate_forward_ref as evaluate_forward_ref, # pyright: ignore - ) - -# ....................{ IMPORTS ~ version : at most }.................... -# Import all public attributes of the "typing" module both available under at -# most some Python interpreter version (typically due to having been deprecated -# by a prior Python interpreter version). - -# If the active Python interpreter targets at most Python <= 3.16... -if _IS_PYTHON_AT_MOST_3_16: - # Import the PEP 585-compliant "collections.abc.ByteString" attribute - # under 3.9 <= Python <= 3.13. Both "collections.abc.ByteString" *AND* - # "typing.ByteString" have been scheduled for removal under Python 3.17 by - # the upstream CPython issue: - # https://github.com/python/cpython/issues/91896 - # - # Note that these attributes were originally scheduled for removal under - # Python 3.14. This removal was since deferred by three minor versions (and - # thus three years) to inform downstream third-party packages with proper - # "DeprecationWarning" warnings emitted by the "typing" module. - from collections.abc import ByteString as ByteString # type: ignore[attr-defined] - - # If the active Python interpreter targets at most Python <= 3.15... - if _IS_PYTHON_AT_MOST_3_15: - # Import the PEP 484-compliant "typing.AnyStr" attribute under 3.9 <= - # Python <= 3.15. This attribute has been scheduled for removal under - # Python 3.16 by the upstream CPython issue: - # https://github.com/python/cpython/issues/105578 - from typing import AnyStr as AnyStr - -# ....................{ PEP ~ 544 }.................... -# If this interpreter is performing static type-checking (e.g., via mypy), defer -# to the standard library versions of the family of "Supports*" protocols. -if TYPE_CHECKING: - from typing import ( # type: ignore[attr-defined] - Protocol as Protocol, # pyright: ignore - SupportsAbs as SupportsAbs, - SupportsBytes as SupportsBytes, - SupportsComplex as SupportsComplex, - SupportsFloat as SupportsFloat, - SupportsIndex as SupportsIndex, # pyright: ignore - SupportsInt as SupportsInt, - SupportsRound as SupportsRound, - runtime_checkable as runtime_checkable, # pyright: ignore - ) -# Else, this interpreter is *NOT* performing static type-checking. In this -# case, prefer our optimized PEP 544 attributes. -else: + # ....................{ PEP ~ 544 }.................... + # Alias both the PEP 544-compliant "Protocol" superclass and all related + # "Supports*" protocols to beartype-specific alternatives, which exhibit + # optimized runtime performed over their default implementations by the + # standard "typing" module. from beartype.typing._typingpep544 import ( Protocol as Protocol, SupportsAbs as SupportsAbs, @@ -343,61 +174,12 @@ else: runtime_checkable as runtime_checkable, ) -# ....................{ PEP ~ 585 }.................... -# If the active Python interpreter is performing static type-checking (e.g., -# "mypy"), import *ALL* public attributes of the "typing" module deprecated by -# PEP 585 as their original values. -# -# This is intentionally performed *BEFORE* the corresponding "else:" branch. -# Why? Because "mypy". If the order of these two branches is reversed, "mypy" -# emits errors when attempting to subscript *ANY* builtin type: e.g., -# error: "tuple" is not subscriptable [misc] -if TYPE_CHECKING: - from typing import ( - AbstractSet as AbstractSet, - AsyncContextManager as AsyncContextManager, - AsyncGenerator as AsyncGenerator, - AsyncIterable as AsyncIterable, - AsyncIterator as AsyncIterator, - Awaitable as Awaitable, - Callable as Callable, - ChainMap as ChainMap, - Collection as Collection, - Container as Container, - ContextManager as ContextManager, - Coroutine as Coroutine, - Counter as Counter, - DefaultDict as DefaultDict, - Deque as Deque, - Dict as Dict, - FrozenSet as FrozenSet, - Generator as Generator, - Hashable as Hashable, - ItemsView as ItemsView, - Iterable as Iterable, - Iterator as Iterator, - KeysView as KeysView, - List as List, - Mapping as Mapping, - Match as Match, - MappingView as MappingView, - MutableMapping as MutableMapping, - MutableSequence as MutableSequence, - MutableSet as MutableSet, - OrderedDict as OrderedDict, - Pattern as Pattern, - Reversible as Reversible, - Set as Set, - Sized as Sized, - Tuple as Tuple, - Type as Type, - Sequence as Sequence, - ValuesView as ValuesView, - ) -# Else, the active Python interpreter is *NOT* performing static type-checking. -# In this case, alias *ALL* public attributes of the "typing" module deprecated -# by PEP 585 to their equivalent values elsewhere in the standard library. -else: + # ....................{ PEP ~ 585 }.................... + # Alias *ALL* PEP 484-compliant public attributes defined by the standard + # "typing" module that have since been officially deprecated by PEP 585 + # (e.g., "typing.ChainMap") to their semantically equivalent non-deprecated + # alternatives defined elsewhere in the standard library (e.g., + # "collections.ChainMap"). from collections import ( ChainMap as ChainMap, Counter as Counter, @@ -440,9 +222,146 @@ else: Pattern as Pattern, ) - Dict = dict # type: ignore[misc] - FrozenSet = frozenset # type: ignore[misc] - List = list # type: ignore[misc] - Set = set # type: ignore[misc] - Tuple = tuple # type: ignore[assignment] - Type = type # type: ignore[assignment] + # Trivially alias all remaining deprecated PEP 484-compliant type hint + # factories to their equivalent builtins. + Dict = dict + FrozenSet = frozenset + List = list + Set = set + Tuple = tuple + Type = type + + # ....................{ PEP ~ 585 : removed }.................... + # If the active Python interpreter targets Python <= 3.14 and thus still + # defines the deprecated "typing.ByteString" type *WITHOUT* yet emitting a + # "DeprecationWarning" warning, delete that type implicitly imported above + # in favour of the preferable "collections.abc.ByteString" type. + # + # Look. It's complicated. And dumb. Really, really dumb. + if _IS_PYTHON_AT_MOST_3_14: + from collections.abc import ByteString + # Else, the active Python interpreter targets Python >= 3.15 and thus either + # no longer defines the deprecated "typing.ByteString" type at all *OR* does + # but only in a manner emitting *WITHOUT* yet emitting a + # "DeprecationWarning" warning. In either case, permit the __getattr__() + # dunder function defined below to handle this edge case more cleverly. +# Else, the active Python interpreter is performing static type-checking. In +# this case, force the unclean static type-checker to pretend that this +# "beartype.typing" submodule is a trivial alias of the "typing" module by... +# *DOING ABSOLUTELY NOTHING WHATSOEVER*. Beartype: "You win by doing nothing." + +# ....................{ DUNDERS }.................... +def __getattr__(attr_name: str) -> object: + ''' + Dynamically retrieve a deprecated attribute with the passed unqualified name + from this submodule and emit a non-fatal deprecation warning on each such + retrieval if this submodule defines this attribute *or* raise an exception + otherwise. + + The Python interpreter implicitly calls this :pep:`562`-compliant module + dunder function under Python >= 3.7 *after* failing to directly retrieve an + explicit attribute with this name from this submodule. Since this dunder + function is only called in the event of an error, neither space nor time + efficiency are a concern here. + + Parameters + ---------- + attr_name : str + Unqualified name of the deprecated attribute to be retrieved. + + Returns + ------- + object + Value of this deprecated attribute. + + Warns + ----- + DeprecationWarning + If this attribute is deprecated. + + Raises + ------ + AttributeError + If this attribute is unrecognized and thus erroneous. + ''' + + # ....................{ IMPORTS }.................... + # Defer dunder-specific imports. + from beartype._util.py.utilpyversion import ( + IS_PYTHON_AT_MOST_3_16, + IS_PYTHON_AT_LEAST_3_11, + ) + + # ....................{ PEP ~ 585 : removed }.................... + # Alias *ALL* PEP 484-compliant public attributes defined by the standard + # "typing" module that have since been quietly removed from that module + # sooner than PEP 585 mandates these attributes be removed. Technically, + # CPython itself is violating PEP 585 here. Pragmatically, nobody cares. + + # Alias the PEP 484-compliant "ByteString" type hint singleton to + # the PEP 585-compliant "collections.abc.ByteString" abstract base + # class (ABC). Both "collections.abc.ByteString" *AND* + # "typing.ByteString" have been scheduled for removal under Python + # 3.17 by the upstream CPython issue: + # https://github.com/python/cpython/issues/91896 + # + # Note that: + # * These attributes were originally scheduled for removal under Python + # 3.14. This removal was since deferred by three minor versions (and thus + # three years) to inform downstream third-party packages with proper + # "DeprecationWarning" warnings emitted by the "typing" module. + # * Unconditionally importing the "collections.abc.ByteString" type above + # under either Python 3.15 or 3.16 would harmfully emit a + # "DeprecationWarning" warning for all beartype users importing from this + # "beartype.typing" subpackage even for users *NEVER* explicitly importing + # "beartype.typing.ByteString". Clearly, that behaviour is unacceptable. + # This submodule this behaves more intelligently under Python 3.15 and + # 3.16 by dynamically deferring the importation of that type until + # explicitly accessed by the caller. + + # If... + if ( + # The active Python interpreter targets at most Python <= 3.16 *AND*... + IS_PYTHON_AT_MOST_3_16 and + # The caller is explicitly importing the deprecated + # "collections.abc.ByteString" type... + attr_name == 'ByteString' + ): + # Import and return this deprecated type. + from collections.abc import ByteString + return ByteString + # Else, either: + # * The active Python interpreter targets Python >= 3.17, in which case the + # deprecated "collections.abc.ByteString" type no longer exists *OR*... + # * The caller is *NOT* explicitly importing the deprecated + # "collections.abc.ByteString" type, in which case the attribute this + # caller is explicitly importing is unrecognized. + + # If the active Python interpreter targets Python >= 3.11, the "typing" + # module defines the __getattr__() dunder method. In this case... + if IS_PYTHON_AT_LEAST_3_11: + # Attempt to... + try: + # Import the typing.__getattr__() dunder method. + from typing import __getattr__ as typing_getattr # type: ignore[attr-defined] + + # Dynamically defer to this method, thus masequerading the + # "beartype.typing" subpackage as the "typing" module even for the + # proper subset of attributes only dynamically exposed by that module. + return typing_getattr(attr_name) + # If even the above call to the typing.__getattr__() dunder method raised + # the standard "AttributeError" exception, the caller requested an invalid + # attribute that genuinely does *NOT* exist. In this case, raise a similar + # "AttributeError" exception below specific to "beartype.typing". + except AttributeError: + pass + # Else, the active Python interpreter targets Python <= 3.10. In this case, + # the "typing" module fails to define the __getattr__() dunder method. + + # Raise the same exception raised by Python on accessing a non-existent + # attribute of a module *NOT* defining this dunder function. + # + # Note that Python's non-trivial import machinery silently coerces this + # "AttributeError" exception into an "ImportError" exception. Just do it! + raise AttributeError( + f"module 'beartype.typing' has no attribute '{attr_name}'") diff --git a/beartype_test/a00_unit/a00_core/test_a90_typing.py b/beartype_test/a00_unit/a00_core/test_a90_typing.py index fc7b4e7..03246cc 100644 --- a/beartype_test/a00_unit/a00_core/test_a90_typing.py +++ b/beartype_test/a00_unit/a00_core/test_a90_typing.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # --------------------( LICENSE )-------------------- -# Copyright (c) 2014-2025 Beartype authors. +# Copyright (c) 2014-2026 Beartype authors. # See "LICENSE" for further details. ''' @@ -33,8 +33,12 @@ testing the behaviour of these attributes to the subsequent # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +from beartype_test._util.mark.pytmark import ignore_warnings # ....................{ TESTS }.................... +# Prevent pytest from capturing and displaying all expected non-fatal +# beartype-specific warnings emitted by this test. Urgh! +@ignore_warnings(DeprecationWarning) def test_api_typing() -> None: ''' Test the public API of the :mod:`beartype.meta` submodule. @@ -50,10 +54,30 @@ def test_api_typing() -> None: from beartype import typing as beartype_typing from beartype._util.py.utilpyversion import ( IS_PYTHON_AT_MOST_3_16, + # IS_PYTHON_AT_LEAST_3_15, IS_PYTHON_AT_LEAST_3_13, ) - # ..................{ MAGIC }.................. + # ..................{ LOCALS ~ beartype }.................. + # Set of the unqualified basenames of all public attributes exported by the + # "beartype.typing.__init__" submodule, initialized to the proper subset of + # these basenames *EXPLICITLY* defined by that submodule. + # + # Note that this fails to account for the remainder of these basenames only + # *DYNAMICALLY* defined by that submodule's __getattr__() dunder function, + # which the dir() builtin fails to capture. + BEARTYPE_TYPING_ATTR_NAMES = set(dir(beartype_typing)) + + # Set of the names of *ALL* attributes (both public and private) declared by + # the standard "typing" module, initialized to the proper subset of these + # basenames *EXPLICITLY* defined by that submodule. + # + # Note that this fails to account for the remainder of these basenames only + # *DYNAMICALLY* defined by that submodule's __getattr__() dunder function, + # which the dir() builtin fails to capture. + OFFICIAL_TYPING_ATTR_NAMES = set(dir(official_typing)) + + # ..................{ LOCALS ~ typing }.................. # Set of the basenames of all erroneously publicized public attributes of # all "typing" modules across all Python versions. Ideally, these attributes # would have been privatized by prefixing these basenames by "_". Ideally, @@ -98,14 +122,9 @@ def test_api_typing() -> None: 'warnings', } - # Set of all soft-deprecated public "typing" attributes only dynamically - # defined by the typing.__getattr__() dunder method and thus inaccessible to - # the standard introspection performed below. - TYPING_ATTR_PUBLIC_DYNAMIC_NAMES = set() - # Set of the basenames of all public attributes declared by the "typing" - # module whose *VALUES* differ from those declared by the "beartype.typing" - # submodule. + # module whose *VALUES* are expected to differ from those declared by the + # "beartype.typing" submodule. TYPING_ATTR_UNEQUAL_NAMES = { # Names of all PEP 484-specific "typing" type hint factories obsoleted # by equivalent PEP 585-specific type hint factories. @@ -161,11 +180,13 @@ def test_api_typing() -> None: 'SupportsRound', } - # ..................{ MAGIC ~ version }.................. + # ..................{ LOCALS ~ version }.................. # If the active Python interpreter targets Python <= 3.16... if IS_PYTHON_AT_MOST_3_16: - # Add all hard-deprecated public "typing" attributes that have since - # been permanently removed under newer Python versions. + # Add deprecated "typing" attributes that have since been permanently + # removed under newer Python versions to various sets defined above. + BEARTYPE_TYPING_ATTR_NAMES.add('ByteString') + OFFICIAL_TYPING_ATTR_NAMES.add('ByteString') TYPING_ATTR_UNEQUAL_NAMES.add('ByteString') # Else, the active Python interpreter targets Python >= 3.17. @@ -174,7 +195,7 @@ def test_api_typing() -> None: # Add all soft-deprecated public "typing" attributes only dynamically # defined by the typing.__getattr__() dunder method and thus # inaccessible to the introspection performed above. - TYPING_ATTR_PUBLIC_DYNAMIC_NAMES.add( + OFFICIAL_TYPING_ATTR_NAMES.add( # This is an odd one, frankly. The typing.__getattr__() dunder # method now dynamically exports both the "AsyncContextManager" and # "ContextManager" ABCs. For unknown reasons, the introspection @@ -184,20 +205,15 @@ def test_api_typing() -> None: ) # Else, the active Python interpreter targets Python <= 3.12. - # ..................{ LOCALS }.................. - # Set of the names of *ALL* attributes (both public and private) declared by - # the standard "typing" module. - OFFICIAL_TYPING_ATTR_NAMES = ( - set(dir(official_typing)) | TYPING_ATTR_PUBLIC_DYNAMIC_NAMES) - + # ..................{ LOCALS ~ beartype : values }.................. # Dictionary mapping from the basenames of all public attributes declared # by the "beartype.typing" subpackage to those attributes. BEARTYPE_TYPING_ATTR_NAME_TO_VALUE = { - # Public attribute declared by the "beartype.typing" submodule. + # Public attribute declared by the "beartype.typing" subpackage. beartype_typing_attr_name: getattr( beartype_typing, beartype_typing_attr_name) - # For the basename of each attribute declared by this submodule... - for beartype_typing_attr_name in dir(beartype_typing) + # For the basename of each attribute declared by that subpackage... + for beartype_typing_attr_name in BEARTYPE_TYPING_ATTR_NAMES # If this basename is... if ( beartype_typing_attr_name[0] not in { @@ -214,13 +230,17 @@ def test_api_typing() -> None: # Else, this attribute is public and thus unignorable. } + # Set of all public attributes exposed by "beartype.typing". + BEARTYPE_TYPING_ATTR_NAMES = BEARTYPE_TYPING_ATTR_NAME_TO_VALUE.keys() + + # ..................{ LOCALS ~ typing : values }.................. # Dictionary mapping from the basenames of all public attributes declared # by the standard "typing" module to those attributes. OFFICIAL_TYPING_ATTR_NAME_TO_VALUE = { # Public attribute declared by the "typing" submodule. official_typing_attr_name: getattr( official_typing, official_typing_attr_name) - # For the basename of each attribute declared by this submodule... + # For the basename of each attribute declared by that module... for official_typing_attr_name in OFFICIAL_TYPING_ATTR_NAMES # If this basename is... if ( @@ -235,10 +255,10 @@ def test_api_typing() -> None: # Else, this attribute is public and thus unignorable. } - # Sets of all public attributes exposed by "beartype.typing" and "typing". - BEARTYPE_TYPING_ATTR_NAMES = BEARTYPE_TYPING_ATTR_NAME_TO_VALUE.keys() + # Sets of all public attributes exposed by "typing". OFFICIAL_TYPING_ATTR_NAMES = OFFICIAL_TYPING_ATTR_NAME_TO_VALUE.keys() + # ..................{ LOCALS ~ compare }.................. # Set of all desynchronized public attributes (i.e., exposed in exactly one # of either "beartype.typing" or "typing" but *NOT* both). DIFFERENT_TYPING_ATTR_NAMES = ( @@ -263,7 +283,20 @@ def test_api_typing() -> None: # Assert that these two modules expose the same number of public attributes. # Since a simple assertion statement would produce non-human-readable # output, we expand this assertion to identify all differing attributes. - assert DIFFERENT_TYPING_ATTR_NAMES == set() + if DIFFERENT_TYPING_ATTR_NAMES: + # Set of all public attributes exposed by "beartype.typing" but *NOT* + # "typing" if any. + BEARTYPE_NOT_OFFICIAL_TYPING_ATTR_NAMES = ( + BEARTYPE_TYPING_ATTR_NAMES - OFFICIAL_TYPING_ATTR_NAMES) + + # Set of all public attributes exposed by "typing" but *NOT* + # "beartype.typing" if any. + OFFICIAL_NOT_BEARTYPE_TYPING_ATTR_NAMES = ( + BEARTYPE_TYPING_ATTR_NAMES - OFFICIAL_TYPING_ATTR_NAMES) + + # Assert whichever of these are non-empty to aid in debuggability. + assert BEARTYPE_NOT_OFFICIAL_TYPING_ATTR_NAMES == set() + assert OFFICIAL_NOT_BEARTYPE_TYPING_ATTR_NAMES == set() # For the basename of each typing attribute whose values *SHOULD* be # identical across these two modules...
From: leycec <[email protected]> Date: Sat, 6 Jun 2026 04:07:00 -0400 Subject: `_colorize` blacklist. This commit blacklists Python's standard (albeit technically private) pure-Python `_colorize` module from consideration by `beartype.claw` import hooks, resolving issue #656 kindly submitted by Fedora-Ansible maestro @gotmax23 (Maxwell G). Since increasingly many core public pure-Python modules also residing in the standard library now require `_colorize` (e.g., `argparse`, `traceback`), this is a surprisingly critical resolution. Thanks a heap to: * @gotmax23 for submitting this to the upstream CPython issue tracker at python/cpython#150994. * @DavidCEllis for joining us from CPython Land. It's a wonderful land! (*Brainy membrane of an entranced remembrance!*) --- beartype/_data/conf/dataconfblack.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/beartype/_data/conf/dataconfblack.py b/beartype/_data/conf/dataconfblack.py index f7d0b2e..4bdc250 100644 --- a/beartype/_data/conf/dataconfblack.py +++ b/beartype/_data/conf/dataconfblack.py @@ -112,6 +112,27 @@ See Also # tester returning True *ONLY* if the passed callable has a "__module__" dunder # attribute whose value is a string residing in this frozenset. BLACKLIST_PACKAGE_NAMES = frozenset(( + # ....................{ ANTIPATTERN ~ stdlib }.................... + # These first-party packages and modules residing in the standard Python + # library employ the "if False:" antipattern and are thus runtime-hostile. + + # The private pure-Python "_colorize" module introduced by Python 3.15 + # employs the "if False:" antipattern: e.g., + # # types + # if False: + # from typing import IO, Literal, Self, ClassVar + # _theme: Theme + # + # That should instead read: + # # types + # lazy from typing import IO, Literal, Self, ClassVar + # if False: + # _theme: Theme + # + # See also the following issue resolved by this blacklist: + # https://github.com/beartype/beartype/issues/656 + '_colorize', + # ....................{ ANTIPATTERN ~ forward ref }.................... # These third-party packages and modules widely employ the forward reference # antipattern throughout their codebases and are thus runtime-hostile.

