File: clawpkgcontext.py

package info (click to toggle)
python-beartype 0.22.9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 9,504 kB
  • sloc: python: 85,502; sh: 328; makefile: 30; javascript: 18
file content (210 lines) | stat: -rw-r--r-- 9,100 bytes parent folder | download
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()