Skip to content

Commit

Permalink
Python 3.13 x 3.
Browse files Browse the repository at this point in the history
This commit is the next in a commit chain officially supporting the
upcoming release of Python 3.13, en-route to resolving future issue #387
kindly submitted by core Python `typing` mega-boss @JelleZijlstra (Jelle
Zijlstra). Specifically, this commit resolves *all* remaining
compatibility issues with respect to Python 3.13 – except for static
type-checking under `mypy` and `pyright`, which remains an open (but
largely boring) question. For all intents and purposes, @beartype now
officially supports Python 3.13! *Wohoooo.* (*Fully syntactic synapses snap sinfully!*)
  • Loading branch information
leycec committed Jun 1, 2024
1 parent 0b4453f commit c8c4f05
Show file tree
Hide file tree
Showing 21 changed files with 428 additions and 239 deletions.
11 changes: 4 additions & 7 deletions beartype/_check/forward/fwdmain.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,14 +413,11 @@ def resolve_hint(
# Ergo, the local scopes for parent classes of this class
# (including the root decorated class) are irrelevant.
cls_curr_locals = get_type_locals(
cls=cls_curr,
exception_cls=exception_cls,
)
cls=cls_curr, exception_cls=exception_cls)

# Forcefully merge this local scope into the current
# local scope, implicitly overwriting any locals of the
# same name. Class locals necessarily assume lexical
# precedence over:
# Forcefully merge this local scope into the current local
# scope, implicitly overwriting any locals of the same name.
# Class locals necessarily assume lexical precedence over:
# * These classes themselves.
# * Locals defined by higher-level parent classes.
# * Locals defined by closures defining these classes.
Expand Down
4 changes: 2 additions & 2 deletions beartype/_conf/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
BeartypeCallHintReturnViolation,
BeartypeDoorHintViolation,
)
from beartype.roar._roarwarn import (
_BeartypeConfReduceDecoratorExceptionToWarningDefault)
# from beartype.roar._roarwarn import (
# _BeartypeConfReduceDecoratorExceptionToWarningDefault)
from beartype.typing import Optional
from beartype._cave._cavemap import NoneTypeOr
from beartype._conf.confenum import (
Expand Down
177 changes: 177 additions & 0 deletions beartype/_data/hint/pep/sign/datapepsignmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#!/usr/bin/env python3
# --------------------( LICENSE )--------------------
# Copyright (c) 2014-2024 Beartype authors.
# See "LICENSE" for further details.

'''
Project-wide **type hint sign mappings** (i.e., dictionary globals mapping from
instances of the :class:`beartype._data.hint.pep.sign.datapepsigncls.HintSign`
class to various metadata associated with categories of type hints).
This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS }....................
from beartype.typing import Dict
from beartype._data.hint.pep.sign.datapepsigncls import HintSign
from beartype._data.hint.pep.sign.datapepsigns import (
HintSignAbstractSet,
HintSignAsyncContextManager,
HintSignAsyncGenerator,
HintSignAsyncIterator,
HintSignAsyncIterable,
HintSignAwaitable,
HintSignChainMap,
HintSignCollection,
HintSignContainer,
HintSignContextManager,
HintSignCoroutine,
HintSignCounter,
HintSignDefaultDict,
HintSignDeque,
HintSignDict,
HintSignFrozenSet,
HintSignGenerator,
HintSignItemsView,
HintSignIterable,
HintSignIterator,
HintSignKeysView,
HintSignList,
HintSignMapping,
HintSignMappingView,
HintSignMatch,
HintSignMutableMapping,
HintSignMutableSequence,
HintSignMutableSet,
HintSignOrderedDict,
HintSignPattern,
HintSignReversible,
HintSignSequence,
HintSignSet,
HintSignType,
HintSignValuesView,
)

# ....................{ PRIVATE ~ globals }....................
# Note that the builtin "range" class initializer "range.__init__(start, stop)"
# is effectively instantiated as [start, stop) -- that is to say, such that:
# * The initial "start" integer is *INCLUSIVE* (i.e., the instantiated range
# includes this integer).
# * The final "stop" integer is *EXCLUSIVE* (i.e., the instantiated range
# excludes this integer).

_ARGS_LEN_1 = range(1, 2) # == [1, 2) == [1, 1]
'''
**One-argument length range** (i.e., :class:`range` instance effectively
equivalent to the integer ``1``, describing type hint factories subscriptable by
exactly one child type hint).
'''


_ARGS_LEN_2 = range(2, 3) # == [2, 3) == [2, 2]
'''
**Two-argument length range** (i.e., :class:`range` instance effectively
equivalent to the integer ``2``, describing type hint factories subscriptable by
exactly two child type hints).
'''


_ARGS_LEN_3 = range(3, 4) # == [3, 4) == [3, 3]
'''
**Three-argument length range** (i.e., :class:`range` instance effectively
equivalent to the integer ``3``, describing type hint factories subscriptable by
exactly three child type hints).
'''


_ARGS_LEN_1_OR_2 = range(1, 3) # == [1, 3) == [1, 2]
'''
**One- or two-argument length range** (i.e., :class:`range` instance effectively
equivalent to the integer range ``[1, 2]``, describing type hint factories
subscriptable by either one or two child type hints).
'''

# ....................{ SIGNS ~ origin : args }....................
# Fully initialized by the _init() function below.
HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE: Dict[HintSign, range] = {
# Type hint factories subscriptable by exactly one child type hint.
HintSignAbstractSet: _ARGS_LEN_1,
HintSignAsyncIterable: _ARGS_LEN_1,
HintSignAsyncIterator: _ARGS_LEN_1,
HintSignAwaitable: _ARGS_LEN_1,
HintSignCollection: _ARGS_LEN_1,
HintSignContainer: _ARGS_LEN_1,
HintSignCounter: _ARGS_LEN_1,
HintSignDeque: _ARGS_LEN_1,
HintSignFrozenSet: _ARGS_LEN_1,
HintSignIterable: _ARGS_LEN_1,
HintSignIterator: _ARGS_LEN_1,
HintSignKeysView: _ARGS_LEN_1,
HintSignList: _ARGS_LEN_1,
HintSignMatch: _ARGS_LEN_1,
HintSignMappingView: _ARGS_LEN_1,
HintSignMutableSequence: _ARGS_LEN_1,
HintSignMutableSet: _ARGS_LEN_1,
HintSignPattern: _ARGS_LEN_1,
HintSignReversible: _ARGS_LEN_1,
HintSignSequence: _ARGS_LEN_1,
HintSignSet: _ARGS_LEN_1,
HintSignType: _ARGS_LEN_1,
HintSignValuesView: _ARGS_LEN_1,

# Type hint factories subscriptable by exactly two child type hints.
HintSignAsyncGenerator: _ARGS_LEN_2,
HintSignChainMap: _ARGS_LEN_2,
HintSignDefaultDict: _ARGS_LEN_2,
HintSignDict: _ARGS_LEN_2,
HintSignItemsView: _ARGS_LEN_2,
HintSignMapping: _ARGS_LEN_2,
HintSignMutableMapping: _ARGS_LEN_2,
HintSignOrderedDict: _ARGS_LEN_2,

# Type hint factories subscriptable by exactly three child type hints.
HintSignCoroutine: _ARGS_LEN_3,
HintSignGenerator: _ARGS_LEN_3,
}
'''
Dictionary mapping from each sign uniquely identifying a PEP-compliant type hint
factory originating from an **isinstanceable origin type** (i.e., isinstanceable
class such that *all* objects satisfying type hints created by subscripting this
factory are instances of this class) to this factory's **argument length range**
(i.e., :class:`range` instance describing the minimum and maximum number of
child type hints that may subscript this factory).
'''

# ....................{ PRIVATE ~ main }....................
def _init() -> None:
'''
Initialize this submodule.
'''

# Defer function-specific imports.
from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_13

# If the active Python interpreter targets Python >= 3.13...
if IS_PYTHON_AT_LEAST_3_13:
# Add all signs uniquely identifying one- or two-argument type hint
# factories under Python >= 3.13, which generalized various one-argument
# type hint factories to accept an additional optional child type hint
# via "PEP 696 – Type Defaults for Type Parameters".
HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE.update({
HintSignAsyncContextManager: _ARGS_LEN_1_OR_2,
HintSignContextManager: _ARGS_LEN_1_OR_2,
})
# Else, the active Python interpreter targets Python <= 3.12. In this
# case...
else:
# Add all signs uniquely identifying two-argument type hint factories
# under Python <= 3.12.
HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE.update({
HintSignAsyncContextManager: _ARGS_LEN_1,
HintSignContextManager: _ARGS_LEN_1,
})
# print(f'HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE: {HINT_SIGN_ORIGIN_ISINSTANCEABLE_TO_ARGS_LEN_RANGE}')


# Initialize this submodule.
_init()
4 changes: 3 additions & 1 deletion beartype/_data/hint/pep/sign/datapepsignset.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,9 @@
'''

# ....................{ SIGNS ~ origin : args }....................
#FIXME: *OBSOLETE.* Excise up all "HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_*"
#sets, please.

# Fully initialized and coerced to a "frozenset" by the _init() function below.
HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1: _FrozenSetHintSign = { # type: ignore[assignment]
HintSignAbstractSet,
Expand Down Expand Up @@ -448,7 +451,6 @@
# Fully initialized and coerced to a "frozenset" by the _init() function below.
HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2: _FrozenSetHintSign = { # type: ignore[assignment]
HintSignAsyncGenerator,
# HintSignCallable, # defined explicitly below
HintSignChainMap,
HintSignDefaultDict,
HintSignDict,
Expand Down
33 changes: 31 additions & 2 deletions beartype/_util/func/utilfuncscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
Optional,
)
from beartype._util.utilobject import get_object_basename_scoped
from beartype._data.func.datafunccodeobj import FUNC_CODEOBJ_NAME_MODULE
from beartype._data.hint.datahinttyping import (
LexicalScope,
TypeException,
Expand Down Expand Up @@ -215,16 +216,16 @@ def get_func_locals(

# ..................{ IMPORTS }..................
# Avoid circular import dependencies.
from beartype._data.func.datafunccodeobj import FUNC_CODEOBJ_NAME_MODULE
from beartype._util.func.utilfunccodeobj import get_func_codeobj_or_none
from beartype._util.func.utilfuncframe import iter_frames
from beartype._util.func.utilfunctest import is_func_nested
from beartype._util.module.utilmodget import get_object_module_name_or_none

# ..................{ NOOP }..................
# Fully-qualified name of the module declaring the passed callable if that
# callable was physically declared by an on-disk module *OR* "None"
# otherwise (i.e., if that callable was dynamically declared in-memory).
func_module_name = func.__module__
func_module_name = get_object_module_name_or_none(func)

# Note that we intentionally return the local scope for this wrapper rather
# than wrappee callable, as local scope can *ONLY* be obtained by
Expand Down Expand Up @@ -494,6 +495,34 @@ def get_func_locals(
# body of this parent callable. Well, isn't that special?
func_scope = func_frame.f_locals

# If this local scope is *NOT* a "dict" instance, coerce this local
# scope into a "dict" instance. Why? Several justifiable reasons:
# * This getter is annotated as returning a "LexicalScope", which is
# currently just a readable alias for "DictStrToAny", which is
# itself an efficiency alias for "Dict[Str, object]". Ergo, static
# type-checkers expect this getter to return "dict" instances.
# * Under Python <= 3.12, the "func_frame.f_locals" instance
# variable actually is a "dict" instance.
# * Under Python >= 3.12, the "func_frame.f_locals" instance
# variable actually is instead a "mappingproxy" instance. Although
# interchangeable for many purposes, "dict" and "mappingproxy"
# instances are *NOT* perfectly interchangeable. In particular,
# callers of this function frequently pass this local scope to
# the dict.update() method -- which expects the passed mapping to
# also be a "dict" instance: e.g.,
# cls_curr_locals = get_type_locals(
# cls=cls_curr, exception_cls=exception_cls)
# > func_locals.update(cls_curr_locals)
# E TypeError: update() argument must be dict or another FrameLocalsProxy
# Why? No idea. Ideally, the dict.update() method would accept
# arbitrary mappings -- but it doesn't. Since it doesn't, we have
# *NO* recourse but to preserve forward compatibility with future
# Python versions by coercing non-"dict" to "dict" instances here
# on behalf of the caller. It is what it is. We sigh! *sigh*
if not isinstance(func_scope, dict):
func_scope = dict(func_scope)
# Else, this local scope is already a "dict" instance.

# Halt iteration.
break
# Else, that callable does *NOT* embody the current lexical scope to be
Expand Down
5 changes: 3 additions & 2 deletions beartype/_util/hint/pep/proposal/pep484585/utilpep484585.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,14 @@ def get_hint_pep484585_args(

# Human-readable noun describing the grammatically correct plurality of
# the number of expected child type hints. English! Why!?!?
exception_noun = 'argument' if args_len == 1 else 'arguments'
exception_noun = (
'child type hint' if args_len == 1 else 'child type hints')

# Raise an exception.
raise BeartypeDecorHintPep585Exception(
f'{exception_prefix}PEP 585 type hint {repr(hint)} '
f'not subscripted (indexed) by {args_len} {exception_noun} (i.e., '
f'subscripted by {len(hint_args)} != {args_len} arguments).'
f'subscripted by {len(hint_args)} != {args_len} child type hints).'
)
# Else, this hint is subscripted by the expected number of child type hints.

Expand Down
Loading

0 comments on commit c8c4f05

Please sign in to comment.