Skip to content

Commit

Permalink
Postponed evaluation of non-referential hints x 1.
Browse files Browse the repository at this point in the history
This commit is the first in a commit chain generalizing @beartype's
support for forward references from **type forward references** (i.e.,
references to user-defined *types* that have yet to be defined in the
current lexical scope) to **arbitrary forward references** (i.e.,
references to user-defined *objects* that have yet to be defined in the
current lexical scope, regardless of whether those objects are types or
not), en-route to resolving feature request #226 kindly submitted by
Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong).
Specifically, this commit:

* Defines a new private `beartype._decor.cache.cachescope` submodule.
* Defines a new `BeartypeableScopeDeferrer` dictionary subclass in that
  submodule. Naturally, this subclass is only partially defined, mostly
  undocumented, and thoroughly untested. The journey of a thousand
  commits begins with a single broken commit.

(*A little belittling is bewitching, but which?*)
  • Loading branch information
leycec committed Aug 11, 2023
1 parent 547f37e commit 1f33612
Show file tree
Hide file tree
Showing 17 changed files with 141 additions and 35 deletions.
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# ....................{ INCLUDE }....................
# Include all requisite top-level install-time files.
include .coveragerc
include .readthedocs.yml
include .readthedocs.yaml
include LICENSE
include MANIFEST.in
include README.rst
Expand Down
2 changes: 1 addition & 1 deletion beartype/_check/code/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1672,7 +1672,7 @@
#* In that submodule:
# * Rename the existing @beartype decorator to beartype_template(). That
# function will now only be called internally rather than externally.
#* Define a new private "beartype._decor._cache.cachedecor" submodule.
#* Define a new private "beartype._decor.cache.cachedecor" submodule.
#* In that submodule:
# * Define a new "BEARTYPE_PARAMS_TO_DECOR" dictionary mapping from a *TUPLE*
# of positional arguments listed in the exact same order as the optional
Expand Down
2 changes: 1 addition & 1 deletion beartype/_check/code/_codescope.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from beartype.roar import BeartypeDecorHintNonpepException
from beartype._cave._cavemap import NoneTypeOr
from beartype._data.hint.datahinttyping import LexicalScope
from beartype._decor._cache.cachetype import (
from beartype._decor.cache.cachetype import (
TYPISTRY_HINT_NAME_TUPLE_PREFIX,
bear_typistry,
register_typistry_forwardref,
Expand Down
4 changes: 2 additions & 2 deletions beartype/_check/convert/convcoerce.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def coerce_func_hint_root(
type hints can be meaningfully memoized. Since this high-level function
internally defers to unmemoized low-level functions that are ``O(n)`` in
``n`` the size of the inheritance hierarchy of this hint, this function
should be called sparingly. See the :mod:`beartype._decor._cache.cachehint`
should be called sparingly. See the :mod:`beartype._decor.cache.cachehint`
submodule for further details.
Parameters
Expand Down Expand Up @@ -212,7 +212,7 @@ def coerce_hint_root(hint: object, exception_prefix: str) -> object:
type hints can be meaningfully memoized. Since this high-level function
internally defers to unmemoized low-level functions that are ``O(n)`` for
``n`` the size of the inheritance hierarchy of this hint, this function
should be called sparingly. See the :mod:`beartype._decor._cache.cachehint`
should be called sparingly. See the :mod:`beartype._decor.cache.cachehint`
submodule for further details.
Parameters
Expand Down
2 changes: 1 addition & 1 deletion beartype/_decor/_wrap/wrapmain.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
ARG_NAME_RETURN,
ARG_NAME_RETURN_REPR,
)
from beartype._decor._cache.cachetype import (
from beartype._decor.cache.cachetype import (
bear_typistry,
register_typistry_forwardref,
)
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
This private submodule defines the core :func:`beartype.beartype` decorator,
conditionally imported (in order):
#. Into the parent :mod:`beartype._decor.decormain`
submodule if this decorator is *not* currently reducing to a noop (e.g., due
to ``python3 -O`` optimization).
#. Into the parent :mod:`beartype._decor.decormain` submodule if this decorator
is *not* currently reducing to a noop (e.g., due to ``python3 -O``
optimization).
#. Into the root :mod:`beartype.__init__` submodule if the :mod:`beartype`
package is *not* currently being installed by :mod:`setuptools`.
Expand Down
93 changes: 93 additions & 0 deletions beartype/_decor/cache/cachescope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
# --------------------( LICENSE )--------------------
# Copyright (c) 2014-2023 Beartype authors.
# See "LICENSE" for further details.

'''
**Beartypeable scope deferrers** (i.e., dictionaries deferring the resolutions
of local and global scopes of classes and callables decorated by the
:func:`beartype.beartype` decorator when dynamically evaluating stringified type
hints for those classes and callables).
This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS }....................
from beartype.typing import Dict
# from beartype._data.hint.datahinttyping import BeartypeableT

# ....................{ SUBCLASSES }....................
class BeartypeableScopeDeferrer(dict):
'''
**Beartypeable scope deferrer** (i.e., dictionary deferring the resolution
of a local or global scope of a **beartypeable** (i.e., class or callable
decorated by the :func:`beartype.beartype` decorator) when dynamically
evaluating stringified type hints for this beartypeable, including both
forward references *and* :pep:`563`-postponed type hints).
Attributes
----------
module_name : str
Fully-qualified name of the (sub)module declaring the local or global
scope deferred by this deferrer.
'''

# ..................{ INITIALIZERS }..................
#FIXME: Implement us up, please.
#FIXME: Docstring us up, please.
def __init__(
self, module_name: str, scope_attrs: Dict[str, object]) -> None:

# Initialize our superclass with this dictionary mapping from each
# attribute name to value of the (possibly only partially initialized)
# local or global scope of this beartypeable.
super().__init__(scope_attrs)

# Classify all passed parameters.
self.module_name = module_name

# ..................{ DUNDERS }..................
def __missing__(self, attr_name: str) -> object:
'''
Dunder method explicitly called by the superclass
:meth:`dict.__getitem__` method implicitly called on caller attempts to
access the passed missing key with ``[``- and ``]``-delimited syntax.
This method treats this attempt to retrieve this missing key as an
attempt to resolve an attribute of this (possibly only partially
initialized) local or global scope that has yet to be defined in this
scope -- but hopefully will be at some later time *after* this scope is
fully initialized.
Parameters
----------
attr_name : str
Name of the attribute to be resolved as a relative forward
reference, relative to the submodule declaring this scope.
Returns
----------
object
Attribute whose name is this missing key.
Raises
----------
BeartypeCallHintForwardRefException
If the submodule declaring this scope contains *no* attribute with
this name.
'''

#FIXME: Implement us up, please.
# # Module attribute whose fully-qualified name is this forward
# # reference, dynamically imported at callable call time.
# hint_class: type = import_module_attr(
# module_attr_name=hint_classname,
# exception_cls=BeartypeCallHintForwardRefException,
# exception_prefix='Forward reference ',
# )
#
# # Return this class. The superclass dict.__getitem__() dunder method
# # then implicitly maps the passed missing key to this class by
# # effectively assigning this name to this class: e.g.,
# # self[hint_classname] = hint_class
# return hint_class # type: ignore[return-value]
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ TODO }....................
#FIXME: Consider obsoleting this submodule entirely in favour of the more
#general-purpose and significantly more powerful "cachescope" submodule, please.

# ....................{ IMPORTS }....................
from beartype.roar import (
BeartypeCallHintForwardRefException,
Expand Down Expand Up @@ -99,7 +103,7 @@ class Beartypistry(dict):
'''
**Beartypistry** (i.e., singleton dictionary mapping from strings uniquely
identifying PEP-noncompliant type hints annotating callables decorated
by the :func:`beartype.beartype` decorator to those hints).**
by the :func:`beartype.beartype` decorator to those hints).
This dictionary implements a global registry for **PEP-noncompliant type
hints** (i.e., :mod:`beartype`-specific annotation *not* compliant with
Expand Down
2 changes: 1 addition & 1 deletion beartype/_decor/decorcore.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
'''
**Unmemoized beartype decorators** (i.e., core lower-level unmemoized decorators
underlying the higher-level memoized :func:`beartype.beartype` decorator, whose
implementation in the parent :mod:`beartype._decor._cache.cachedecor` submodule
implementation in the parent :mod:`beartype._decor.cache.cachedecor` submodule
is a thin wrapper efficiently memoizing closures internally created and returned
by that decorator; in turn, those closures directly defer to this submodule).
Expand Down
2 changes: 1 addition & 1 deletion beartype/_decor/decormain.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def beartype( # type: ignore[no-redef]
# case, define the @beartype decorator in the standard way.
else:
# This is where @beartype *REALLY* lives. Grep here for all the goods.
from beartype._decor._cache.cachedecor import beartype
from beartype._decor.cache.cachedecor import beartype

# ....................{ DECORATORS ~ doc }....................
# Document the @beartype decorator with the same documentation regardless of
Expand Down
2 changes: 1 addition & 1 deletion beartype/claw/_clawmagic.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
'''

# ....................{ STRINGS ~ decorator }....................
BEARTYPE_DECORATOR_MODULE_NAME = 'beartype._decor._cache.cachedecor'
BEARTYPE_DECORATOR_MODULE_NAME = 'beartype._decor.cache.cachedecor'
'''
Fully-qualified name of the submodule defining the **beartype decorator** (i.e.,
:mod:`beartype` decorator applied by our abstract syntax tree (AST) node
Expand Down
2 changes: 1 addition & 1 deletion beartype/door/_doorcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
BeartypeConf,
)
# from beartype._data.hint.datahintfactory import TypeGuard
from beartype._decor._cache.cachedecor import beartype
from beartype._decor.cache.cachedecor import beartype
from beartype._util.cache.utilcachecall import callable_cached
from beartype._util.error.utilerror import reraise_exception_placeholder
from beartype._util.hint.utilhinttest import die_unless_hint
Expand Down
2 changes: 1 addition & 1 deletion beartype/roar/_roarexc.py
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,7 @@ class _BeartypeDecorBeartypistryException(BeartypeDecorException):
This exception is raised at decoration time from the
:func:`beartype.beartype` decorator when erroneously accessing the
**beartypistry** (i.e.,
:class:`beartype._decor._cache.cachetype.bear_typistry` singleton).
:class:`beartype._decor.cache.cachetype.bear_typistry` singleton).
This private exception denotes a critical internal issue and should thus
*never* be raised -- let alone exposed to end users.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def test_express_func_scope_type_forwardref() -> None:

# Defer test-specific imports.
from beartype.roar import BeartypeDecorHintForwardRefException
from beartype._decor._cache.cachetype import bear_typistry
from beartype._decor.cache.cachetype import bear_typistry
from beartype._check.checkmagic import ARG_NAME_TYPISTRY
from beartype._check.code._codescope import (
express_func_scope_type_forwardref)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
**Beartypistry unit tests.**
This submodule unit tests the
:attr:`beartype._decor._cache.cachetype.bear_typistry` singleton.
:attr:`beartype._decor.cache.cachetype.bear_typistry` singleton.
'''

# ....................{ IMPORTS }....................
Expand All @@ -20,12 +20,12 @@
def test_typistry_singleton_pass() -> None:
'''
Test successful usage of the
:attr:`beartype._decor._cache.cachetype.bear_typistry` singleton.
:attr:`beartype._decor.cache.cachetype.bear_typistry` singleton.
'''

# Defer test-specific imports.
from beartype.roar._roarexc import _BeartypeDecorBeartypistryException
from beartype._decor._cache.cachetype import bear_typistry
from beartype._decor.cache.cachetype import bear_typistry
from beartype._util.utilobject import get_object_type_name
from pytest import raises

Expand Down Expand Up @@ -54,12 +54,12 @@ class TestTypistrySingletonPassType(object): pass
def test_typistry_singleton_fail() -> None:
'''
Test unsuccessful usage of the
:attr:`beartype._decor._cache.cachetype.bear_typistry` singleton.
:attr:`beartype._decor.cache.cachetype.bear_typistry` singleton.
'''

# Defer test-specific imports.
from beartype.roar._roarexc import _BeartypeDecorBeartypistryException
from beartype._decor._cache.cachetype import bear_typistry
from beartype._decor.cache.cachetype import bear_typistry
from pytest import raises

# Assert that keys that are *NOT* strings are *NOT* registrable.
Expand Down Expand Up @@ -90,15 +90,15 @@ def test_typistry_singleton_fail() -> None:
# '''
# Dynamically evaluate the passed Python expression (assumed to be a string
# returned by either the
# :func:`beartype._decor._cache.cachetype.register_typistry_type` or
# :func:`beartype._decor._cache.cachetype.register_typistry_tuple` functions)
# :func:`beartype._decor.cache.cachetype.register_typistry_type` or
# :func:`beartype._decor.cache.cachetype.register_typistry_tuple` functions)
# *and* return the resulting value (assumed to either be a type or tuple of
# types).
# '''
# assert isinstance(hint_expr, str), '{repr(hint_expr)} not string.'
#
# # Defer test-specific imports.
# from beartype._decor._cache.cachetype import bear_typistry
# from beartype._decor.cache.cachetype import bear_typistry
# from beartype._decor._wrap.wrapsnip import ARG_NAME_TYPISTRY
#
# # Dictionary of all local variables required to evaluate this expression.
Expand Down
31 changes: 20 additions & 11 deletions doc/src/api_decor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -456,14 +456,20 @@ Beartype Configuration API
>>> BeartypeConf().strategy
<BeartypeStrategy.O1: 2>
Beartype configurations support various **optional read-only keyword-only**
Keyword Parameters
------------------

Beartype configurations support **optional read-only keyword-only**
parameters at instantiation time. Most parameters are suitable for passing by
*all* beartype users in *all* possible use cases. Some, however, are only
intended to be passed by *some* beartype users in *some* isolated use cases.
*all* beartype users in *all* possible use cases. Some are only intended to
be passed by *some* beartype users in *some* isolated use cases.

This is their story.

General-purpose configuration parameters that are *always* safely passable
include:
General Keyword Parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^

General-purpose configuration parameters are *always* safely passable:

.. py:attribute:: is_debug
Expand Down Expand Up @@ -612,10 +618,13 @@ Beartype Configuration API
strategy – maximizing scalability at a cost of also maximizing false
positives.

**Executable-only configuration parameters** (i.e., parameters intended to be
passed *only* by downstream Python packages that are typically executed as an
app, binary, script, server, or other executable process rather than imported
from other Python packages in the current Python process) include:
App-only Keyword Parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^^

**App-only configuration parameters** are passed *only* by first-party
packages executed as apps, binaries, scripts, servers, or other executable
processes (rather than imported as libraries, frameworks, or other importable
APIs into the current process):

.. py:attribute:: is_color
Expand Down Expand Up @@ -857,12 +866,12 @@ variables globally override :class:`.BeartypeConf` parameters of similar names,
enabling end users to enforce global configuration policies across their full
app stacks.
Beneath environment variables thy humongous codebase shalt rise!
Beneath environment variables... *thy humongous codebase shalt rise.*
.. _api_decor:beartype_is_color:
${BEARTYPE_IS_COLOR}
********************
--------------------
The ``${BEARTYPE_IS_COLOR}`` environment variable globally overrides the
:attr:`.BeartypeConf.is_color` parameter, enabling end users to enforce a global
Expand Down

0 comments on commit 1f33612

Please sign in to comment.