1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
|
#!/usr/bin/env python3
# --------------------( LICENSE )--------------------
# Copyright (c) 2014-2025 Beartype authors.
# See "LICENSE" for further details.
'''
Beartype **import path hook context managers** (i.e., data structure caching
package names on behalf of the higher-level :func:`beartype.claw._clawmain`
submodule, which beartype import path hooks internally created by that submodule
subsequently lookup when deciding whether or not (and how) to decorate by
:func:`beartype.beartype` the currently imported user-specific submodule).
This private submodule is *not* intended for importation by downstream callers.
'''
# ....................{ IMPORTS }....................
from beartype.claw._package.clawpkgtrie import (
remove_beartype_pathhook_unless_packages_trie)
from beartype.typing import (
Iterator,
Optional,
)
from beartype._conf.confcommon import BEARTYPE_CONF_DEFAULT
from beartype._conf.confmain import BeartypeConf
from contextlib import contextmanager
# ....................{ CONTEXTS }....................
#FIXME: Highly non-ideal. As defined, this context manager currently fails to
#behave as expected by users. This context manager claims to be thread-safe, but
#almost certainly isn't. Why? Because it registers the beartype_all() hook --
#which then applies to *ALL* threads. Ideally, this context manager should
#*ONLY* apply to the current thread of operation.
#FIXME: Unit test us up, please.
@contextmanager
def beartyping(
# Optional keyword-only parameters.
*,
conf: BeartypeConf = BEARTYPE_CONF_DEFAULT,
) -> Iterator[None]:
'''
Context manager temporarily registering a new **universal beartype import
path hook** (i.e., callable inserted to the front of the standard
:mod:`sys.path_hooks` list recursively decorating *all* typed callables and
classes of *all* submodules of *all* packages on the first importation of
those submodules with the :func:`beartype.beartype` decorator, wrapping
those callables and classes with performant runtime type-checking).
Specifically, this context manager (in order):
#. Temporarily registers this hook by calling the public
:func:`beartype.claw.beartype_all` function.
#. Runs the body of the caller-defined ``with beartyping(...):`` block.
#. Unregisters the hook registered by the prior call to that function.
This context manager is thread-safe.
Parameters
----------
conf : BeartypeConf, optional
**Beartype configuration** (i.e., dataclass configuring the
:mod:`beartype.beartype` decorator for *all* decoratable objects
recursively decorated by the path hook added by this function).
Defaults to ``BeartypeConf()``, the default :math:`O(1)` configuration.
Yields
------
None
This context manager yields *no* objects.
Raises
------
BeartypeClawHookException
If the passed ``conf`` parameter is *not* a beartype configuration
(i.e., :class:`.BeartypeConf` instance).
See Also
--------
:func:`beartype.claw.beartype_all`
Arguably unsafer alternative to this function globalizing the effect of
this function to *all* imports performed anywhere.
'''
# Avoid circular import dependencies.
from beartype.claw import beartype_all
from beartype.claw._clawstate import (
claw_lock,
claw_state,
)
# Prior global beartype configuration registered by a prior call to the
# beartype_all() function if any *OR* "None" otherwise.
packages_trie_conf_if_hooked_old: Optional[BeartypeConf] = None
# Attempt to...
try:
# With a "beartype.claw"-specific thread-safe reentrant lock...
with claw_lock:
# Store the prior global beartype configuration if any.
packages_trie_conf_if_hooked_old = (
claw_state.packages_trie_whitelist.conf_if_hooked)
# Prevent the beartype_all() function from raising an exception on
# conflicting registrations of beartype configurations.
claw_state.packages_trie_whitelist.conf_if_hooked = None
# Globalize the passed beartype configuration.
beartype_all(conf=conf)
# Defer to the caller body of the parent "with beartyping(...):" block.
yield
# After doing so (regardless of whether doing so raised an exception)...
finally:
# With a "beartype.claw"-specific thread-safe reentrant lock...
with claw_lock:
# If the current global beartype configuration is still the passed
# beartype configuration, then the caller's body of the parent "with
# beartyping(...):" block has *NOT* itself called the beartype_all()
# function with a conflicting beartype configuration. In this
# case...
if claw_state.packages_trie_whitelist.conf_if_hooked == conf:
# Restore the prior global beartype configuration if any.
claw_state.packages_trie_whitelist.conf_if_hooked = (
packages_trie_conf_if_hooked_old)
# Possibly remove our beartype import path hook added by the
# above call to beartype_all() if *NO* packages are registered.
remove_beartype_pathhook_unless_packages_trie()
# Else, the caller's body of the parent "with beartyping(...):"
# block has itself called the beartype_all() function with a
# conflicting beartype configuration. In this case, preserve that
# configuration as is.
# ....................{ CONTEXTS ~ test }....................
#FIXME: Unit test us up, please.
@contextmanager
def packages_trie_reverted() -> Iterator[None]:
'''
Test-specific context manager reverting (i.e., clearing, resetting) *all*
**import hook global state** (i.e., the contents of the
:data:`beartype.claw._clawstate.claw_state` global dataclass) back to its
**prior state** (i.e., the state *before* invoking this context manager)
*after* running the body of the caller-defined ``with
packages_trie_reverted(...):`` block.
This context manager is thread-safe.
Caveats
-------
**This context manager is intentionally hidden from users as a private
attribute of this submodule** rather than publicly exported. Why? Because
this context manager is *only* intended to be invoked by unit and
integration tests in our test suite.
Yields
------
None
This context manager yields *no* objects.
'''
# print('Clearing "beartype.claw" state...')
# Avoid circular import dependencies.
from beartype.claw._clawstate import (
claw_lock,
claw_state,
)
#FIXME: Uncomment this *AFTER*:
#* Correctly implementing the copy_deep() method called below. *sigh*
#* Defining and calling the set_claw_state() function documented below.
# # With a submodule-specific thread-safe reentrant lock, capture the prior
# # global state of the "beartype.claw" subpackage *BEFORE* performing the
# # caller-defined body of the parent "with" statement.
# with claw_lock:
# # Deep copy of the current beartype import hook state, enabling this
# # context manager below to transparently restore this state *BEFORE*
# # returning.
# claw_state_prior = claw_state.copy_deep()
# Perform the caller-defined body of the parent "with" statement.
try:
yield
# After doing so, regardless of whether doing so raised an exception...
finally:
# print(f'claw_state [after test]: {repr(claw_state)}')
# With a submodule-specific thread-safe reentrant lock...
with claw_lock:
#FIXME: *UHM. NO. WE NOW NEED TO RESTORE THE DEEP COPY MADE ABOVE.*
#This is sorta trivial, but sorta not. For safety, we probably want
#to define a new *THREAD-SAFE* utility function in the "_clawstate"
#submodule resembling:
# def set_claw_state(claw_state_new: BeartypeClawState) -> None:
# assert isinstance(claw_state_new, BeartypeClawState), (
# f'{repr(claw_state_new)} not "beartype.claw" state.')
#
# global claw_state
#
# with claw_lock:
# claw_state = claw_state_new
# Restore the global state of the "beartype.claw" subpackage back to
# its prior state *BEFORE* performing the caller-defined body of the
# parent "with" statement.
claw_state.reinit()
# If *NO* packages are currently hooked by "beartype.claw" import
# hooks, remove the beartype import path hook added by any import
# hooks registered by the caller-defined body of the parent "with"
# statement. Phew!
remove_beartype_pathhook_unless_packages_trie()
|