Skip to content

Commit

Permalink
beartype.claw resurrection x 7.
Browse files Browse the repository at this point in the history
This commit is the next in a commit chain resurrecting our *nearly*
complete `beartype.claw` API from an early and ignominious grave,
en-route to resolving feature request #43 kindly submitted a literal
lifetime ago by @ZeevoX Beeblebrox the gentle typing giant.
Specifically, this commit does... such a glut of new things that @leycec
rapidly lost the plot as to what, exactly, if anything, he did. It's
likely that this is important stuff, however. Let's believe it.
(*Impressive imps limp regressively!*)
  • Loading branch information
leycec committed May 6, 2023
1 parent 2768816 commit 2235560
Show file tree
Hide file tree
Showing 18 changed files with 455 additions and 126 deletions.
35 changes: 17 additions & 18 deletions beartype/_decor/decorcore.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ def beartype_object_nonfatal(
warning_category: TypeWarning,

# Optional parameters.
cls_stack: TypeStack = None,
**kwargs
) -> BeartypeableT:
'''
Expand Down Expand Up @@ -244,11 +243,6 @@ def beartype_object_nonfatal(
warning_category : TypeWarning
Category of the non-fatal warning to emit if :func:`beartype.beartype`
fails to generate a type-checking wrapper for this callable or class.
cls_stack : TypeStack, optional
**Type stack** (i.e., either a tuple of the one or more
:func:`beartype.beartype`-decorated classes lexically containing the
class variable or method annotated by this hint *or* :data:`None`).
Defaults to :data:`None`.
All remaining keyword parameters are passed as is to the lower-level
:func:`.beartype_object` decorator internally called by this higher-level
Expand Down Expand Up @@ -284,16 +278,19 @@ class variable or method annotated by this hint *or* :data:`None`).
assert isinstance(warning_category, Warning), (
f'{repr(warning_category)} not warning category.')

#FIXME: Unconditionally munge this error message by:
#* Globally replacing *EACH* newline (i.e., "\n" substring) in this
# message with a newline followed by four spaces (i.e., "\n ").
#* Stripping all ANSI colors. While colors are useful for exception
# messages that typically percolate down to the terminal, warnings are
# another breed entirely. Maybe? Maybe.
# Avoid circular import dependencies.
from beartype._util.text.utiltextansi import strip_text_ansi
from beartype._util.text.utiltextlabel import label_beartypeable_kind
from beartype._util.text.utiltextmunge import uppercase_char_first

# Original error message to be embedded in the warning message to be
# emitted, defined as either...
error_message = (
# emitted, stripped of *ALL* ANSI color. While colors improve the
# readability of exception messages that percolate down to an ANSI-aware
# command line, warnings are usually harvested and then regurgitated by
# intermediary packages into ANSI-unaware logfiles.
#
# This message is defined as either...
error_message = strip_text_ansi(
# If this exception is beartype-specific, this exception's message
# is probably human-readable as is. In this case, coerce only that
# message directly into a warning for brevity and readability.
Expand All @@ -308,6 +305,12 @@ class variable or method annotated by this hint *or* :data:`None`).
format_exc()
)

# Globally replace *EVERY* newline in this message with a newline
# followed by four spaces. Doing so visually offsets this lower-level
# exception message from the higher-level warning message embedding this
# exception message.
error_message.replace('\n', '\n ')

#FIXME: Woops. Looks like we accidentally duplicated functionality here
#that already exists in the label_callable() function. Let's generalize
#that functionality out of label_callable() into a lower-level
Expand All @@ -320,10 +323,6 @@ class variable or method annotated by this hint *or* :data:`None`).
#
# obj_label_capitalized = uppercase_char_first(obj_label)

# Avoid circular import dependencies.
from beartype._util.text.utiltextlabel import label_beartypeable_kind
from beartype._util.text.utiltextmunge import uppercase_char_first

# Fully-qualified name of this beartypeable.
obj_name = get_object_name(obj)

Expand Down
60 changes: 59 additions & 1 deletion beartype/_util/cls/utilclsget.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,70 @@

# ....................{ IMPORTS }....................
from beartype.roar._roarexc import _BeartypeUtilTypeException
from beartype.typing import Optional
from beartype._data.datatyping import (
LexicalScope,
TypeException,
)

# ....................{ VALIDATORS }....................
# ....................{ GETTERS }....................
#FIXME: Unit test us up.
def get_type_filename_or_none(cls: type) -> Optional[str]:
'''
Absolute filename of the file on the local filesystem containing the
pure-Python source code for the script or module defining the passed class
if that class is defined on-disk *or* :data:`None` otherwise (i.e., if that
class is dynamically defined in-memory by a prior call to the :func:`exec`
or :func:`eval` builtins).
Parameters
----------
cls : type
Class to be inspected.
Returns
----------
Optional[str]
Either:
* If this class was physically declared by a file, the absolute filename
of that file.
* If this class was dynamically declared in-memory, :data:`None`.
'''

# Avoid circular import dependencies.
from beartype._util.mod.utilmodget import (
get_module_filename_or_none,
get_object_module_name_or_none,
)
from beartype._util.mod.utilmodimport import get_module_imported_or_none

# Fully-qualified name of the module declaring this type if any *OR* "None".
#
# Note that *ALL* types should be declared by *SOME* modules. Nonetheless,
# this is Python. It's best to assume the worst.
type_module_name = get_object_module_name_or_none(cls)

# If a module declares this type...
if type_module_name:
# This module if previously imported *OR* "None".
#
# Note that this module *SHOULD* necessarily already have been imported,
# as this type obviously exists. Nonetheless, this module will be
# unimportable for types dynamically declared in-memory rather than
# on-disk, in which case the name of this module will have been a lie.
type_module = get_module_imported_or_none(type_module_name)

# If this module was previously imported...
if type_module:
# Return the filename defining this module if any *OR* "None".
return get_module_filename_or_none(type_module)

# If all else fails, this type was probably declared in-memory rather than
# on-disk. In this case, fallback to merely returning "None".
return None


#FIXME: Unit test us up, please.
def get_type_locals(
# Mandatory parameters.
Expand Down
14 changes: 7 additions & 7 deletions beartype/_util/func/utilfuncfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def is_func_file(func: Callable) -> bool:
# One-liners for abstruse abstraction.
return get_func_filename_or_none(func) is not None

# ....................{ GETTERS ~ code : lines }....................
# ....................{ GETTERS }....................
def get_func_filename_or_none(
# Mandatory parameters.
func: Callable,
Expand All @@ -76,9 +76,9 @@ def get_func_filename_or_none(
'''
Absolute filename of the file on the local filesystem containing the
pure-Python source code for the script or module defining the passed
callable if that callable is defined on-disk *or* ``None`` otherwise (i.e.,
if that callable is dynamically defined in-memory by a prior call to the
:func:`exec` or :func:`eval` builtins).
callable if that callable is defined on-disk *or* :data:`None` otherwise
(i.e., if that callable is dynamically defined in-memory by a prior call to
the :func:`exec` or :func:`eval` builtins).
Parameters
----------
Expand All @@ -90,9 +90,9 @@ def get_func_filename_or_none(
Optional[str]
Either:
* If the passed callable was physically declared by a file, the
absolute filename of that file.
* If the passed callable was dynamically declared in-memory, ``None``.
* If that callable was physically declared by a file, the absolute
filename of that file.
* If that callable was dynamically declared in-memory, :data:`None`.
'''

# Code object underlying the passed callable if that callable is pure-Python
Expand Down
54 changes: 27 additions & 27 deletions beartype/_util/mod/utilmodget.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,33 +150,6 @@ def get_object_module_line_number_begin(obj: object) -> int:
f'{repr(obj)} neither callable nor class.')

# ....................{ GETTERS ~ object : name }....................
#FIXME: Unit test us up, please.
def get_object_module_name_or_none(obj: object) -> Optional[str]:
'''
**Fully-qualified name** (i.e., ``.``-delimited name prefixed by the
declaring package) of the module declaring the passed object if this object
defines the ``__module__`` dunder instance variable *or* :data:`None`
otherwise.
Parameters
----------
obj : object
Object to be inspected.
Returns
----------
Optional[str]
Either:
* Fully-qualified name of the module declaring this object if this
object declares a ``__module__`` dunder attribute.
* :data:`None` otherwise.
'''

# Let it be, speaking one-liners of wisdom.
return getattr(obj, '__module__', None)


#FIXME: Unit test us up, please.
def get_object_module_name(obj: object) -> str:
'''
Expand Down Expand Up @@ -217,6 +190,33 @@ def get_object_module_name(obj: object) -> str:
# Return this name.
return module_name


#FIXME: Unit test us up, please.
def get_object_module_name_or_none(obj: object) -> Optional[str]:
'''
**Fully-qualified name** (i.e., ``.``-delimited name prefixed by the
declaring package) of the module declaring the passed object if this object
defines the ``__module__`` dunder instance variable *or* :data:`None`
otherwise.
Parameters
----------
obj : object
Object to be inspected.
Returns
----------
Optional[str]
Either:
* Fully-qualified name of the module declaring this object if this
object declares a ``__module__`` dunder attribute.
* :data:`None` otherwise.
'''

# Let it be, speaking one-liners of wisdom.
return getattr(obj, '__module__', None)

# ....................{ GETTERS ~ object : type : name }....................
#FIXME: Unit test us up, please.
def get_object_type_module_name_or_none(obj: object) -> Optional[str]:
Expand Down
43 changes: 38 additions & 5 deletions beartype/_util/mod/utilmodimport.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,34 @@
from types import ModuleType
from warnings import warn

# ....................{ GETTERS }....................
#FIXME: Unit test us up, please.
def get_module_imported_or_none(module_name: str) -> Optional[ModuleType]:
'''
Previously imported module, package, or C extension with the passed
fully-qualified name if previously imported *or* :data:`None` otherwise
(i.e., if that module, package, or C extension has yet to be imported).
Parameters
----------
module_name : str
Fully-qualified name of the previously imported module to be returned.
Returns
----------
Either:
* If a module, package, or C extension with this fully-qualified name has
already been imported, that module, package, or C extension.
* Else, :data:`None`.
'''

# Donkey One-liner Country: Codebase Freeze!
return sys_modules.get(module_name)

# ....................{ IMPORTERS }....................
#FIXME: Preserved until requisite, which shouldn't be long.
#FIXME: Unit test us up.
#FIXME: Unit test us up, please.
# def import_module(
# # Mandatory parameters.
# module_name: str,
Expand Down Expand Up @@ -75,7 +100,7 @@
def import_module_or_none(module_name: str) -> Optional[ModuleType]:
'''
Dynamically import and return the module, package, or C extension with the
passed fully-qualified name if importable *or* return ``None`` otherwise
passed fully-qualified name if importable *or* return :data:`None` otherwise
(i.e., if that module, package, or C extension is unimportable).
For safety, this function also emits a non-fatal warning when that module,
Expand All @@ -87,6 +112,14 @@ def import_module_or_none(module_name: str) -> Optional[ModuleType]:
module_name : str
Fully-qualified name of the module to be imported.
Returns
----------
Either:
* If a module, package, or C extension with this fully-qualified name is
importable, that module, package, or C extension.
* Else, :data:`None`.
Warns
----------
BeartypeModuleUnimportableWarning
Expand All @@ -97,7 +130,7 @@ def import_module_or_none(module_name: str) -> Optional[ModuleType]:

# Module cached with "sys.modules" if this module has already been imported
# elsewhere under the active Python interpreter *OR* "None" otherwise.
module = sys_modules.get(module_name)
module = get_module_imported_or_none(module_name)

# If this module has already been imported, return this cached module.
if module is not None:
Expand Down Expand Up @@ -205,7 +238,7 @@ def import_module_attr_or_none(
'''
Dynamically import and return the **module attribute** (i.e., object
declared at module scope) with the passed fully-qualified name if
importable *or* return ``None`` otherwise.
importable *or* return :data:`None` otherwise.
Parameters
----------
Expand All @@ -223,7 +256,7 @@ def import_module_attr_or_none(
object
Either:
* If *no* module prefixed this name exists, ``None``.
* If *no* module prefixed this name exists, :data:`None`.
* If a module prefixed by this name exists *but* that module declares
no attribute by this name, ``None``.
* Else, the module attribute with this fully-qualified name.
Expand Down
13 changes: 6 additions & 7 deletions beartype/_util/text/utiltextansi.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,20 @@
'''

# ....................{ TESTERS }....................
#FIXME: Unit test us up, please.
def is_text_ansi(text: str) -> bool:
'''
``True`` if the passed text contains one or more ANSI escape sequences.
:data:`True` if the passed text contains one or more ANSI escape sequences.
Parameters
----------
text : str
Text to be tested for ANSI.
Text to be tested.
Returns
----------
bool
``True`` only if this text contains one or more ANSI escape sequences.
:data:`True` only if this text contains one or more ANSI escape
sequences.
'''
assert isinstance(text, str), f'{repr(text)} not string.'

Expand All @@ -72,15 +72,14 @@ def is_text_ansi(text: str) -> bool:
return _ANSI_REGEX.search(text) is not None

# ....................{ STRIPPERS }....................
#FIXME: Unit test us up, please.
def strip_text_ansi(text: str) -> str:
'''
Strip all ANSI escape sequences from the passed string.
Strip *all* ANSI escape sequences from the passed string.
Parameters
----------
text : str
Text to be stripped of ANSI.
Text to be stripped.
Returns
----------
Expand Down

0 comments on commit 2235560

Please sign in to comment.