Skip to content

Commit

Permalink
Python 3.12 x 2.
Browse files Browse the repository at this point in the history
This commit is the last in a commit chain generalizing @beartype to
officially support the recently released Python 3.12. Specifically, this
commit:

* Refactors our `beartype.claw` import hook API to reflect Python
  3.12-specific changes, including deprecations of:
  * The `ast.Str` node subclass -- which was technically deprecated by
    the `ast.Constant` node subclass by Python 3.8. Somehow, we only
    just got the memo. *facepalm*
* Refactors our test suite to reflect Python 3.12-specific changes,
  including deprecations of:
  * The `typing.ByteString` type hint factory.
  * The `collections.abc.ByteString` abstract base class (ABC).

(*Quest for nihilistic Nyquist hills!*)
  • Loading branch information
leycec committed Oct 25, 2023
1 parent 973a00e commit a8fe768
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 110 deletions.
27 changes: 24 additions & 3 deletions beartype/_util/py/utilpyversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,38 @@
'''


#FIXME: After dropping Python 3.12 support:
#FIXME: After dropping Python 3.13 support:
#* Refactor all code conditionally testing this global to be unconditional.
#* Remove this global.
#* Remove all decorators resembling:
# @skip_if_python_version_less_than('3.12.0')
IS_PYTHON_AT_LEAST_3_12 = IS_PYTHON_AT_LEAST_4_0 or version_info >= (3, 12)
# @skip_if_python_version_less_than('3.13.0')
IS_PYTHON_AT_LEAST_3_13 = IS_PYTHON_AT_LEAST_4_0 or version_info >= (3, 13)
'''
:data:`True` only if the active Python interpreter targets at least Python
3.12.0.
'''

#FIXME: After dropping Python 3.13 support:
#* Refactor all code conditionally testing this global to be unconditional.
#* Remove this global.
#* Remove all decorators resembling:
# @skip_if_python_version_less_than('3.12.0')
IS_PYTHON_AT_LEAST_3_12 = IS_PYTHON_AT_LEAST_3_13 or version_info >= (3, 12)
'''
:data:`True` only if the active Python interpreter targets at least Python
3.13.0.
'''


#FIXME: After dropping Python 3.10 support:
#* Refactor all code conditionally testing this global to be unconditional.
#* Remove this global.
IS_PYTHON_AT_MOST_3_11 = not IS_PYTHON_AT_LEAST_3_12
'''
:data:`True` only if the active Python interpreter targets at most Python
3.11.x.
'''


#FIXME: After dropping Python 3.10 support:
#* Refactor all code conditionally testing this global to be unconditional.
Expand Down
7 changes: 3 additions & 4 deletions beartype/claw/_ast/clawastmain.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,12 @@
Attribute,
Call,
ClassDef,
# Constant,
Constant,
Expr,
ImportFrom,
Module,
Name,
NodeTransformer,
Str,
# Subscript,
# alias,
# expr,
Expand Down Expand Up @@ -363,7 +362,7 @@ def visit_Module(self, node: Module) -> Module:
# docstring by reducing that docstring to an ignorable string.)
(
isinstance(node_import_prev, Expr) and
isinstance(node_import_prev.value, Str)
isinstance(node_import_prev.value, Constant)
) or
# A future import (i.e., import of the form "from __future__
# ...") *OR*...
Expand Down Expand Up @@ -573,7 +572,7 @@ def visit_AnnAssign(self, node: AnnAssign) -> NodeVisitResult:
hint annotating this assignment, typically an instance of either:
* :class:`ast.Name`.
* :class:`ast.Str`.
* :class:`ast.Constant`.
Note that this node is *not* itself a valid PEP-compliant type hint
and should *not* be treated as such here or elsewhere.
Expand Down
101 changes: 79 additions & 22 deletions beartype_test/a00_unit/a40_api/door/_doorfixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,10 @@ class MuhGenericTwoIntInt(MuhGenericTwo[int, int]):

pass

# ..................{ LISTS }..................
# ..................{ LISTS ~ cases }..................
# List of all hint subhint cases (i.e., 3-tuples "(subhint, superhint,
# is_subhint)" describing the subhint relations between two PEP-compliant
# type hints) to be returned by this fixture.
HINT_SUBHINT_CASES = [
# ..................{ PEP 484 ~ argless : any }..................
# PEP 484-compliant catch-all type hint.
Expand Down Expand Up @@ -462,30 +465,84 @@ class MuhGenericTwoIntInt(MuhGenericTwo[int, int]):
(MuhDict, dict, True),
]

# ..................{ HINTS ~ abcs }..................
# Smoke test for all "typing" ABCs. Smoke out those bugs, pytest!
for cls_name in dir(collections.abc) + ['Deque']:
if not cls_name.startswith("_"):
typing_abc = getattr(typing, cls_name)

#FIXME: This logic (probably) isn't behaving as expected under
#Python >= 3.9, where unsubscripted "typing" factories (probably)
#are *NOT* parametrized by type variables. Let's research this up!

# Tuple of the zero or more type variables parametrizing this ABC.
typing_abc_typevars = get_hint_pep_typevars(typing_abc)
# ..................{ LISTS ~ typing }..................
# List of the unqualified basenames of all standard ABCs published by
# the standard "collections.abc" module, defined as...
COLLECTIONS_ABC_BASENAMES = [
# For the unqualified basename of each attribute defined by the standard
# "collections.abc" module...
COLLECTIONS_ABC_BASENAME
for COLLECTIONS_ABC_BASENAME in dir(collections.abc)
# If this basename is *NOT* prefixed by an underscore, this attribute is
# public and thus an actual ABC. In this case, include this ABC.
if not COLLECTIONS_ABC_BASENAME.startswith('_')
# Else, this is an unrelated private attribute. In this case, silently
# ignore this attribute and continue to the next.
]

# Number of type variables parametrizing this ABC.
nparams = len(typing_abc_typevars)
# List of the unqualified basenames of all standard abstract base classes
# (ABCs) supported by the standard "typing" module, defined as the
# concatenation of...
TYPING_ABC_BASENAMES = (
# List of the unqualified basenames of all standard ABCs published by
# the standard "collections.abc" module *PLUS*...
COLLECTIONS_ABC_BASENAMES +
# List of the unqualified basenames of all ancillary ABCs *NOT*
# published by the standard "collections.abc" module but nonetheless
# supported by the standard "typing" module.
['Deque']
)

if nparams:
sub = typing_abc[(list,) * nparams]
sup = typing_abc[(Sequence,) * nparams]
else:
sub = typing_abc
sup = typing_abc
# ..................{ HINTS ~ abcs }..................
# For the unqualified basename of each standard ABCs supported by the
# standard "typing" module...
#
# Note this also constitutes a smoke test (i.e., high-level test validating
# core functionality) for whether the DOOR API supports standard abstract
# base classes (ABCs). Smoke out those API inconsistencies, pytest!
for TYPING_ABC_BASENAME in TYPING_ABC_BASENAMES:
#FIXME: This logic is likely to fail under a future Python release.
# Type hint factory published by the "typing" module corresponding to
# this ABC if any *OR* "None" otherwise (i.e., if "typing" publishes
# *NO* such type hint factory).
TypingABC = getattr(typing, TYPING_ABC_BASENAME, None)

# If "typing" publishes *NO* such type hint factory, silently ignore
# this ABC and continue to the next.
if TypingABC is None:
continue
# Else, "typing" publishes this type hint factory.

# Number of type variables parametrizing this ABC, defined as either...
TYPING_ABC_TYPEVARS_LEN = (
# If the active Python interpreter targets Python >= 3.9, a private
# instance variable of this type hint factory yielding this
# metadata. Under Python >= 3.9, unsubscripted type hint factories
# are *NOT* parametrized by type variables.
TypingABC._nparams
if IS_PYTHON_AT_LEAST_3_9 else
# Else, the active Python interpreter targets Python < 3.9. In this
# case, the number of type variables directly parametrizing this
# ABC.
len(get_hint_pep_typevars(TypingABC))
)

HINT_SUBHINT_CASES.append((sub, sup, True))
# If this ABC is parametrized by one or more type variables, exercise
# that this ABC subscripted by one or more arbitrary concrete types is a
# non-trivial subhint of this same ABC subscripted by one or more
# arbitrary different ABCs of those concrete types.
if TYPING_ABC_TYPEVARS_LEN:
subhint = TypingABC[(list,) * TYPING_ABC_TYPEVARS_LEN]
superhint = TypingABC[(Sequence,) * TYPING_ABC_TYPEVARS_LEN]
# Else, this ABC is parametrized by *NO* type variables. In this case,
# fallback to exercising that this ABC is a trivial subhint of itself.
else:
subhint = TypingABC
superhint = TypingABC

# Append a new hint subhint case exercising that this subhint is
# actually a subhint of this superhint.
HINT_SUBHINT_CASES.append((subhint, superhint, True))

# ..................{ HINTS ~ version }..................
# If the active Python interpreter targets Python >= 3.9 and thus
Expand Down

0 comments on commit a8fe768

Please sign in to comment.