Skip to content

Commit

Permalink
beartype.claw resurrection x 8.
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 finalizes an initial draft implementation of
the private `@beartype_object_nonfatal` decorator defined by the
`beartype._decor.decorcore` submodule. Since this low-level decorator
underlies the higher-level `beartype.claw` API, we are well on our way
to accomplishing something that will resonate through the ages like a
barnacle-encrusted bell sunken in the Atlantic seabed. (*Barnacle Bob!*)
  • Loading branch information
leycec committed May 10, 2023
1 parent 3bfe3e1 commit d083dc3
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 76 deletions.
42 changes: 22 additions & 20 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -148,50 +148,52 @@ __pycache__/
/.venv

# ....................{ FILES ~ general }....................
# Ignore all audio and video files.
*.mp4

# Ignore all C extensions.
*.so

# Ignore all data interchange files.
*.csv

# Ignore all Django-specific private files.
local_settings.py

# Ignore all pyenv-specific state files.
.python-version

# Ignore all Jython-specific byte-compiled Python files.
*$py.class

# Ignore all "trace"-specific output files.
*.cover

# Ignore all data interchange files.
*.csv
# Ignore all "gettext"-specific intermediary translation files.
*.mo
*.pot

# Ignore all Jupyter Notebook-specific checkpoint files.
.ipynb_checkpoints

# Ignore all logfiles.
*.log

# Ignore all "gettext"-specific intermediary translation files.
*.mo
*.pot
# Ignore all macOS-specific filesystem viewer configuration files.
.DS_Store

# Ignore all audio and video files.
*.mp4
# Ignore all pyenv-specific state files.
.python-version

# Ignore all "python-coverage"-specific output Python files.
*.py,cover

# Ignore all Python-specific byte-compiled, optimized, and DLL files.
*.py[cod]

# Ignore all Python-specific EGG packages.
*.egg

# Ignore all "python-coverage"-specific output Python files.
*.py,cover

# Ignore all SageMath-specific output Python files.
*.sage.py

# Ignore all C extensions.
*.so

# Ignore all temporary files.
*~
*.log
*.sw?

# Ignore all "trace"-specific output files.
*.cover
60 changes: 16 additions & 44 deletions beartype/_decor/decorcore.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@
)
from beartype._util.func.utilfuncmake import make_func
from beartype._util.func.utilfunctest import is_func_python
from beartype._util.mod.utilmodget import get_object_module_line_number_begin
from beartype._util.utilobject import get_object_name
from beartype._util.text.utiltextansi import strip_text_ansi
from beartype._util.text.utiltextlabel import label_object_context
from beartype._util.text.utiltextmunge import uppercase_char_first
from beartype._util.text.utiltextprefix import prefix_beartypeable
from traceback import format_exc
from warnings import warn

Expand Down Expand Up @@ -278,11 +280,6 @@ def beartype_object_nonfatal(
assert isinstance(warning_category, Warning), (
f'{repr(warning_category)} not warning category.')

# 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, stripped of *ALL* ANSI color. While colors improve the
# readability of exception messages that percolate down to an ANSI-aware
Expand All @@ -305,43 +302,18 @@ def beartype_object_nonfatal(
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
#label_object_context() function, please.
#FIXME: Refactor the existing prefix_beartypeable() function to
#internally call either label_callable() *OR* label_type() depending on
#the type of the passed object. Then call that function below like so:
# obj_label = prefix_beartypeable(
# obj=obj, is_context=True)
#
# obj_label_capitalized = uppercase_char_first(obj_label)

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

# Line number of the first line declaring this beartypeable in its
# underlying source code module file.
obj_lineno = get_object_module_line_number_begin(obj)

#FIXME: Replace our existing usage of the oddball prefix "@beartyped" in
#exception messages with this much more readable alternative, please.

# Human-readable string describing the type of this object as either...
obj_type = label_beartypeable_kind(obj)

# This string with the first character capitalized.
obj_type_capitalized = uppercase_char_first(obj_type)

# Warning message to be emitted.
warning_message = (
f'{obj_type_capitalized} {obj_name} at line number {obj_lineno}:\n'
# Indent this exception message by globally replacing *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 below.
error_message = error_message.replace('\n', '\n ')

# Warning message to be emitted, consisting of:
# * A human-readable label contextually describing this beartypeable,
# capitalized such that the first character is uppercase.
# * This indented exception message.
warning_message = uppercase_char_first(
f'{prefix_beartypeable(obj)}{label_object_context(obj)}:\n'
f'{error_message}'
)

Expand Down
2 changes: 1 addition & 1 deletion beartype/_util/text/utiltextlabel.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def label_object_context(obj: object) -> str:
obj_lineno = get_object_module_line_number_begin(obj)

# Return a string describing the context of this object.
return f'declared on line {obj_lineno} of file "{obj_filename}"'
return f'in file "{obj_filename}" line {obj_lineno}'
# Else, this object was defined in-memory. In this case, avoid attempting to
# needlessly contextualize this object.

Expand Down
14 changes: 11 additions & 3 deletions beartype/_util/text/utiltextprefix.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
from beartype._data.datakind import ARG_NAME_RETURN
from beartype._data.datatyping import BeartypeableT
from beartype._util.text.utiltextlabel import (
label_beartypeable_kind,
label_callable,
label_type,
)
from collections.abc import Callable

# ....................{ PREFIXERS ~ beartypeable }....................
#FIXME: Unit test this function with respect to classes, please.
def prefix_beartypeable(
obj: BeartypeableT, # pyright: ignore[reportInvalidTypeVarUse]
) -> str:
Expand All @@ -45,8 +46,15 @@ def prefix_beartypeable(
Human-readable label describing this beartypeable.
'''

# One-liner: "I choose you!"
return f'{label_callable(obj)} ' # type: ignore[arg-type]
# Return either...
return (
# If this beartypeable is a class, a label describing this class;
f'{label_type(obj)} '
if isinstance(obj, type) else
# Else, this beartypeable is a callable. In this case, a label
# describing this callable.
f'{label_callable(obj)} ' # type: ignore[arg-type]
)

# ....................{ PREFIXERS ~ beartypeable : pith }....................
def prefix_beartypeable_pith(func: Callable, pith_name: str) -> str:
Expand Down
14 changes: 6 additions & 8 deletions pytest
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
set -e

# ....................{ ARRAYS }....................
# Array of all arguments with which to invoke Python. Dismantled, this is:
# Array of all shell words with which to invoke Python. Dismantled, this is:
# * "-X dev", enabling the Python Development Mode (PDM). See also commentary
# for the ${PYTHONDEVMODE} shell variable in the "tox.ini" file.
PYTHON_ARGS=( command python3 -X dev )
Expand All @@ -36,7 +36,7 @@ PYTHON_ARGS=( command python3 -X dev )
# PYTHON_ARGS=( command python3.11 -X dev )
# PYTHON_ARGS=( command pypy3.7 -X dev )

# Array of all arguments to be passed to "python3" below.
# Array of all shell words to be passed to "python3" below.
PYTEST_ARGS=(
pytest

Expand All @@ -51,6 +51,7 @@ PYTEST_ARGS=(
# to our general-purpose "pytest.ini" configuration.
'--maxfail=1'

# Pass all remaining arguments to "pytest" as is.
"${@}"
)
# echo "pytest args: ${PYTEST_ARGS[*]}"
Expand Down Expand Up @@ -164,15 +165,12 @@ if is_package coverage && [[ ! " ${PYTEST_ARGS[*]} " =~ " -k " ]]; then
# See the following official documentation for further details, entitled
# "Initialization: determining rootdir and inifile":
# https://docs.pytest.org/en/latest/customize.html
"${PYTHON_ARGS[@]}" -m \
coverage run -m "${PYTEST_ARGS[@]}" . &&
"${PYTHON_ARGS[@]}" -m \
coverage report
"${PYTHON_ARGS[@]}" -m coverage run -m "${PYTEST_ARGS[@]}" . &&
"${PYTHON_ARGS[@]}" -m coverage report
# Else, run this project's pytest-based test suite with all passed arguments
# *WITHOUT* measuring coverage.
else
"${PYTHON_ARGS[@]}" -m \
"${PYTEST_ARGS[@]}" .
"${PYTHON_ARGS[@]}" -m "${PYTEST_ARGS[@]}" .
fi

# 0-based exit code reported by the prior command.
Expand Down

0 comments on commit d083dc3

Please sign in to comment.