Skip to content

Commit

Permalink
"typing_extensions.Annotated" x 7.
Browse files Browse the repository at this point in the history
This commit is the next in a commit chain adding transparent support
for the third-party `typing_extensions.Annotated` type hint back-ported
to Python < 3.9, en route to resolving #34. Once finalized, this will
enable usage of beartype validators under Python < 3.9 via this hint.
Specifically, this commit refactors the codebase to dramatically
simplify handling of the `typing.NoReturn` singleton.
(*Allegro allegations pull aggro!*)
  • Loading branch information
leycec committed Jun 4, 2021
1 parent 1dcbe4b commit 45f115f
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 262 deletions.
19 changes: 6 additions & 13 deletions beartype/_decor/_cache/cachehint.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@
temporarily caching hints in an LRU cache is pointless, as there are *no*
space savings in dropping stale references to unused hints.
.. _PEP 484:
https://www.python.org/dev/peps/pep-0484
.. _PEP 563:
https://www.python.org/dev/peps/pep-0563
.. _PEP 585:
Expand Down Expand Up @@ -127,11 +125,11 @@ def coerce_hint_pep(
* If this hint is a **PEP-noncompliant tuple union** (i.e., tuple of one or
more standard classes and forward references to standard classes):
* Coerces this tuple union into the equivalent `PEP 484`_-compliant
* Coerces this tuple union into the equivalent :pep:`484`-compliant
union.
* Replaces this tuple union in the ``__annotations__`` dunder tuple of
this callable with this `PEP 484`_-compliant union.
* Returns this `PEP 484`_-compliant union.
this callable with this :pep:`484`-compliant union.
* Returns this :pep:`484`-compliant union.
* Else if this hint is already PEP-compliant, preserves and returns this
hint unmodified as is.
Expand Down Expand Up @@ -171,9 +169,6 @@ def coerce_hint_pep(
* A PEP-noncompliant type hint.
* A supported PEP-compliant type hint.
.. _PEP 484:
https://www.python.org/dev/peps/pep-0484
'''

# If this hint is a PEP-noncompliant tuple union, coerce this union into
Expand Down Expand Up @@ -230,10 +225,10 @@ def cache_hint_nonpep563(
one or more standard classes and forward references to standard
classes), this function:
* Coerces this tuple union into the equivalent `PEP 484`_-compliant
* Coerces this tuple union into the equivalent :pep:`484`-compliant
union.
* Replaces this tuple union in the ``__annotations__`` dunder tuple of
this callable with this `PEP 484`_-compliant union.
this callable with this :pep:`484`-compliant union.
#. Else (i.e., if this hint is neither PEP-compliant nor -noncompliant and
thus unsupported by :mod:`beartype`), this function raises an exception.
Expand All @@ -255,7 +250,7 @@ def cache_hint_nonpep563(
------
This function does *not* bother caching **self-cached type hints** (i.e.,
type hints that externally cache themselves), as these hints are already
cached elsewhere. Self-cached type hints include most `PEP 484`_-compliant
cached elsewhere. Self-cached type hints include most :pep:`484`-compliant
type hints declared by the :mod:`typing` module, which means that
subscripting type hints declared by the :mod:`typing` module with the same
child type hints reuses the exact same internally cached objects rather
Expand Down Expand Up @@ -334,8 +329,6 @@ def cache_hint_nonpep563(
* A PEP-noncompliant type hint.
* A supported PEP-compliant type hint.
.. _PEP 484:
https://www.python.org/dev/peps/pep-0484
.. _PEP 563:
https://www.python.org/dev/peps/pep-0563
.. _PEP 585:
Expand Down
27 changes: 14 additions & 13 deletions beartype/_decor/_code/_pep/_pephint.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from beartype.roar import (
BeartypeDecorHintPepException,
BeartypeDecorHintPepUnsupportedException,
BeartypeDecorHintPep484Exception,
BeartypeDecorHintPep593Exception,
)
from beartype._cave._cavefast import NoneType
Expand All @@ -34,6 +33,8 @@
from beartype._decor._code.codesnip import (
ARG_NAME_GETRANDBITS,
ARG_NAME_TYPISTRY,
VAR_NAME_PREFIX_PITH,
VAR_NAME_PITH_ROOT,
)
from beartype._decor._code._pep._pepmagic import (
FUNC_WRAPPER_LOCAL_LABEL,
Expand Down Expand Up @@ -64,8 +65,6 @@
PEP_CODE_HINT_FORWARDREF_UNQUALIFIED_PLACEHOLDER_PREFIX,
PEP_CODE_HINT_FORWARDREF_UNQUALIFIED_PLACEHOLDER_SUFFIX,
PEP_CODE_PITH_ASSIGN_EXPR,
PEP_CODE_PITH_NAME_PREFIX,
PEP_CODE_PITH_ROOT_NAME,
PEP484_CODE_CHECK_HINT_UNION_CHILD_PEP,
PEP484_CODE_CHECK_HINT_UNION_CHILD_NONPEP,
PEP484_CODE_CHECK_HINT_UNION_PREFIX,
Expand Down Expand Up @@ -158,7 +157,7 @@
from beartype._util.text.utiltextrepr import represent_object
from collections.abc import Callable
from random import getrandbits
from typing import Tuple, NoReturn
from typing import Tuple

# See the "beartype.cave" submodule for further commentary.
__all__ = ['STAR_IMPORTS_CONSIDERED_HARMFUL']
Expand Down Expand Up @@ -332,7 +331,7 @@ def pep_code_check_hint(

# Python code snippet evaluating to the current passed parameter or return
# value to be type-checked against the root hint.
pith_root_expr = PEP_CODE_PITH_ROOT_NAME
pith_root_expr = VAR_NAME_PITH_ROOT

# ..................{ HINT ~ current }..................
# Currently visited hint.
Expand Down Expand Up @@ -455,7 +454,7 @@ def pep_code_check_hint(
# * The currently visited hint is *NOT* the root hint (i.e., "hint_root").
# If the currently visited hint is the root hint, the current pith has
# already been localized to a local variable whose name is the value of
# the "PEP_CODE_PITH_ROOT_NAME" string global and thus need *NOT* be
# the "VAR_NAME_PITH_ROOT" string global and thus need *NOT* be
# relocalized to another local variable using an assignment expression.
#
# This is a necessary and sufficient condition for deciding whether a
Expand Down Expand Up @@ -1023,7 +1022,7 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
# Reduce the current pith expression to the name of this local
# variable.
pith_curr_assigned_expr = (
f'{PEP_CODE_PITH_NAME_PREFIX}'
f'{VAR_NAME_PREFIX_PITH}'
f'{pith_curr_assign_expr_name_counter}'
)

Expand Down Expand Up @@ -1334,12 +1333,14 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
# previously called higher-level pep_code_check_return()
# function has already handled the single case in which this
# hint is valid, implying this hint to be invalid here.
if hint_curr is NoReturn:
raise BeartypeDecorHintPep484Exception(
f'{hint_curr_label} {repr(hint_curr)} child '
f'"typing.NoReturn" invalid (i.e., "typing.NoReturn" '
f'valid only as non-nested return annotation).'
)

#FIXME: This shouldn't be needed anymore. Excise if true!
# if hint_curr is NoReturn:
# raise BeartypeDecorHintPep484Exception(
# f'{hint_curr_label} {repr(hint_curr)} child '
# f'"typing.NoReturn" invalid (i.e., "typing.NoReturn" '
# f'valid only as non-nested return annotation).'
# )
# Else, this hint is *NOT* "NoReturn".

# ............{ ORIGIN }............
Expand Down
68 changes: 12 additions & 56 deletions beartype/_decor/_code/_pep/_pepsnip.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
ARG_NAME_FUNC,
ARG_NAME_RAISE_EXCEPTION,
VAR_NAME_ARGS_LEN,
VAR_NAME_PITH_ROOT,
VAR_NAME_RANDOM_INT,
)
from inspect import Parameter
Expand All @@ -37,21 +38,6 @@
'''


PEP_CODE_PITH_NAME_PREFIX = '__beartype_pith_'
'''
Substring prefixing all local variables providing a **pith** (i.e., either the
current parameter or return value *or* item contained the current parameter or
return value being type-checked by the current call).
'''

# ....................{ PITH ~ root }....................
PEP_CODE_PITH_ROOT_NAME = f'{PEP_CODE_PITH_NAME_PREFIX}0'
'''
Name of the local variable providing the **root pith** (i.e., value of the
current parameter or return value being type-checked by the current call).
'''


PEP_CODE_PITH_ROOT_PARAM_NAME_PLACEHOLDER = '?|PITH_ROOT_NAME`^'
'''
Placeholder source substring to be globally replaced by the **root pith name**
Expand Down Expand Up @@ -88,28 +74,28 @@
Parameter.POSITIONAL_OR_KEYWORD: f'''
# Localize this positional or keyword parameter if passed *OR* to the
# sentinel value "__beartype_raise_exception" guaranteed to never be passed.
{PEP_CODE_PITH_ROOT_NAME} = (
{VAR_NAME_PITH_ROOT} = (
args[{{arg_index}}] if {VAR_NAME_ARGS_LEN} > {{arg_index}} else
kwargs.get({{arg_name!r}}, {ARG_NAME_RAISE_EXCEPTION})
)
# If this parameter was passed...
if {PEP_CODE_PITH_ROOT_NAME} is not {ARG_NAME_RAISE_EXCEPTION}:''',
if {VAR_NAME_PITH_ROOT} is not {ARG_NAME_RAISE_EXCEPTION}:''',

# Snippet localizing any keyword-only parameter (e.g., "*, kwarg") by
# lookup in the wrapper's variadic "**kwargs" dictionary. (See above.)
Parameter.KEYWORD_ONLY: f'''
# Localize this keyword-only parameter if passed *OR* to the sentinel value
# "__beartype_raise_exception" guaranteed to never be passed.
{PEP_CODE_PITH_ROOT_NAME} = kwargs.get({{arg_name!r}}, {ARG_NAME_RAISE_EXCEPTION})
{VAR_NAME_PITH_ROOT} = kwargs.get({{arg_name!r}}, {ARG_NAME_RAISE_EXCEPTION})
# If this parameter was passed...
if {PEP_CODE_PITH_ROOT_NAME} is not {ARG_NAME_RAISE_EXCEPTION}:''',
if {VAR_NAME_PITH_ROOT} is not {ARG_NAME_RAISE_EXCEPTION}:''',

# Snippet iteratively localizing all variadic positional parameters.
Parameter.VAR_POSITIONAL: f'''
# For all passed positional variadic parameters...
for {PEP_CODE_PITH_ROOT_NAME} in args[{{arg_index!r}}:]:''',
for {VAR_NAME_PITH_ROOT} in args[{{arg_index!r}}:]:''',
}
'''
Dictionary mapping from the type of each callable parameter supported by the
Expand All @@ -121,7 +107,7 @@
PEP_CODE_CHECK_RETURN_PREFIX = f'''
# Call this function with all passed parameters and localize the value
# returned from this call.
{PEP_CODE_PITH_ROOT_NAME} = {ARG_NAME_FUNC}(*args, **kwargs)
{VAR_NAME_PITH_ROOT} = {ARG_NAME_FUNC}(*args, **kwargs)
# Noop required to artifically increase indentation level. Note that
# CPython implicitly optimizes this conditional away - which is nice.
Expand All @@ -147,7 +133,7 @@


PEP_CODE_CHECK_RETURN_SUFFIX = f'''
return {PEP_CODE_PITH_ROOT_NAME}'''
return {VAR_NAME_PITH_ROOT}'''
'''
PEP-compliant code snippet returning from the wrapper function the successfully
type-checked value returned from the decorated callable.
Expand All @@ -157,30 +143,6 @@
:data:`PEP_CODE_GET_RETURN` snippet.
'''

# ....................{ RETURN ~ noreturn }....................
PEP484_CODE_CHECK_NORETURN = f'''
# Call this function with all passed parameters and localize the value
# returned from this call.
{PEP_CODE_PITH_ROOT_NAME} = {ARG_NAME_FUNC}(*args, **kwargs)
# Since this function annotated by "typing.NoReturn" successfully returned
# a value rather than raising an exception or halting the active Python
# interpreter, unconditionally raise an exception.
{ARG_NAME_RAISE_EXCEPTION}(
func={ARG_NAME_FUNC},
pith_name={PEP_CODE_PITH_ROOT_PARAM_NAME_PLACEHOLDER},
pith_value={PEP_CODE_PITH_ROOT_NAME},
)'''
'''
`PEP 484`_-compliant code snippet calling the decorated callable annotated by
the :attr:`typing.NoReturn` singleton and raising an exception if this call
successfully returned a value rather than raising an exception or halting the
active Python interpreter.
.. _PEP 484:
https://www.python.org/dev/peps/pep-0484
'''

# ....................{ HINT ~ placeholder : child }....................
PEP_CODE_HINT_CHILD_PLACEHOLDER_PREFIX = '@['
'''
Expand Down Expand Up @@ -241,7 +203,7 @@
{ARG_NAME_RAISE_EXCEPTION}(
func={ARG_NAME_FUNC},
pith_name={PEP_CODE_PITH_ROOT_PARAM_NAME_PLACEHOLDER},
pith_value={PEP_CODE_PITH_ROOT_NAME},{{random_int_if_any}}
pith_value={VAR_NAME_PITH_ROOT},{{random_int_if_any}}
)
'''
'''
Expand Down Expand Up @@ -290,7 +252,7 @@
{indent_curr} isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and'''
'''
PEP-compliant code snippet prefixing all code type-checking the current pith
against each unerased pseudo-superclass subclassed by a `PEP 484`_-compliant
against each unerased pseudo-superclass subclassed by a :pep:`484`-compliant
**generic** (i.e., PEP-compliant type hint subclassing a combination of one or
more of the :mod:`typing.Generic` superclass, the :mod:`typing.Protocol`
superclass, and/or other :mod:`typing` non-class objects).
Expand All @@ -301,21 +263,15 @@
efficiently defer its interpolation until the complete PEP-compliant code
snippet type-checking the current pith against *all* subscripted arguments of
this parent type has been generated.
.. _PEP 484:
https://www.python.org/dev/peps/pep-0484
'''


PEP_CODE_CHECK_HINT_GENERIC_SUFFIX = '''
{indent_curr})'''
'''
PEP-compliant code snippet suffixing all code type-checking the current pith
against each unerased pseudo-superclass subclassed by a `PEP 484`_-compliant
against each unerased pseudo-superclass subclassed by a :pep:`484`-compliant
generic.
.. _PEP 484:
https://www.python.org/dev/peps/pep-0484
'''


Expand All @@ -325,7 +281,7 @@
{{indent_curr}} {hint_child_placeholder} and'''
'''
PEP-compliant code snippet type-checking the current pith against the current
unerased pseudo-superclass subclassed by a `PEP 484`_-compliant generic.
unerased pseudo-superclass subclassed by a :pep:`484`-compliant generic.
Caveats
----------
Expand Down

0 comments on commit 45f115f

Please sign in to comment.