Source code for beartype._conf.confcls

#!/usr/bin/env python3
# --------------------( LICENSE                            )--------------------
# Copyright (c) 2014-2024 Beartype authors.
# See "LICENSE" for further details.

'''
Beartype **configuration class hierarchy** (i.e., public dataclasses enabling
users to configure :mod:`beartype` with optional runtime behaviours).

Most of the public attributes defined by this private submodule are explicitly
exported to external users in our top-level :mod:`beartype.__init__` submodule.
This private submodule is *not* intended for direct importation by downstream
callers.
'''

# ....................{ TODO                               }....................
#FIXME: Generalize "warning_cls_on_decorator_exception", please. Specifically:
#* Deprecate "warning_cls_on_decorator_exception".
#* Define a new "decoration_exception_type: Optional[TypeException] = None"
#  parameter accepting *ANY* arbitrary exception rather than merely a warning.

#FIXME: [DOCOS] Document all newly defined configuration parameters in our
#reST-formatted docos, please -- including:
#* "claw_is_pep526".
#* "hint_overrides".
#* "violation_door_type".
#* "violation_param_type".
#* "violation_return_type".
#* "violation_type".
#* "violation_verbosity".
#* "warning_cls_on_decorator_exception".

# ....................{ IMPORTS                            }....................
from beartype.roar._roarwarn import (
    _BeartypeConfReduceDecoratorExceptionToWarningDefault,
)
from beartype.typing import (
    TYPE_CHECKING,
    Dict,
    Optional,
)
from beartype._conf.confenum import (
    BeartypeStrategy,
    BeartypeViolationVerbosity,
)
from beartype._conf.confoverrides import (
    BEARTYPE_HINT_OVERRIDES_EMPTY,
    BeartypeHintOverrides,
)
from beartype._conf.conftest import (
    default_conf_kwargs_after,
    default_conf_kwargs_before,
    die_if_conf_kwargs_invalid,
)
from beartype._conf._confget import get_is_color
from beartype._data.hint.datahinttyping import (
    BoolTristateUnpassable,
    DictStrToAny,
    TypeException,
    TypeWarning,
)
from beartype._data.func.datafuncarg import ARG_VALUE_UNPASSED
from beartype._util.utilobject import get_object_type_basename
from threading import Lock

# ....................{ CLASSES                            }....................
[docs]class BeartypeConf(object): ''' **Beartype configuration** (i.e., self-caching dataclass encapsulating all flags, options, settings, and other metadata configuring each type-checking operation performed by :mod:`beartype` -- including each decoration of a callable or class by the :func:`beartype.beartype` decorator). Attributes ---------- _claw_is_pep526 : bool :data:`True` only if type-checking **annotated variable assignments** (i.e., :pep:`526`-compliant assignments to local, global, class, and instance variables annotated by type hints) when importing modules under import hooks published by the :mod:`beartype.claw` subpackage. See also the :meth:`__new__` method docstring. _conf_args : tuple Tuple of the values of *all* possible keyword parameters (in arbitrary order) configuring this configuration. _conf_kwargs : Dict[str, object] Dictionary mapping from the names to values of *all* possible keyword parameters configuring this configuration. _hash : int Precomputed configuration hash returned by the :meth:`__hash__` dunder method for efficiency. _hint_overrides : Dict **Type hint overrides** (i.e., frozen dictionary mapping from arbitrary source to target type hints), enabling callers to lie to both their users and all other packages other than :mod:`beartype`. This dictionary enables callers to externally present a public API annotated by simplified type hints while internally instructing :mod:`beartype` to privately type-check that API under a completely different set of (typically more complicated) type hints. _is_color : Optional[bool] Tri-state boolean governing how and whether beartype colours **type-checking violations** (i.e., :class:`beartype.roar.BeartypeCallHintViolation` exceptions) with POSIX-compliant ANSI escape sequences for readability. Specifically, if this boolean is: * :data:`False`, beartype *never* colours type-checking violations raised by callables configured with this configuration. * :data:`True`, beartype *always* colours type-checking violations raised by callables configured with this configuration. * :data:`None`, beartype conditionally colours type-checking violations raised by callables configured with this configuration only when standard output is attached to an interactive terminal. _is_debug : bool :data:`True` only if debugging :mod:`beartype`. See also the :meth:`__new__` method docstring. _is_pep484_tower : bool :data:`True` only if enabling support for the :pep:`484`-compliant implicit numeric tower. See also the :meth:`__new__` method docstring. _is_violation_door_warn : bool :data:`True` only if :attr:`violation_door_type` is a warning subclass. Note that this is stored only as a negligible optimization to avoid needless recomputation of this boolean during code generation. _is_violation_param_warn : bool :data:`True` only if :attr:`violation_param_type` is a warning subclass. Note that this is stored only as a negligible optimization to avoid needless recomputation of this boolean during code generation. _is_violation_return_warn : bool :data:`True` only if :attr:`violation_return_type` is a warning subclass. Note that this is stored only as a negligible optimization to avoid needless recomputation of this boolean during code generation. _is_warning_cls_on_decorator_exception_set : bool :data:`True` only if the caller explicitly passed the :attr:`_warning_cls_on_decorator_exception` parameter. See also the :meth:`__new__` method docstring. _repr : Optional[str] Either: * If the :func:`repr` builtin has yet to call the :meth:`__repr__` dunder method, :data:`None`. * Else, the machine-readable representation of this configuration, _strategy : BeartypeStrategy **Type-checking strategy** (i.e., :class:`BeartypeStrategy` enumeration member) with which to implement all type-checks in the wrapper function dynamically generated by the :func:`beartype.beartype` decorator for the decorated callable. violation_door_type : TypeException **DOOR violation type** (i.e., type of exception raised by the :func:`beartype.door.die_if_unbearable` type-checker when the object passed to that type-checker violates the type hint passed to that type-checker). See also the :meth:`__new__` method docstring. _violation_param_type : TypeException **Parameter violation type** (i.e., type of exception raised by callables generated by the :func:`beartype.beartype` decorator when those callables receive parameters violating the type hints annotating those parameters). See also the :meth:`__new__` method docstring. _violation_return_type : TypeException **Return violation type** (i.e., type of exception raised by callables generated by the :func:`beartype.beartype` decorator when those callables return values violating the type hints annotating those returns). See also the :meth:`__new__` method docstring. _violation_type : Optional[TypeException] **Default violation type** (i.e., type of exception to default whichever of the ``violation_door_type``, ``violation_param_type``, and ``violation_return_type`` exception types are unpassed and thus :data:`None`). See also the :meth:`__new__` method docstring. _violation_verbosity : BeartypeViolationVerbosity **Violation verbosity** (i.e., positive integer in the inclusive range ``[1, 5]`` governing the verbosity of exception messages raised by type-checking wrappers generated by the :func:`beartype.beartype` decorator when either receiving parameters *or* returning values violating their annotated type hints). See also the :meth:`__new__` method docstring. _warning_cls_on_decorator_exception : Optional[TypeWarning] Configuration parameter governing whether the :func:`beartype.beartype` decorator reduces otherwise fatal exceptions raised at decoration time to equivalent non-fatal warnings of this warning category. See also the :meth:`__new__` method docstring. ''' # ..................{ CLASS VARIABLES }.................. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize this slots list with the implementations of: # * The __new__() dunder method. # * The __eq__() dunder method. # * The __hash__() dunder method. # * The __repr__() dunder method. # CAUTION: Subclasses declaring uniquely subclass-specific instance # variables *MUST* additionally slot those variables. Subclasses violating # this constraint will be usable but unslotted, which defeats our purposes. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently called # cache dunder methods. Slotting has been shown to reduce read and write # costs by approximately ~10%, which is non-trivial. __slots__ = ( '_claw_is_pep526', '_conf_args', '_conf_kwargs', '_hash', '_hint_overrides', '_is_color', '_is_debug', '_is_pep484_tower', '_is_violation_door_warn', '_is_violation_param_warn', '_is_violation_return_warn', '_is_warning_cls_on_decorator_exception_set', '_repr', '_strategy', '_violation_door_type', '_violation_param_type', '_violation_return_type', '_violation_type', '_violation_verbosity', '_warning_cls_on_decorator_exception', ) # Squelch false negatives from mypy. This is absurd. This is mypy. See: # https://github.com/python/mypy/issues/5941 if TYPE_CHECKING: _claw_is_pep526: bool _conf_args: tuple _conf_kwargs: DictStrToAny _hash: int _hint_overrides: BeartypeHintOverrides _is_color: Optional[bool] _is_debug: bool _is_pep484_tower: bool _is_violation_door_warn: bool _is_violation_param_warn: bool _is_violation_return_warn: bool _is_warning_cls_on_decorator_exception_set: bool _repr: Optional[str] _strategy: BeartypeStrategy _violation_door_type: TypeException _violation_param_type: TypeException _violation_return_type: TypeException _violation_type: Optional[TypeException] _violation_verbosity: BeartypeViolationVerbosity _warning_cls_on_decorator_exception: Optional[TypeWarning] # ..................{ INSTANTIATORS }.................. # Note that this __new__() dunder method implements the superset of the # functionality typically implemented by the __init__() dunder method. Due # to Python instantiation semantics, the __init__() dunder method is # intentionally left undefined. Why? Because Python unconditionally invokes # __init__() if defined, even when the initialization performed by that # __init__() has already been performed for the cached instance returned by # __new__(). In short, __init__() and __new__() are largely mutually # exclusive; one typically defines one or the other but *NOT* both. def __new__( cls, # Optional keyword-only parameters. *, # Uncomment us when implementing O(n) type-checking, please. # check_time_max_multiplier: Union[int, None] = 1000, claw_is_pep526: bool = True, hint_overrides: BeartypeHintOverrides = BEARTYPE_HINT_OVERRIDES_EMPTY, is_color: BoolTristateUnpassable = ARG_VALUE_UNPASSED, is_debug: bool = False, is_pep484_tower: bool = False, strategy: BeartypeStrategy = BeartypeStrategy.O1, violation_door_type: Optional[TypeException] = None, violation_param_type: Optional[TypeException] = None, violation_return_type: Optional[TypeException] = None, violation_type: Optional[TypeException] = None, violation_verbosity: BeartypeViolationVerbosity = ( BeartypeViolationVerbosity.DEFAULT), warning_cls_on_decorator_exception: Optional[TypeWarning] = ( _BeartypeConfReduceDecoratorExceptionToWarningDefault), ) -> 'BeartypeConf': ''' Instantiate this configuration if needed (i.e., if *no* prior configuration with these same parameters was previously instantiated) *or* reuse that previously instantiated configuration otherwise. This dunder methods guarantees beartype configurations to be memoized: .. code-block:: python >>> from beartype import BeartypeConf >>> BeartypeConf() is BeartypeConf() True This memoization is *not* merely an optimization. The :func:`beartype.beartype` decorator internally memoizes the private closure it creates and returns on the basis of this configuration, which *must* thus also be memoized. Parameters ---------- check_time_max_multiplier : Union[int, None] = 1000 **Deadline multiplier** (i.e., positive integer instructing :mod:`beartype` to prematurely halt the current type-check when the total running time of the active Python interpreter exceeds this integer multiplied by the running time consumed by both the current type-check and all prior type-checks *and* the caller also passed a non-default ``strategy``) *or* :data:`None` if :mod:`beartype` should never prematurely halt runtime type-checks. Increasing this integer increases the number of container items that :mod:`beartype` type-checks at a cost of decreasing application responsiveness. Likewise, decreasing this integer increases application responsiveness at a cost of decreasing the number of container items that :mod:`beartype` type-checks. Ignored when ``strategy`` is :attr:`BeartypeStrategy.O1`, as that strategy is already effectively instantaneous; imposing deadlines and thus bureaucratic bookkeeping on that strategy would only reduce its efficiency for no good reason, which is a bad reason. Defaults to 1000, in which case a maximum of 0.10% of the total runtime of the active Python process will be devoted to performing non-constant :mod:`beartype` type-checks over container items. This default has been carefully tuned to strike a reasonable balance between runtime type-check coverage and application responsiveness, typically enabling smaller containers to be fully type-checked without noticeably impacting codebase performance. **Theory time.** Let: * :math:`T` be the total time this interpreter has been running. * :math:``b` be the total time :mod:`beartype` has spent type-checking in this interpreter. Clearly, :math:`b <= T`. Generally, :math:`b <<<<<<< T` (i.e., type-checks consume much less time than the total time consumed by the process). However, it's all too easy to exhibit worst-case behaviour of :math:`b ~= T` (i.e., type-checks consume most of the total time). How? By passing the :func:`beartype.door.is_bearable` tester an absurdly large nested container subject to the non-default ``strategy`` of :attr:`BeartypeStrategy.On`. This deadline multiplier mitigates that worst-case behaviour. Specifically, :mod:`beartype` will prematurely halt any iterative type-check across a container when this constraint is triggered: .. code-block:: python b * check_time_max_multiplier >= T claw_is_pep526 : bool, optional :data:`True` only if implicitly type-checking **annotated variable assignments** (i.e., :pep:`526`-compliant assignments to local, global, class, and instance variables annotated by type hints) when importing modules under import hooks published by the :mod:`beartype.claw` subpackage by injecting calls to the :func:`beartype.door.die_if_unbearable` function immediately *after* those assignments in those modules. Enabling this boolean: * Effectively augments :mod:`beartype` into a full-blown **hybrid runtime-static type-checker** (i.e., performing both standard runtime type-checking *and* non-standard static type-checking at runtime). * Adds negligible runtime overhead to all annotated variable assignments in all modules imported under those import hooks. Although the *individual* cost of this overhead for any given assignment is negligible, the *aggregate* cost across all such assignments could be non-negligible in worst-case use cases. Ideally, this boolean should only be disabled for a small subset of performance-sensitive modules *after* profiling those modules to suffer performance regressions under import hooks published by the :mod:`beartype.claw` subpackage. Defaults to :data:`True`. hint_overrides : BeartypeHintOverrides **Type hint overrides** (i.e., frozen dictionary mapping from arbitrary source to target type hints), enabling callers to lie to both their users and all other packages other than :mod:`beartype`. This dictionary enables callers to externally present a public API annotated by simplified type hints while internally instructing :mod:`beartype` to privately type-check that API under a completely different set of (typically more complicated) type hints. Doing so preserves a facade of simplicity for downstream consumers like end users, static type-checkers, and document generators. Defaults to the empty dictionary. Specifically, for each source type hint annotating each callable, class, or variable assignment observed by :mod:`beartype`, if that source type hint is a key of this dictionary, :mod:`beartype` maps that source type hint to the corresponding target type hint in this dictionary. That target type hint then globally "overrides" (i.e., replaces, substitutes for) that source type hint. :mod:`beartype` then uses that target type hint in place of that source type hint. For example, consider this Abomination Unto the Eyes of Guido: .. code-block:: python from beartype, BeartypeConf, BeartypeHintOverrides # @beartype decorator configured to expand all "float" type hints # to "int | float" type hints. lyingbeartype = beartype(conf=BeartypeConf( hint_overrides=BeartypeHintOverrides({float: int | float}))) # The @lyingbeartype decorator now expands this signature... @lyingbeartype def lies(all_lies: list[int]) -> int: return all_lies[0] # ...as if it had been annotated like this instead. @beartype def lies(all_lies: list[int | float]) -> int | float: return all_lies[0] is_color : BoolTristateUnpassable Tri-state boolean governing how and whether beartype colours **type-checking violations** (i.e., :class:`beartype.roar.BeartypeCallHintViolation` exceptions) with POSIX-compliant ANSI escape sequences for readability. Specifically, if this boolean is: * :data:`False`, beartype *never* colours type-checking violations raised by callables configured with this configuration. * :data:`True`, beartype *always* colours type-checking violations raised by callables configured with this configuration. * :data:`None`, beartype conditionally colours type-checking violations raised by callables configured with this configuration only when standard output is attached to an interactive terminal. The ``${BEARTYPE_IS_COLOR}`` environment variable globally overrides *all* attempts by *all* callers to explicitly pass this parameter, enabling end users to enforce a global colour policy across their full app stack. If ``${BEARTYPE_IS_COLOR}`` is set to a different value than that of this parameter, this constructor emits a non-fatal :class:`beartype.roar.BeartypeConfShellVarWarning` warning informing the caller of this configuration conflict. To avoid this conflict, open-source libraries are recommended to *not* pass this parameter; ideally, *only* end user apps should pass this parameter. Effectively defaults to :data:`None`. Technically, this parameter defaults to a private magic constant *not* intended to be passed by callers, enabling :mod:`beartype` to reliably detect whether the caller has explicitly passed this parameter or not. is_debug : bool, optional :data:`True` only if debugging :mod:`beartype`. Enabling this boolean: * Prints the definition (including both the signature and body) of each type-checking wrapper function dynamically generated by :mod:`beartype` to standard output. * Caches the body of each type-checking wrapper function dynamically generated by :mod:`beartype` with the standard :mod:`linecache` module, enabling these function bodies to be introspected at runtime *and* improving the readability of tracebacks whose call stacks contain one or more calls to these :func:`beartype.beartype`-decorated functions. * Appends to the declaration of each **hidden parameter** (i.e., whose name is prefixed by ``"__beartype_"`` and whose value is that of an external attribute internally referenced in the body of that function) the machine-readable representation of the initial value of that parameter, stripped of newlines and truncated to a hopefully sensible length. Since the :func:`beartype._util.text.utiltextrepr.represent_object` function called to do so is shockingly slow, these substrings are conditionally embedded in the returned signature *only* when enabling this boolean. Defaults to :data:`False`. is_pep484_tower : bool, optional :data:`True` only if enabling support for the :pep:`484`-compliant **implicit numeric tower** (i.e., lossy conversion of integers to floating-point numbers *and* both integers and floating-point numbers to complex numbers). Specifically, enabling this instructs :mod:`beartype` to automatically expand: * All :class:`float` type hints to ``float | int``, thus implicitly accepting both integers and floating-point numbers for objects annotated as only accepting floating-point numbers. * All :class:`complex` type hints to ``complex | float | int``, thus implicitly accepting integers, floating-point, and complex numbers for objects annotated as only accepting complex numbers. Defaults to :data:`False` to minimize precision error introduced by lossy conversions from integers to floating-point numbers to complex numbers. Since most integers do *not* have exact representations as floating-point numbers, each conversion of an integer into a floating-point number typically introduces a small precision error that accumulates over multiple conversions and operations into a larger precision error. Enabling this improves the usability of public APIs at a cost of introducing precision errors. strategy : BeartypeStrategy, optional **Type-checking strategy** (i.e., :class:`BeartypeStrategy` enumeration member) with which to implement all type-checks in the wrapper function dynamically generated by the :func:`beartype.beartype` decorator for the decorated callable. Defaults to :attr: `BeartypeStrategy.O1`, the ``O(1)`` constant-time strategy. violation_door_type : Optional[TypeException] **DOOR violation type** (i.e., type of exception raised by the :func:`beartype.door.die_if_unbearable` type-checker when the object passed to that type-checker violates the type hint passed to that type-checker). Defaults to :data:`None`, in which case this type defaults to either: * If ``violation_type`` is passed and *not* :data:`None`, that type. * Else, :exc:`.BeartypeDoorHintViolation`. violation_param_type : Optional[TypeException] **Parameter violation type** (i.e., type of exception raised by callables generated by the :func:`beartype.beartype` decorator when those callables receive parameters violating the type hints annotating those parameters). Defaults to :data:`None`, in which case this type defaults to either: * If ``violation_type`` is passed and *not* :data:`None`, that type. * Else, :exc:`.BeartypeCallHintParamViolation`. violation_return_type : Optional[TypeException] **Return violation type** (i.e., type of exception raised by callables generated by the :func:`beartype.beartype` decorator when those callables return values violating the type hints annotating those returns). Defaults to :data:`None`, in which case this type defaults to either: * If ``violation_type`` is passed and *not* :data:`None`, that type. * Else, :exc:`.BeartypeCallHintReturnViolation`. violation_type : Optional[TypeException] **Default violation type** (i.e., type of exception to default whichever of the ``violation_door_type``, ``violation_param_type``, and ``violation_return_type`` exception types are unpassed and thus :data:`None`). This parameter is merely a convenience enabling callers to trivially control the ``violation_door_type``, ``violation_param_type``, and ``violation_return_type`` parameters *without* having to explicitly pass all three of those parameters. Specifically, if this parameter is: * *Not* :data:`None`, then the ``violation_door_type``, ``violation_param_type``, and ``violation_return_type`` parameters all default to the value of this parameter. * :data:`None`, then: * ``violation_door_type`` defaults to :exc:`.BeartypeDoorHintViolation`. * ``violation_param_type`` defaults to :exc:`.BeartypeCallHintParamViolation`. * ``violation_return_type`` defaults to :exc:`.BeartypeCallHintReturnViolation`. Defaults to :data:`None`. violation_verbosity : BeartypeViolationVerbosity, optional **Violation verbosity** (i.e., positive integer in the inclusive range ``[1, 5]`` governing the verbosity of exception messages raised by type-checking wrappers generated by the :func:`beartype.beartype` decorator when either receiving parameters *or* returning values violating their annotated type hints). Defaults to :attr:`.BeartypeViolationVerbosity.DEFAULT`. warning_cls_on_decorator_exception : Optional[TypeWarning] Configuration parameter governing whether the :func:`beartype.beartype` decorator reduces what would otherwise be fatal exceptions raised at decoration time to equivalent non-fatal warnings of the passed **warning category** (i.e., subclass of the standard :class:`Warning` class). Specifically, this parameter may be either: * :data:`None`, in which case the :func:`beartype.beartype` decorator raises fatal exceptions at decoration time. * A warning category, in which case the :func:`beartype.beartype` decorator reduces fatal exceptions to non-fatal warnings of this category at decoration time. Defaults to a private warning type *not* intended to be passed by callers, enabling :mod:`beartype` to reliably detect when the caller has *not* explicitly passed this parameter and respond accordingly by defaulting this parameter to a context-dependent value. Notably, if this parameter is *not* explicitly passed: * The :func:`beartype.beartype` decorator defaults this parameter to :data:`None`, thus raising decoration-time exceptions. * The :mod:`beartype.claw` API defaults this parameter to the public :class:`beartype.roar.BeartypeClawDecorWarning` warning category, thus reducing decoration-time exceptions to warnings of that category when performing import hooks. This default behaviour significantly increases the likelihood that import hooks installed by :mod:`beartype.claw` will successfully decorate the entirety of their target packages rather than prematurely halt with a single fatal exception at the first decoration issue. Returns ------- BeartypeConf Beartype configuration memoized with these parameters. Raises ------ BeartypeConfParamException If either: * ``is_color`` is *not* a tri-state boolean. * ``is_debug`` is *not* a boolean. * ``is_pep484_tower`` is *not* a boolean. * ``strategy`` is *not* a :class:`BeartypeStrategy` enumeration member. * ``warning_cls_on_decorator_exception`` is neither :data:`None` *nor* a **warning category** (i.e., :class:`Warning` subclass). BeartypeConfShellVarException If either: * The external ``${BEARTYPE_IS_COLOR}`` shell environment variable is set to an unrecognized string (i.e., neither ``"True"``, ``"False"``, nor ``"None"``). ''' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize this tuple with the similar "self._conf_kwargs" # dictionary defined below. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # In a non-reentrant thread lock specific to beartype configurations... # # Note that this lock is potentially overkill and thus unnecessary. # Nonetheless, since the number of beartype configurations instantiated # over the lifetime of the average Python interpreter is small, since # non-reentrant thread locks are reasonably fast to enter, and since the # cost of race conditions is high, this lock does no real-world harm and # may actually do a great deal of real-world good. Safety first, all! with _beartype_conf_lock: # ..................{ CACHE }.................. # Validate and possibly override the "is_color" parameter by the # value of the ${BEARTYPE_IS_COLOR} environment variable (if set). is_color = get_is_color(is_color) # Efficiently hashable tuple of these parameters in arbitrary order. conf_args = ( claw_is_pep526, hint_overrides, is_color, is_debug, is_pep484_tower, strategy, violation_door_type, violation_param_type, violation_return_type, violation_type, violation_verbosity, warning_cls_on_decorator_exception, ) # If this method has already instantiated a configuration with these # parameters, return that configuration for consistency and # efficiency. if conf_args in _beartype_conf_args_to_conf: return _beartype_conf_args_to_conf[conf_args] # Else, this method has *NOT* yet instantiated a configuration with # these parameters. In this case, continue to do so and then cache # that configuration. # Dictionary mapping from the names to values of *ALL* possible # keyword parameters configuring this configuration, intentionally # defined *AFTER* this method first attempts to efficiently reduce # to a noop by returning a previously instantiated configuration. conf_kwargs = dict( claw_is_pep526=claw_is_pep526, hint_overrides=hint_overrides, is_color=is_color, is_debug=is_debug, is_pep484_tower=is_pep484_tower, strategy=strategy, violation_door_type=violation_door_type, violation_param_type=violation_param_type, violation_return_type=violation_return_type, violation_type=violation_type, violation_verbosity=violation_verbosity, warning_cls_on_decorator_exception=( warning_cls_on_decorator_exception), ) # Default all parameters not explicitly passed by the user to sane # defaults *BEFORE* validating these parameters. default_conf_kwargs_before(conf_kwargs) # If one or more passed parameters are invalid, raise an exception. die_if_conf_kwargs_invalid(conf_kwargs) # Else, all passed parameters are valid. # Default all parameters not explicitly passed by the user to sane # defaults *AFTER* validating these parameters. default_conf_kwargs_after(conf_kwargs) # ..................{ INSTANTIATE }.................. # Instantiate a new configuration of this type. self = super().__new__(cls) # Nullify critical instance variables for safety. self._repr = None # Precompute the hash to be returned by the __hash__() dunder method # as the hash of a tuple containing these parameters in an arbitrary # (albeit well-defined) order. # # Note this has been profiled to be the optimal means of hashing # object attributes in Python, where "optimal" means: # * Optimally fast. CPython in particular optimizes the creation and # garbage collection of "small" tuples, where "small" is # ill-defined but almost certainly applies here. # * Optimally uniformly distributed, thus minimizing the likelihood # of expensive hash collisions. self._hash = hash(conf_args) # Store data structures encapsulating these passed parameters for # subsequent reuse *BEFORE* possibly modifying the values of these # parameters below. self._conf_args = conf_args self._conf_kwargs = conf_kwargs # Assert that these two data structures encapsulate the same number # of configuration parameters (as a feeble safety check). assert len(self._conf_args) == len(self._conf_kwargs) # Cache this configuration with all relevant dictionary singletons # *BEFORE* possibly modifying the values of passed parameters below. _beartype_conf_args_to_conf[conf_args] = self # ..................{ CLASSIFY }.................. # Classify all passed parameters that have now been possibly # modified above with this configuration. # # Note that this classification intentionally accesses these # parameters from the "conf_kwargs" dictionary possibly modified by # the above call to the default_conf_kwargs() function rather than # the original passed values of these parameters. self._claw_is_pep526 = conf_kwargs['claw_is_pep526'] # pyright: ignore self._hint_overrides = conf_kwargs['hint_overrides'] # pyright: ignore self._is_color = conf_kwargs['is_color'] # pyright: ignore self._is_debug = conf_kwargs['is_debug'] # pyright: ignore self._is_pep484_tower = conf_kwargs['is_pep484_tower'] # pyright: ignore self._strategy = conf_kwargs['strategy'] # pyright: ignore self._violation_door_type = conf_kwargs['violation_door_type'] # pyright: ignore self._violation_param_type = conf_kwargs['violation_param_type'] # pyright: ignore self._violation_return_type = conf_kwargs['violation_return_type'] # pyright: ignore self._violation_type = conf_kwargs['violation_type'] # pyright: ignore self._violation_verbosity = conf_kwargs['violation_verbosity'] # pyright: ignore # Classify all remaining instance variables. self._is_violation_door_warn = issubclass( self._violation_door_type, Warning) # pyright: ignore self._is_violation_param_warn = issubclass( self._violation_param_type, Warning) # pyright: ignore self._is_violation_return_warn = issubclass( self._violation_return_type, Warning) # pyright: ignore # ..................{ CLASSIFY ~ more }.................. # If the value of the "warning_cls_on_decorator_exception" parameter # is still the default private fake warning category established # above, then the caller failed to explicitly pass a valid value. In # this case... if ( warning_cls_on_decorator_exception is _BeartypeConfReduceDecoratorExceptionToWarningDefault ): # Note this fact for subsequent reference elsewhere (e.g., in # the "beartype.claw" subpackage). self._is_warning_cls_on_decorator_exception_set = False # Default this parameter to "None" for safety. Since this # default private fake warning category is *NOT* an actual # warning category intended for real-world use, this category # *MUST* be replaced with a sane default that is safely usable. warning_cls_on_decorator_exception = None # Else, the caller explicitly passed a valid value for this # parameter. In this case, preserve this value and note this fact. else: self._is_warning_cls_on_decorator_exception_set = True self._warning_cls_on_decorator_exception = ( warning_cls_on_decorator_exception) # Return this configuration. return self # ..................{ PROPERTIES }.................. # Read-only public properties effectively prohibiting mutation of their # underlying private attributes. #FIXME: Publicly document this in our reST-formatted docos, please. @property def kwargs(self) -> DictStrToAny: ''' **Beartype configuration keyword dictionary** (i.e., dictionary mapping from the names of all keyword parameters accepted by the :meth:`__new__` method to the corresponding values of those parameters in this configuration). This property can be used to permute new configurations from existing configurations, overriding only a small handful of parameters while preserving all other parameters as is: e.g., .. code-block:: python # Arbitrary input beartype configuration. conf = BeartypeConf(is_color=True) # New keyword dictionary permuted from this input. conf_kwargs = conf.kwargs.copy() conf_kwargs['is_debug'] = True # New beartype configuration initialized by this dictionary. debug_conf = BeartypeConf(**conf_kwargs) See Also -------- :meth:`__new__` Further details. ''' return self._conf_kwargs # ..................{ PROPERTIES ~ options }.................. # Read-only public properties with which this configuration was originally # instantiated (as keyword-only parameters). @property def hint_overrides(self) -> BeartypeHintOverrides: ''' **Type hint overrides** (i.e., frozen dictionary mapping from arbitrary source to target type hints), enabling callers to lie to both their users and all other packages other than :mod:`beartype`. This dictionary enables callers to externally present a public API annotated by simplified type hints while internally instructing :mod:`beartype` to privately type-check that API under a completely different set of (typically more complicated) type hints. See Also -------- :meth:`__new__` Further details. ''' return self._hint_overrides @property def strategy(self) -> BeartypeStrategy: ''' **Type-checking strategy** (i.e., :class:`BeartypeStrategy` enumeration member) with which to implement all type-checks in the wrapper function dynamically generated by the :func:`beartype.beartype` decorator for the decorated callable. See Also -------- :meth:`__new__` Further details. ''' return self._strategy @property def warning_cls_on_decorator_exception(self) -> ( Optional[TypeWarning]): ''' Configuration parameter governing whether the :func:`beartype.beartype` decorator reduces otherwise fatal exceptions raised at decoration time to equivalent non-fatal warnings of this warning category. See Also -------- :meth:`__new__` Further details. ''' return self._warning_cls_on_decorator_exception # ..................{ PROPERTIES ~ options : bool }.................. # Read-only public properties with which this configuration was originally # instantiated (as keyword-only parameters). @property def claw_is_pep526(self) -> bool: ''' :data:`True` only if type-checking **annotated variable assignments** (i.e., :pep:`526`-compliant assignments to local, global, class, and instance variables annotated by type hints) when importing modules under import hooks published by the :mod:`beartype.claw` subpackage. See Also -------- :meth:`__new__` Further details. ''' return self._claw_is_pep526 @property def is_color(self) -> Optional[bool]: ''' Tri-state boolean governing how and whether beartype colours **type-checking violations** (i.e., :class:`beartype.roar.BeartypeCallHintViolation` exceptions) with POSIX-compliant ANSI escape sequences for readability. Specifically, if this boolean is: * :data:`False`, beartype *never* colours type-checking violations raised by callables configured with this configuration. * :data:`True`, beartype *always* colours type-checking violations raised by callables configured with this configuration. * :data:`None`, beartype conditionally colours type-checking violations raised by callables configured with this configuration only when standard output is attached to an interactive terminal. See Also -------- :meth:`__new__` Further details. ''' return self._is_color @property def is_debug(self) -> bool: ''' :data:`True` only if debugging :mod:`beartype`. See Also -------- :meth:`__new__` Further details. ''' return self._is_debug @property def is_pep484_tower(self) -> bool: ''' :data:`True` only if enabling support for the :pep:`484`-compliant implicit numeric tower. See Also -------- :meth:`__new__` Further details. ''' return self._is_pep484_tower # ..................{ PROPERTIES ~ options : violation }.................. @property def violation_door_type(self) -> TypeException: ''' **DOOR violation type** (i.e., type of exception raised by the :func:`beartype.door.die_if_unbearable` type-checker when the object passed to that type-checker violates the type hint passed to that type-checker). See Also -------- :meth:`__new__` Further details. ''' return self._violation_door_type @property def violation_param_type(self) -> TypeException: ''' **Parameter violation type** (i.e., type of exception raised by callables generated by the :func:`beartype.beartype` decorator when those callables receive parameters violating the type hints annotating those parameters). See Also -------- :meth:`__new__` Further details. ''' return self._violation_param_type @property def violation_return_type(self) -> TypeException: ''' **Return violation type** (i.e., type of exception raised by callables generated by the :func:`beartype.beartype` decorator when those callables return values violating the type hints annotating those returns). See Also -------- :meth:`__new__` Further details. ''' return self._violation_return_type @property def violation_type(self) -> Optional[TypeException]: ''' **Default violation type** (i.e., type of exception to default whichever of the ``violation_door_type``, ``violation_param_type``, and ``violation_return_type`` exception types are unpassed and thus :data:`None`). See Also -------- :meth:`__new__` Further details. ''' return self._violation_type @property def violation_verbosity(self) -> BeartypeViolationVerbosity: ''' **Violation verbosity** (i.e., positive integer in the inclusive range ``[1, 5]`` governing the verbosity of exception messages raised by type-checking wrappers generated by the :func:`beartype.beartype` decorator when either receiving parameters *or* returning values violating their annotated type hints). See Also -------- :meth:`__new__` Further details. ''' return self._violation_verbosity # ..................{ DUNDERS }.................. def __eq__(self, other: object) -> bool: ''' **Beartype configuration equality comparator.** Parameters ---------- other : object Arbitrary object to be compared for equality against this configuration. Returns ------- Union[bool, type(NotImplemented)] Either: * If this other object is also a beartype configuration, either: * If these configurations share the same settings, :data:`True`. * Else, :data:`False`. * Else, :data:`NotImplemented`. See Also -------- :func:`_hash_beartype_conf` Further details. ''' # Return either... return ( # If this other object is also a beartype configuration, true only # if these configurations share the same settings; self._conf_args == other._conf_args if isinstance(other, BeartypeConf) else # Else, this other object is *NOT* also a beartype configuration. In # this case, the standard singleton informing Python that this # equality comparator fails to support this comparison. NotImplemented # type: ignore[return-value] ) def __hash__(self) -> int: ''' **Hash** (i.e., non-negative integer quasi-uniquely identifying this beartype configuration with respect to hashable container membership). Returns ------- int Hash of this configuration. ''' # Return the precomputed hash for this configuration. return self._hash def __repr__(self) -> str: ''' **Beartype configuration representation** (i.e., machine-readable string which, when dynamically evaluated as code, restores access to this exact configuration object). Returns ------- str Representation of this configuration. ''' # If machine-readable representation of this configuration has yet to be # computed... if self._repr is None: # Initialize this representation to the unqualified basename of the # class of this configuration. conf_repr = f'{get_object_type_basename(self)}(' # Dictionary mapping from the names to values of *ALL* possible # keyword parameters configuring the default beartype configuration. KWARGS_DEFAULT = BEARTYPE_CONF_DEFAULT._conf_kwargs # For the name and value of each keyword parameter with which this # configuration was instantiated... for kwarg_name, kwarg_value in self._conf_kwargs.items(): # If this value differs from that of the default value for this # keyword parameter... if kwarg_value != KWARGS_DEFAULT[kwarg_name]: # Append a comma-delimited representation of this keyword # argument to this representation. conf_repr += f'{kwarg_name}={kwarg_value}, ' # Else, this value is the default value for this keyword # parameter. In this case, silently ignore this value. Appending # this value to this representation would convey *NO* meaningful # semantics and, indeed, only inhibit the readability of this # representation for end users and developers alike. # If this representation is suffixed by a whitespaced comma, remove # that suffix. if conf_repr[-2:] == ', ': conf_repr = conf_repr[:-2] # Else, this representation is *NOT* suffixed by a comma. In this # case, preserve this representation as is. # Preserve this representation for subsequent use. self._repr = f'{conf_repr})' # Else, the machine-readable representation of this configuration has # already been computed. # Return the machine-readable representation of this configuration. return self._repr
# ....................{ PRIVATE ~ globals }.................... _beartype_conf_lock = Lock() ''' **Non-reentrant beartype configuration thread lock** (i.e., low-level thread locking mechanism implemented as a highly efficient C extension, defined as an global for non-reentrant reuse elsewhere as a context manager). ''' _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 :meth:`BeartypeConf.__new__` instantiator to the unique :class:`BeartypeConf` instance instantiated by that call). Caveats ------- **This cache is non-thread-safe.** However, since this cache is only used as a memoization optimization, the only harmful consequences of a race condition between threads contending over this cache is a mildly inefficient (but otherwise harmless) repeated re-memoization of duplicate configurations. ''' # ....................{ GLOBALS }.................... # This global is intentionally defined *AFTER* all other attributes above, which # this global implicitly assumes to be defined. BEARTYPE_CONF_DEFAULT = BeartypeConf() ''' **Default beartype configuration** (i.e., :class:`BeartypeConf` class instantiated with *no* parameters and thus default parameters), globalized to trivially optimize external access to this configuration throughout this codebase. Note that this global is *not* publicized to end users, who can simply instantiate ``BeartypeConf()`` to obtain the same singleton. '''