Repository URL to install this package:
|
Version:
0.18.5 ▾
|
#!/usr/bin/env python3
# --------------------( LICENSE )--------------------
# Copyright (c) 2014-2024 Beartype authors.
# See "LICENSE" for further details.
'''
Project-wide **PEP-agnostic type hint sanitizers** (i.e., high-level callables
converting type hints from one format into another, either permanently or
temporarily and either losslessly or in a lossy manner).
This private submodule is *not* intended for importation by downstream callers.
'''
# ....................{ IMPORTS }....................
from beartype.typing import (
Any,
Optional,
)
from beartype._check.checkcall import BeartypeCall
from beartype._check.convert.convcoerce import (
coerce_func_hint_root,
coerce_hint_any,
coerce_hint_root,
)
from beartype._check.convert.convreduce import reduce_hint
from beartype._conf.confcls import BeartypeConf
from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER
from beartype._data.func.datafuncarg import ARG_NAME_RETURN
from beartype._data.hint.datahinttyping import TypeStack
from beartype._util.cache.map.utilmapbig import CacheUnboundedStrong
from beartype._util.hint.pep.proposal.pep484585.utilpep484585func import (
reduce_hint_pep484585_func_return)
from beartype._util.hint.utilhinttest import is_hint_ignorable
# ....................{ SANIFIERS ~ root }....................
#FIXME: Unit test us up, please.
def sanify_hint_root_func(
# Mandatory parameters.
hint: object,
pith_name: str,
bear_call: BeartypeCall,
# Optional parameters.
exception_prefix: str = EXCEPTION_PLACEHOLDER,
) -> object:
'''
PEP-compliant type hint sanified (i.e., sanitized) from the passed **root
type hint** (i.e., possibly PEP-noncompliant type hint annotating the
parameter or return with the passed name of the passed callable) if this
hint is reducible *or* this hint as is otherwise (i.e., if this hint is
irreducible).
Specifically, this function:
* 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
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.
* Else if this hint is already PEP-compliant, preserves and returns this
hint unmodified as is.
* Else (i.e., if this hint is neither PEP-compliant nor -noncompliant and
thus invalid as a type hint), raise an exception.
Caveats
-------
This sanifier *cannot* be meaningfully memoized, since the passed type hint
is *not* guaranteed to be cached somewhere. Only functions passed cached
type hints can be meaningfully memoized. Even if this function *could* be
meaningfully memoized, there would be no benefit; this function is only
called once per parameter or return of the currently decorated callable.
This sanifier is intended to be called *after* all possibly
:pep:`563`-compliant **deferred type hints** (i.e., type hints persisted as
evaluatable strings rather than actual type hints) annotating this callable
if any have been evaluated into actual type hints.
Parameters
----------
hint : object
Possibly PEP-noncompliant root type hint to be sanified.
cls_stack : TypeStack, optional
**Type stack** (i.e., either a tuple of the one or more
:func:`beartype.beartype`-decorated classes lexically containing the
class variable or method annotated by this hint *or* :data:`None`).
Defaults to :data:`None`.
pith_name : str
Either:
* If this hint annotates a parameter, the name of that parameter.
* If this hint annotates the return, ``"return"``.
bear_call : BeartypeCall
Decorated callable directly annotated by this hint.
exception_prefix : str, optional
Human-readable label prefixing exception messages raised by this
function. Defaults to :data:`EXCEPTION_PLACEHOLDER`.
Returns
-------
object
Either:
* If this hint is PEP-noncompliant, a PEP-compliant type hint converted
from this hint.
* If this hint is PEP-compliant, this hint unmodified as is.
Raises
------
BeartypeDecorHintNonpepException
If this object is neither:
* A PEP-noncompliant type hint.
* A supported PEP-compliant type hint.
'''
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: Synchronize with the sanify_hint_root_statement() sanitizer.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# PEP-compliant type hint coerced (i.e., permanently converted in the
# annotations dunder dictionary of the passed callable) from this possibly
# PEP-noncompliant type hint if this hint is coercible *OR* this hint as is
# otherwise. Since the passed hint is *NOT* necessarily PEP-compliant,
# perform this coercion *BEFORE* validating this hint to be PEP-compliant.
hint = bear_call.func_wrappee.__annotations__[pith_name] = (
coerce_func_hint_root(
hint=hint,
pith_name=pith_name,
bear_call=bear_call,
exception_prefix=exception_prefix,
)
)
# If this hint annotates the return, reduce this hint to a simpler hint if
# this hint is either PEP 484- or 585-compliant *AND* requires reduction
# (e.g., from "Coroutine[None, None, str]" to just "str"). Raise an
# exception if this hint is contextually invalid for this callable (e.g.,
# generator whose return is *NOT* annotated as "Generator[...]").
#
# Perform this reduction *BEFORE* performing subsequent tests (e.g., to
# accept "Coroutine[None, None, typing.NoReturn]" as expected).
#
# Note that this logic *ONLY* pertains to callables (rather than statements)
# and is thus *NOT* performed by the sanify_hint_root_statement() sanitizer.
if pith_name == ARG_NAME_RETURN:
hint = reduce_hint_pep484585_func_return(
func=bear_call.func_wrappee, exception_prefix=exception_prefix)
# Else, this hint annotates a parameter.
# Reduce this hint to a lower-level PEP-compliant type hint if this hint is
# reducible *OR* this hint as is otherwise. Reductions simplify subsequent
# logic elsewhere by transparently converting non-trivial hints (e.g.,
# numpy.typing.NDArray[...]) into semantically equivalent trivial hints
# (e.g., beartype validators).
#
# Whereas the above coercion permanently persists for the duration of the
# active Python process (i.e., by replacing the original type hint in the
# annotations dunder dictionary of this callable), this reduction only
# temporarily persists for the duration of the current call stack. Why?
# Because hints explicitly coerced above are assumed to be either:
# * PEP-noncompliant and thus harmful (in the general sense).
# * PEP-compliant but semantically deficient and thus equally harmful (in
# the general sense).
#
# In either case, coerced type hints are generally harmful in *ALL* possible
# contexts for *ALL* possible consumers (including other competing runtime
# type-checkers). Reduced type hints, however, are *NOT* harmful in any
# sense whatsoever; they're simply non-trivial for @beartype to support in
# their current form and thus temporarily reduced in-memory into a more
# convenient form for beartype-specific type-checking purposes elsewhere.
#
# Note that parameters are intentionally passed positionally to both
# optimize memoization efficiency and circumvent memoization warnings.
hint = reduce_hint(
hint=hint,
conf=bear_call.conf,
cls_stack=bear_call.cls_stack,
pith_name=pith_name,
exception_prefix=exception_prefix,
)
# Return this sanified hint.
return hint
#FIXME: Unit test us up, please.
def sanify_hint_root_statement(
hint: object,
conf: BeartypeConf,
exception_prefix: str,
) -> object:
'''
PEP-compliant type hint sanified (i.e., sanitized) from the passed **root
type hint** (i.e., possibly PEP-noncompliant type hint that has *no* parent
type hint) if this hint is reducible *or* this hint as is otherwise (i.e.,
if this hint is irreducible).
This sanifier is principally intended to be called by a **statement-level
type-checker factory** (i.e., a function creating and returning a runtime
type-checker type-checking this hint, outside the context of any standard
type hinting annotation like a user-defined class variable, callable
parameter or return, or assignment statement). Such factories include:
* The private :func:`beartype._check.checkmake.make_func_tester` factory,
internally called by:
* The public :func:`beartype.door.is_bearable` function.
* The public :meth:`beartype.door.TypeHint.is_bearable` method.
Parameters
----------
hint : object
Possibly PEP-noncompliant root type hint to be sanified.
conf : BeartypeConf
**Beartype configuration** (i.e., self-caching dataclass encapsulating
all settings configuring type-checking for the passed object).
exception_prefix : str
Human-readable label prefixing the representation of this object in the
exception message.
Returns
-------
object
Either:
* If this hint is PEP-noncompliant, a PEP-compliant type hint converted
from this hint.
* If this hint is PEP-compliant, this hint unmodified as is.
Raises
------
BeartypeDecorHintNonpepException
If this object is neither:
* A PEP-noncompliant type hint.
* A supported PEP-compliant type hint.
See Also
--------
:func:`.sanify_hint_root_func`
Further details.
'''
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# CAUTION: Synchronize with the sanify_hint_root_func() sanitizer, please.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# PEP-compliant type hint coerced from this possibly PEP-noncompliant type
# hint if this hint is coercible *OR* this hint as is otherwise. Since the
# passed hint is *NOT* necessarily PEP-compliant, perform this coercion
# *BEFORE* validating this hint to be PEP-compliant.
hint = coerce_hint_root(hint=hint, exception_prefix=exception_prefix)
# Reduce this hint to a lower-level PEP-compliant type hint if this hint is
# reducible *OR* this hint as is otherwise. See
# sanify_hint_root_func() for further commentary.
hint = reduce_hint(hint=hint, conf=conf, exception_prefix=exception_prefix)
# Return this sanified hint.
return hint
# ....................{ SANIFIERS ~ any }....................
#FIXME: Unit test us up, please.
def sanify_hint_child_if_unignorable_or_none(*args, **kwargs) -> Any:
'''
Type hint sanified (i.e., sanitized) from the passed **possibly insane child
type hint** (i.e., hint transitively subscripting the root type hint
annotating a parameter or return of the currently decorated callable) if
this hint is both reducible and ignorable, this hint unmodified if this hint
is both irreducible and ignorable, and :data:`None` otherwise (i.e., if this
hint is ignorable).
This high-level sanifier effectively chains the lower-level
:func:`sanify_hilt_child` sanifier and
:func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester into a
single unified function, streamlining sanification and ignorability
detection throughout the codebase.
Note that a :data:`None` return unambiguously implies this hint to be
ignorable, even if the passed hint is itself :data:`None`. Why? Because if
the passed hint were :data:`None`, then a :pep:`484`-compliant reducer will
internally reduce this hint to ``type(None)``. After reduction, *all* hints
are guaranteed to be non-:data:`None`.
Parameters
----------
All passed arguments are passed as is to the lower-level
:func:`sanify_hilt_child` sanifier.
Returns
-------
object
Either:
* If the passed possibly insane child type hint is ignorable after
reduction to a sane child type hint, :data:`None`.
* Else, the sane child type hint to which this hint reduces.
'''
# Sane child hint sanified from this possibly insane child hint if this hint
# is reducible *OR* this hint as is otherwise (i.e., if irreducible).
hint_child = sanify_hint_child(*args, **kwargs)
# Return either "None" if this hint is ignorable or this hint otherwise.
return None if is_hint_ignorable(hint_child) else hint_child
def sanify_hint_child(
# Mandatory parameters.
hint: object,
conf: BeartypeConf,
exception_prefix: str,
# Optional parameters.
cls_stack: TypeStack = None,
pith_name: Optional[str] = None,
) -> Any:
'''
Type hint sanified (i.e., sanitized) from the passed **possibly insane child
type hint** (i.e., hint transitively subscripting the root type hint
annotating a parameter or return of the currently decorated callable) if
this hint is reducible *or* this hint unmodified otherwise (i.e., if this
hint is irreducible).
Parameters
----------
hint : object
Type hint to be sanified.
conf : BeartypeConf
**Beartype configuration** (i.e., self-caching dataclass encapsulating
all settings configuring type-checking for the passed object).
exception_prefix : str
Substring prefixing exception messages raised by this function.
cls_stack : TypeStack, optional
**Type stack** (i.e., either a tuple of the one or more
:func:`beartype.beartype`-decorated classes lexically containing the
class variable or method annotated by this hint *or* :data:`None`).
Defaults to :data:`None`.
pith_name : Optional[str], optional
Either:
* If this hint directly annotates a callable parameter (as the root type
hint of that parameter), the name of this parameter.
* If this hint directly annotates a callable return (as the root type
hint of that return), the magic string ``"return"``.
* Else, :data:`None`.
Defaults to :data:`None`.
Returns
-------
object
Type hint sanified from this possibly insane child type hint.
'''
# This sanifier covers the proper subset of logic performed by the
# sanify_hint_root_statement() sanifier applicable to child type hints.
# PEP-compliant type hint coerced (i.e., permanently converted in the
# annotations dunder dictionary of the passed callable) from this possibly
# PEP-noncompliant type hint if this hint is coercible *OR* this hint as is
# otherwise. Since the passed hint is *NOT* necessarily PEP-compliant,
# perform this coercion *BEFORE* validating this hint to be PEP-compliant.
hint = coerce_hint_any(hint)
# Return this hint reduced.
return reduce_hint(
hint=hint,
conf=conf,
cls_stack=cls_stack,
pith_name=pith_name,
exception_prefix=exception_prefix,
)
# ....................{ PRIVATE ~ mappings }....................
_HINT_REPR_TO_HINT = CacheUnboundedStrong()
'''
**Type hint cache** (i.e., thread-safe cache mapping from the machine-readable
representations of all non-self-cached type hints to those hints).**
This cache caches:
* :pep:`585`-compliant type hints, which do *not* cache themselves.
This cache does *not* cache:
* Type hints declared by the :mod:`typing` module, which implicitly cache
themselves on subscription thanks to inscrutable metaclass magic.
* :pep:`563`-compliant **deferred type hints** (i.e., type hints persisted as
evaluable strings rather than actual type hints). Ideally, this cache would
cache the evaluations of *all* deferred type hints. Sadly, doing so is
infeasible in the general case due to global and local namespace lookups
(e.g., ``Dict[str, int]`` only means what you think it means if an
importation resembling ``from typing import Dict`` preceded that type hint).
Design
------
**This dictionary is intentionally thread-safe.** Why? Because this dictionary
is used to modify the ``__attributes__`` dunder variable of arbitrary callables.
Since most of those callables are either module- or class-scoped, that variable
is effectively global. To prevent race conditions between competing threads
contending over that global variable, this dictionary *must* be thread-safe.
This dictionary is intentionally designed as a naive dictionary rather than a
robust LRU cache, for the same reasons that callables accepting hints are
memoized by the :func:`beartype._util.cache.utilcachecall.callable_cached`
rather than the :func:`functools.lru_cache` decorator. Why? Because:
* The number of different type hints instantiated across even worst-case
codebases is negligible in comparison to the space consumed by those hints.
* The :attr:`sys.modules` dictionary persists strong references to all
callables declared by previously imported modules. In turn, the
``func.__annotations__`` dunder dictionary of each such callable persists
strong references to all type hints annotating that callable. In turn, these
two statements imply that type hints are *never* garbage collected but
instead persisted for the lifetime of the active Python process. Ergo,
temporarily caching hints in an LRU cache is pointless, as there are *no*
space savings in dropping stale references to unused hints.
'''