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 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
|
#!/usr/bin/env python3
# --------------------( LICENSE )--------------------
# Copyright (c) 2014-2025 Beartype authors.
# See "LICENSE" for further details.
'''
**Beartype decorator PEP-compliant code wrapper scope utilities** (i.e.,
functions handling the possibly nested lexical scopes enclosing wrapper
functions generated by the :func:`beartype.beartype` decorator).
This private submodule is *not* intended for importation by downstream callers.
'''
# ....................{ TODO }....................
#FIXME: Hah-hah! Finally figured out how to do PEP-noncompliant recursive type
#hints... mostly. That said, since @beartype already supports PEP 695-compliant
#recursive type aliases, it's unclear whether any of this is desirable.
#Still, we specced it out. So, here it is. It's a two-parter consisting of:
#* *PART I.* In the first part:
# * Refactor our code generation algorithm to additionally maintain a stack of
# all parent type hints of the currently visited type hint. Note that we need
# to do this anyway to support the __beartype_hint__() protocol. See "FIXME:"
# comments in the "beartype.plug._plughintable" submodule pertaining to that
# protocol for further details on properly building out this stack.
# * When that algorithm visits a forward reference:
# * That algorithm calls the express_func_scope_type_ref() function
# generating type-checking code for that reference. Refactor that call to
# additionally pass that stack of parent hints to that function.
# * Refactor the express_func_scope_type_ref() function to:
# * If the passed forward reference is relative, additionally return that
# stack in the returned 3-tuple
# "(forwardref_expr, refs_type_basename, forwardref_parent_hints)",
# where "forwardref_parent_hints" is that stack.
#* *PART II.* In the second part:
# * Refactor the beartype._decor._nontype._wrap.wrapmain._unmemoize_func_wrapper_code()
# function to additionally:
# * If the passed forward reference is relative *AND* the unqualified
# basename of an existing attribute in a local or global scope of the
# currently decorated callable *AND* the value of that attribute is a
# parent type hint on the stack of parent type hints returned by the
# previously called express_func_scope_type_ref() function, then
# *THIS REFERENCE INDICATES A RECURSIVE TYPE HINT.* In this case:
# * Replace this forward reference with a new recursive type-checking
# "beartype._check.forward.reference.fwdrefabc.BeartypeForwardRef_{forwardref}"
# subclass whose is_instance() tester method recursively calls itself
# indefinitely. If doing so generates a "RecursionError", @beartype
# considers that the user's problem. *wink*
# * Note that this is_instance() tester method should guard itself against
# recursion by accepting an optional "obj_ids: FrozenSetInts =
# FROZEN_SET_EMPTY" parameter recording the IDs of all previously tested
# objects. Consider infinite containers: e.g.,
# infinite_list = []
# infinite_list.append(infinite_list)
#
#Done and done. Phew!
# ....................{ IMPORTS }....................
from beartype.roar import BeartypeDecorHintNonpepException
from beartype.typing import (
Dict,
List,
Optional,
Tuple,
)
from beartype._cave._cavemap import NoneTypeOr
from beartype._check.forward.reference.fwdrefmake import (
make_forwardref_indexable_subtype)
from beartype._check.forward.reference.fwdreftest import is_beartype_forwardref
from beartype._check.code.snip.codesnipstr import (
CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX,
CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX,
)
from beartype._data.cls.datacls import TYPES_SET_OR_TUPLE
from beartype._data.typing.datatyping import (
LexicalScope,
Pep484585ForwardRef,
SetOrTupleTypes,
TypeOrSetOrTupleTypes,
TupleTypes,
)
from beartype._util.cls.pep.clspep3119 import (
die_unless_type_isinstanceable,
die_unless_object_isinstanceable,
)
from beartype._util.cls.utilclstest import is_type_builtin
from beartype._util.func.utilfuncscope import add_func_scope_attr
from beartype._util.hint.pep.proposal.pep484585.pep484585ref import (
get_hint_pep484585_ref_names)
from beartype._util.utilobject import get_object_type_basename
from collections.abc import Set
# ....................{ ADDERS ~ type }....................
#FIXME: Unit test us up, please.
def add_func_scope_ref(
# Mandatory parameters.
func_scope: LexicalScope,
ref_module_name: Optional[str],
ref_name: str,
# Optional parameters.
exception_prefix: str = 'Globally or locally scoped forward reference ',
) -> str:
'''
Add a new **scoped forward reference proxy** (i.e., new key-value pair of
the passed dictionary mapping from the name to value of each globally or
locally scoped attribute externally accessed elsewhere, whose key is a
machine-readable name internally generated by this function to uniquely
refer to a new forward reference proxy proxying the class with the passed
attribute name residing in the module with the passed module name) to the
passed scope *and* return that name.
Parameters
----------
func_scope : LexicalScope
Local or global scope to add this class or tuple of classes to.
ref_module_name : Optional[str]
Possibly undefined fully-qualified module name referred to by this
forward reference.
ref_name : str
Possibly unqualified classname referred to by this forward reference.
exception_prefix : str, optional
Human-readable label prefixing the representation of this object in the
exception message. Defaults to a sensible string.
Returns
-------
str
Name of this forward reference proxy in this scope generated by this
function.
Raises
------
_BeartypeUtilCallableException
If an attribute with the same name as that internally generated by this
adder but having a different value already exists in this scope. This
adder uniquifies names by object identifier and should thus *never*
generate name collisions. This exception is thus intentionally raised
as a private rather than public exception.
'''
# Forward reference proxy referring to this class.
hint_ref = make_forwardref_indexable_subtype(ref_module_name, ref_name)
# Name of a new parameter passing this forward reference proxy.
hint_ref_arg_name = add_func_scope_attr(
func_scope=func_scope, attr=hint_ref)
# Return this name.
return hint_ref_arg_name
# ....................{ ADDERS ~ type }....................
#FIXME: Unit test us up, please.
def add_func_scope_type_or_types(
# Mandatory parameters.
func_scope: LexicalScope,
type_or_types: TypeOrSetOrTupleTypes,
# Optional parameters.
exception_prefix: str = (
'Globally or locally scoped class or tuple of classes '),
) -> str:
'''
Add a new **scoped class or tuple of classes** (i.e., new key-value pair of
the passed dictionary mapping from the name to value of each globally or
locally scoped attribute externally accessed elsewhere, whose key is a
machine-readable name internally generated by this function to uniquely
refer to the passed class or tuple of classes and whose value is that class
or tuple) to the passed scope *and* return that name.
This function additionally caches this tuple with the beartypistry
singleton to reduce space consumption for tuples duplicated across the
active Python interpreter.
Parameters
----------
func_scope : LexicalScope
Local or global scope to add this class or tuple of classes to.
type_or_types : TypeOrSetOrTupleTypes
Classes to be added to this scope, defined as either:
* A single class.
* A set of one or more classes.
* A tuple of one or more classes.
exception_prefix : str, optional
Human-readable label prefixing the representation of this object in the
exception message. Defaults to a sensible string.
Returns
-------
str
Name of this class or tuple in this scope generated by this function.
Raises
------
BeartypeDecorHintNonpepException
If this hint is either:
* Neither a class nor tuple.
* A tuple that is empty.
BeartypeDecorHintPep3119Exception
If hint is:
* A class that is *not* isinstanceable (i.e., passable as the second
argument to the :func:`isinstance` builtin).
* A tuple of one or more items that are *not* isinstanceable classes.
_BeartypeUtilCallableException
If an attribute with the same name as that internally generated by this
adder but having a different value already exists in this scope. This
adder uniquifies names by object identifier and should thus *never*
generate name collisions. This exception is thus intentionally raised
as a private rather than public exception.
'''
# Return either...
return (
# If this hint is a class, the name of a new parameter passing this
# class;
add_func_scope_type(
func_scope=func_scope,
cls=type_or_types,
exception_prefix=exception_prefix,
)
if isinstance(type_or_types, type) else
# Else, this hint is *NOT* a class. In this case:
# * If this hint is a tuple of classes, the name of a new parameter
# passing this tuple.
# * Else, raise an exception.
add_func_scope_types(
func_scope=func_scope,
types=type_or_types,
exception_prefix=exception_prefix,
)
)
def add_func_scope_type(
# Mandatory parameters.
func_scope: LexicalScope,
cls: type,
# Optional parameters.
exception_prefix: str = 'Globally or locally scoped class ',
) -> str:
'''
Add a new **scoped class** (i.e., new key-value pair of the passed
dictionary mapping from the name to value of each globally or locally scoped
attribute externally accessed elsewhere, whose key is a machine-readable
name internally generated by this function to uniquely refer to the passed
class and whose value is that class) to the passed scope *and* return that
name.
Parameters
----------
func_scope : LexicalScope
Local or global scope to add this class to.
cls : type
Arbitrary class to be added to this scope.
exception_prefix : str, optional
Human-readable label prefixing the representation of this object in the
exception message. Defaults to a sensible string.
Returns
-------
str
Name of this class in this scope generated by this function.
Raises
------
BeartypeDecorHintPep3119Exception
If this class is *not* isinstanceable (i.e., passable as the second
argument to the :func:`isinstance` builtin).
_BeartypeUtilCallableException
If an attribute with the same name as that internally generated by this
adder but having a different value already exists in this scope. This
adder uniquifies names by object identifier and should thus *never*
generate name collisions. This exception is thus intentionally raised
as a private rather than public exception.
'''
# If this object is *NOT* an isinstanceable class, raise an exception.
die_unless_type_isinstanceable(cls=cls, exception_prefix=exception_prefix)
# Else, this object is an isinstanceable class.
# Return either...
return (
# If this type is a builtin (i.e., globally accessible C-based type
# requiring *no* explicit importation), the unqualified basename of
# this type as is, as this type requires no parametrization;
get_object_type_basename(cls)
if is_type_builtin(cls) else
# Else, the name of a new parameter passing this class.
add_func_scope_attr(
func_scope=func_scope, attr=cls, exception_prefix=exception_prefix)
)
def add_func_scope_types(
# Mandatory parameters.
func_scope: LexicalScope,
types: SetOrTupleTypes,
# Optional parameters.
is_unique: Optional[bool] = None,
exception_prefix: str = (
'Globally or locally scoped set or tuple of classes '),
) -> str:
'''
Add a new **scoped tuple of classes** (i.e., new key-value pair of the
passed dictionary mapping from the name to value of each globally or locally
scoped attribute externally accessed elsewhere, whose key is a
machine-readable name internally generated by this function to uniquely
refer to the passed set or tuple of classes and whose value is that tuple)
to the passed scope *and* return that machine-readable name.
This function additionally caches this tuple with the
:data:`._tuple_union_to_tuple_union` dictionary to reduce space consumption
for tuples duplicated across the active Python interpreter.
Parameters
----------
func_scope : LexicalScope
Local or global scope to add this object to.
types : SetOrTupleOfTypes
Set or tuple of arbitrary types to be added to this scope.
is_unique : Optional[bool]
Tri-state boolean governing whether this function attempts to
deduplicate types in the ``types`` iterable. Specifically, either:
* :data:`True`, in which case the caller guarantees ``types`` to contain
*no* duplicate types.
* :data:`False`, in which case this function assumes ``types`` to
contain duplicate types by internally (in order):
#. Coercing this tuple into a set, thus implicitly ignoring both
duplicates and ordering of types in this tuple.
#. Coercing that set back into another tuple.
#. If these two tuples differ, the passed tuple contains one or more
duplicates; in this case, the duplicate-free tuple is cached and
passed.
#. Else, the passed tuple contains no duplicates; in this case, the
passed tuple is cached and passed.
* :data:`None`, in which case this function reduces this parameter to
either:
* :data:`True` if ``types`` is a :class:`tuple`.
* :data:`False` if ``types`` is a :class:`set`.
This tri-state boolean does *not* simply enable an edge-case
optimization, though it certainly does that; this boolean enables
callers to guarantee that this function caches and passes the passed
tuple rather than a new tuple internally created by this function.
Defaults to :data:`None`.
exception_prefix : str, optional
Human-readable label prefixing the representation of this object in the
exception message. Defaults to a sensible string.
Returns
-------
str
Name of this tuple in this scope generated by this function.
Raises
------
BeartypeDecorHintNonpepException
If this hint is either:
* Neither a set nor tuple.
* A set or tuple that is empty.
BeartypeDecorHintPep3119Exception
If one or more items of this hint are *not* isinstanceable classes
(i.e., classes passable as the second argument to the
:func:`isinstance` builtin).
_BeartypeUtilCallableException
If an attribute with the same name as that internally generated by this
adder but having a different value already exists in this scope. This
adder uniquifies names by object identifier and should thus *never*
generate name collisions. This exception is thus intentionally raised
as a private rather than public exception.
'''
assert isinstance(is_unique, NoneTypeOr[bool]), (
f'{repr(is_unique)} neither bool nor "None".')
# ....................{ VALIDATE }....................
# If this container is neither a set nor tuple, raise an exception.
if not isinstance(types, TYPES_SET_OR_TUPLE):
raise BeartypeDecorHintNonpepException(
f'{exception_prefix}{repr(types)} neither set nor tuple.')
# Else, this container is either a set or tuple.
#
# If this container is empty, raise an exception.
elif not types:
raise BeartypeDecorHintNonpepException(f'{exception_prefix}empty.')
# Else, this container is non-empty.
#
# If this container only contains one type, register only this type.
elif len(types) == 1:
return add_func_scope_type(
# The first and only item of this container, accessed as either:
# * If this container is a tuple, that item with fast indexing.
# * If this container is a set, that item with slow iteration.
cls=types[0] if isinstance(types, tuple) else next(iter(types)),
func_scope=func_scope,
exception_prefix=exception_prefix,
)
# Else, this container either contains two or more types.
# If the caller did *NOT* explicitly pass the "is_unique" parameter, default
# this parameter to true *ONLY* if this container is a set.
if is_unique is None:
is_unique = isinstance(types, set)
# Else, the caller explicitly passed the "is_unique" parameter.
#
# In either case, "is_unique" is now a proper bool.
assert isinstance(is_unique, bool)
# ....................{ FORWARDREF }....................
# True only if this container contains one or more beartype-specific forward
# reference proxies. Although these proxies are technically isinstanceable
# classes, attempting to pass these proxies as the second parameter to the
# isinstance() builtin also raises exceptions when the underlying
# user-defined classes proxied by these proxies have yet to be declared.
# Since these proxies are thus *MUCH* more fragile than standard classes, we
# reduce the likelihood of exceptions by deprioritizing these proxies in
# this container (i.e., moving these proxies to the end of this container).
is_types_ref = False
# For each type in this container...
for cls in types:
# If this type is a beartype-specific forward reference proxy...
if is_beartype_forwardref(cls):
# print(f'Found forward reference proxy {repr(cls)}...')
# Note that this container contains at least one such proxy.
is_types_ref = True
# Halt iteration.
break
# If this container contains at least one such proxy...
if is_types_ref:
# List of all such proxies in this container.
#
# Note that we intentionally avoid instantiating this pair of lists
# above in the common case that this container contains no such proxies.
types_ref: List[type] = []
# List of all other types in this container (i.e., normal types that are
# *NOT* beartype-specific forward reference proxies).
types_nonref: List[type] = []
# For each type in this container...
for cls in types:
# If this type is such a proxy, append this proxy to the list of all
# such proxies.
if is_beartype_forwardref(cls):
types_ref.append(cls)
# Else, this type is *NOT* such a proxy. In this case...
else:
# print(f'Appending non-forward reference proxy {repr(cls)}...')
# If this non-proxy is *NOT* an isinstanceable class, raise an
# exception.
#
# Note that the companion "types_ref" tuple is intentionally
# *NOT* validated above. Why? Because doing so would prematurely
# invoke the __instancecheck__() dunder method on the metaclass
# of the proxies in that tuple, which would then erroneously
# attempt to resolve the possibly undefined types to which those
# proxies refer. Instead, simply accept that tuple of proxies as
# is for now and defer validating those proxies for later.
die_unless_type_isinstanceable(
cls=cls, exception_prefix=exception_prefix)
# Append this proxy to the list of all non-proxy types
types_nonref.append(cls)
# If the caller guaranteed these tuples to be duplicate-free,
# efficiently concatenate these lists into a tuple such that all
# non-proxy types appear *BEFORE* all proxy types.
if is_unique:
types = tuple(types_nonref + types_ref)
# Else, the caller failed to guarantee these tuples to be
# duplicate-free. In this case, coerce these tuples into (in order):
# * Sets, thus ignoring duplicates and ordering.
# * Back into duplicate-free tuples.
else:
types = tuple(set(types_nonref)) + tuple(set(types_ref))
# Else, the caller guaranteed these tuples to be duplicate-free.
# Else, this container contains *NO* such proxies. In this case, preserve
# the ordering of items in this container as is.
else:
# If this container is a set, coerce this frozenset into a tuple.
if isinstance(types, Set):
types = tuple(types)
# Else, this container is *NOT* a set. By elimination, this container
# should now be a tuple.
#
# In either case, this container should now be a tuple.
# If this container is *NOT* a tuple or is a tuple containing one or
# more items that are *NOT* isinstanceable classes, raise an exception.
die_unless_object_isinstanceable(
obj=types, exception_prefix=exception_prefix)
# Else, this container is a tuple of only isinstanceable classes.
# If the caller failed to guarantee this tuple to be duplicate-free,
# coerce this tuple into (in order):
# * A set, thus ignoring duplicates and ordering.
# * Back into a duplicate-free tuple.
if not is_unique:
# print(f'Uniquifying type tuple {repr(types)} to...')
types = tuple(set(types))
# print(f'...uniquified type tuple {repr(types)}.')
# Else, the caller guaranteed this tuple to be duplicate-free.
# In either case, this container is now guaranteed to be a tuple containing
# only duplicate-free classes.
assert isinstance(types, tuple), (
f'{exception_prefix}{repr(types)} not tuple.')
# ....................{ CACHE }....................
# If this tuple has *NOT* already been cached, do so.
if types not in _tuple_union_to_tuple_union:
_tuple_union_to_tuple_union[types] = types
# Else, this tuple has already been cached. In this case, deduplicate this
# tuple by reusing the previously cached tuple.
else:
types = _tuple_union_to_tuple_union[types]
# ....................{ RETURN }....................
# Return the name of a new parameter passing this tuple.
return add_func_scope_attr(
attr=types, func_scope=func_scope, exception_prefix=exception_prefix)
# ....................{ EXPRESSERS ~ type }....................
def express_func_scope_type_ref(
# Mandatory parameters.
func_scope: LexicalScope,
forwardref: Pep484585ForwardRef,
refs_type_basename: Optional[set],
# Optional parameters.
exception_prefix: str = 'Globally or locally scoped forward reference ',
) -> Tuple[str, Optional[set]]:
'''
Express the passed :pep:`484`- or :pep:`585`-compliant **forward reference**
(i.e., fully-qualified or unqualified name of an arbitrary class that
typically has yet to be declared) as a Python expression evaluating to this
forward reference when accessed via the beartypistry singleton added as a
new key-value pair of the passed dictionary, whose key is the string
:attr:`beartype._data.code.datacodename.ARG_NAME_TYPISTRY` and whose value is the
beartypistry singleton.
Parameters
----------
func_scope : LexicalScope
Local or global scope to add this forward reference to.
forwardref : Pep484585ForwardRef
Forward reference to be expressed relative to this scope.
refs_type_basename : Optional[set]
Set of all existing **relative forward references** (i.e., unqualified
basenames of all types referred to by all relative forward references
relative to this scope) if any *or* :data:`None` otherwise (i.e., if no
relative forward references have been expressed relative to this scope).
exception_prefix : str, optional
Human-readable substring prefixing raised exception messages. Defaults
to a sensible string.
Returns
-------
Tuple[str, Optional[set]]
2-tuple ``(forwardref_expr, refs_type_basename)``, where:
* ``forwardref_expr`` is the Python expression evaluating to this
forward reference when accessed via the beartypistry singleton added
to this scope.
* ``refs_type_basename`` is either:
* If this forward reference is a fully-qualified classname, the
passed ``refs_type_basename`` set as is.
* If this forward reference is an unqualified classname, either:
* If the passed ``refs_type_basename`` set is *not* :data:`None`,
this set with this classname added to it.
* Else, a new set containing only this classname.
Raises
------
BeartypeDecorHintForwardRefException
If this forward reference is *not* actually a forward reference.
'''
# Possibly undefined fully-qualified module name and possibly unqualified
# classname referred to by this forward reference.
ref_module_name, ref_name = get_hint_pep484585_ref_names(
hint=forwardref, exception_prefix=exception_prefix)
# If either...
if (
# This reference was instantiated with a module name...
ref_module_name or
# This classname contains one or more "." characters and is thus already
# (...hopefully) fully-qualified...
'.' in ref_name
# Then this classname is either absolute *OR* relative to some module. In
# either case, the class referred to by this reference can now be
# dynamically imported at a later time. In this case...
):
# Name of the hidden parameter providing this forward reference
# proxy to be passed to this wrapper function.
ref_expr = add_func_scope_ref(
func_scope=func_scope,
ref_module_name=ref_module_name,
ref_name=ref_name,
exception_prefix=exception_prefix,
)
# Else, this classname is unqualified. In this case...
else:
assert isinstance(refs_type_basename, NoneTypeOr[set]), (
f'{repr(refs_type_basename)} neither set nor "None".')
# If this set of unqualified classnames referred to by all relative
# forward references has yet to be instantiated, do so.
if refs_type_basename is None:
refs_type_basename = set()
# In any case, this set now exists.
# Add this unqualified classname to this set.
refs_type_basename.add(ref_name)
# Placeholder substring to be replaced by the caller with a Python
# expression evaluating to this unqualified classname canonicalized
# relative to the module declaring the currently decorated callable
# when accessed via the private "__beartypistry" parameter.
ref_expr = (
f'{CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX}'
f'{ref_name}'
f'{CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX}'
)
# Return a 2-tuple of this expression and set of unqualified classnames.
return (ref_expr, refs_type_basename)
# ....................{ PRIVATE ~ globals }....................
_tuple_union_to_tuple_union: Dict[TupleTypes, TupleTypes] = {}
'''
**Tuple union cache** (i.e., dictionary mapping from each tuple union passed to
the :func:`.add_func_scope_types` adder to that same union, preventing tuple
unions from being duplicated across calls to that adder).
This cache serves a dual purpose. Notably, this cache both enables:
* External callers to iterate over all previously instantiated forward reference
proxies. This is particularly useful when responding to module reloading,
which requires that *all* previously cached types be uncached.
* A minor reduction in space complexity by de-duplicating duplicating tuple
unions. Since the existing ``callable_cached`` decorator could trivially do so
as well, however, this is only a negligible side effect.
'''
|