Is there a workaround for preventing `pytest` from dumping the source of `raise_pep_call_exception()` to stdout for every `@beartype`-caused test failure?
See original GitHub issueConsider this source file:
from beartype import beartype
@beartype
def fn() -> int:
return ""
def test_fn():
fn()
If you save this and call it angry.py
, and then run pytest angry.py
, you should see something like this:
============================= test session starts ==============================
platform linux -- Python 3.9.11, pytest-7.1.1, pluggy-1.0.0
rootdir: /home/user
plugins: mock-3.7.0
collected 1 item
angry.py F [100%]
=================================== FAILURES ===================================
___________________________________ test_fn ____________________________________
def test_fn():
> fn()
angry.py:8:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
<@beartype(angry.fn) at 0x7f488f91a1f0>:17: in fn
???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
func = <function fn at 0x7f488f91a1f0>, pith_name = 'return', pith_value = ''
random_int = None
def raise_pep_call_exception(
# Mandatory parameters.
func: Callable,
pith_name: str,
pith_value: object,
# Optional parameters.
random_int: Optional[int] = None,
) -> None:
'''
Raise a human-readable exception detailing the failure of the parameter
with the passed name *or* return value if this name is the magic string
``return`` of the passed decorated function fails to satisfy the
PEP-compliant type hint annotated on this parameter or return value.
Design
----------
The :mod:`beartype` package actually implements two parallel PEP-compliant
runtime type-checkers, each complementing the other by providing
functionality unsuited for the other. These are:
* The :mod:`beartype._decor._code._pep` submodule, dynamically generating
optimized PEP-compliant runtime type-checking code embedded in the body
of the wrapper function wrapping the decorated callable. For both
efficiency and maintainability, that code only tests whether or not a
parameter passed to that callable or value returned from that callable
satisfies a PEP-compliant annotation on that callable; that code does
*not* raise human-readable exceptions in the event that value fails to
satisfy that annotation. Instead, that code defers to...
* This function, performing unoptimized PEP-compliant runtime type-checking
generically applicable to all wrapper functions. The aforementioned
code calls this function only in the event that value fails to satisfy
that annotation, in which case this function then raises a human-readable
exception after discovering the underlying cause of this type failure by
recursively traversing that value and annotation. While efficiency is the
foremost focus of this package, efficiency is irrelevant during exception
handling -- which typically only occurs under infrequent edge cases.
Likewise, while raising this exception *would* technically be feasible
from the aforementioned code, doing so proved sufficiently non-trivial,
fragile, and ultimately unmaintainable to warrant offloading to this
function universally callable from all wrapper functions.
Parameters
----------
func : CallableTypes
Decorated callable to raise this exception from.
pith_name : str
Either:
* If the object failing to satisfy this hint is a passed parameter, the
name of this parameter.
* Else, the magic string ``return`` implying this object to be the
value returned from this callable.
pith_value : object
Passed parameter or returned value failing to satisfy this hint.
random_int: Optional[int]
**Pseudo-random integer** (i.e., unsigned 32-bit integer
pseudo-randomly generated by the parent :func:`beartype.beartype`
wrapper function in type-checking randomly indexed container items by
the current call to that function) if that function generated such an
integer *or* ``None`` otherwise (i.e., if that function generated *no*
such integer). Note that this parameter critically governs whether this
exception handler runs in constant or linear time. Specifically, if
this parameter is:
* An integer, this handler runs in **constant time.** Since there
exists a one-to-one relation between this integer and the random
container item(s) type-checked by the parent
:func:`beartype.beartype` wrapper function, receiving this integer
enables this handler to efficiently re-type-check the same random
container item(s) type-checked by the parent in constant time rather
type-checking all container items in linear time.
* ``None``, this handler runs in **linear time.**
Defaults to ``None``, implying this exception handler runs in linear
time by default.
Raises
----------
BeartypeCallHintParamViolation
If the object failing to satisfy this hint is a parameter.
BeartypeCallHintReturnViolation
If the object failing to satisfy this hint is a return value.
BeartypeDecorHintPepException
If the type hint annotating this object is *not* PEP-compliant.
_BeartypeCallHintPepRaiseException
If the parameter or return value with the passed name is unannotated.
_BeartypeCallHintPepRaiseDesynchronizationException
If this pith actually satisfies this hint, implying either:
* The parent wrapper function generated by the :mod:`beartype.beartype`
decorator type-checking this pith triggered a false negative by
erroneously misdetecting this pith as failing this type check.
* This child helper function re-type-checking this pith triggered a
false positive by erroneously misdetecting this pith as satisfying
this type check when in fact this pith fails to do so.
'''
assert callable(func), f'{repr(func)} uncallable.'
assert isinstance(pith_name, str), f'{repr(pith_name)} not string.'
assert isinstance(random_int, NoneTypeOr[int]), (
f'{repr(random_int)} neither integer nor "None".')
# print('''raise_pep_call_exception(
# func={!r},
# pith_name={!r},
# pith_value={!r}',
# )'''.format(func, pith_name, pith_value))
# Type of exception to be raised.
exception_cls: TypeException = None # type: ignore[assignment]
# Human-readable label describing this parameter or return value.
exception_prefix: str = None # type: ignore[assignment]
# If the name of this parameter is the magic string implying the passed
# object to be a return value, set the above local variables appropriately.
if pith_name == 'return':
exception_cls = BeartypeCallHintReturnViolation
exception_prefix = prefix_callable_decorated_return_value(
func=func, return_value=pith_value)
# Else, the passed object is a parameter. In this case, set the above local
# variables appropriately.
else:
exception_cls = BeartypeCallHintParamViolation
exception_prefix = prefix_callable_decorated_arg_value(
func=func,
arg_name=pith_name,
arg_value=pith_value,
)
# If this parameter or return value is unannotated, raise an exception.
#
# Note that this should *NEVER* occur, as the caller guarantees this
# parameter or return value to be annotated. Nonetheless, since callers
# could deface the "__annotations__" dunder dictionary without our
# knowledge or permission, precautions are warranted.
if pith_name not in func.__annotations__:
raise _BeartypeCallHintPepRaiseException(
f'{exception_prefix}unannotated.')
# Else, this parameter or return value is annotated.
# PEP-compliant type hint annotating this parameter or return value.
hint = func.__annotations__[pith_name]
# If this is *NOT* the PEP 484-compliant "typing.NoReturn" type hint
# permitted *ONLY* as a return annotation, this is a standard type hint
# generally supported by both parameters and return values. In this case...
if hint is not NoReturn:
# If type hint is *NOT* a supported type hint, raise an exception.
die_unless_hint(hint=hint, exception_prefix=exception_prefix)
# Else, this type hint is supported.
# Human-readable string describing the failure of this pith to satisfy this
# hint if this pith fails to satisfy this hint *OR* "None" otherwise (i.e.,
# if this pith satisfies this hint).
exception_cause = CauseSleuth(
func=func,
pith=pith_value,
hint=hint,
cause_indent='',
exception_prefix=exception_prefix,
random_int=random_int,
).get_cause_or_none()
# If this pith does *NOT* satisfy this hint...
if exception_cause:
# This failure suffixed by a period if *NOT* yet suffixed by a period.
exception_cause_suffixed = suffix_unless_suffixed(
text=exception_cause, suffix='.')
# Raise an exception of the desired class embedding this cause.
> raise exception_cls( # type: ignore[misc]
f'{exception_prefix}violates type hint {repr(hint)}, as '
f'{exception_cause_suffixed}'
)
E beartype.roar.BeartypeCallHintReturnViolation: @beartyped fn() return '' violates type hint <class 'int'>, as '' not instance of int.
conda/envs/anki/lib/python3.9/site-packages/beartype/_decor/_error/errormain.py:301: BeartypeCallHintReturnViolation
=========================== short test summary info ============================
FAILED angry.py::test_fn - beartype.roar.BeartypeCallHintReturnViolation: @be...
============================== 1 failed in 0.04s ===============================
Is there any way of suppressing the bit where it dumps this rather tall function to stdout/stderr? What I’d like to see is maybe an opt-in setting or something where you can make it look like this:
============================= test session starts ==============================
platform linux -- Python 3.9.11, pytest-7.1.1, pluggy-1.0.0
rootdir: /home/user
plugins: mock-3.7.0
collected 1 item
angry.py F [100%]
=================================== FAILURES ===================================
___________________________________ test_fn ____________________________________
def test_fn():
> fn()
angry.py:8:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
<@beartype(angry.fn) at 0x7f488f91a1f0>:17: in fn
???
...
E beartype.roar.BeartypeCallHintReturnViolation: @beartyped fn() return '' violates type hint <class 'int'>, as '' not instance of int.
conda/envs/anki/lib/python3.9/site-packages/beartype/_decor/_error/errormain.py:301: BeartypeCallHintReturnViolation
=========================== short test summary info ============================
FAILED angry.py::test_fn - beartype.roar.BeartypeCallHintReturnViolation: @be...
============================== 1 failed in 0.04s ===============================
Occasionally I break something in a project and when I run the tests, I get about 40 of these errors and you know
40 * TALL == VERY_TALL
Perhaps this is not something that beartype should support, but if anyone has got an idea of whether or not this is even possible, please let me know!
Issue Analytics
- State:
- Created a year ago
- Reactions:2
- Comments:8 (5 by maintainers)
Top Results From Across the Web
How to disable pytest dumping out source code?
However, something when the error is raised from another library, it still dumps the function source code flood the output. Is it possible...
Read more >"--show-capture=no" option still capture teardown logs. #3816
Hello, I wanted to use --show-capture=no option to hide the captured log. Environment: platform linux -- Python 3.6.5, pytest-3.5.1, ...
Read more >Allow pytest to be run without of capture : PY-35973
Indeed, output is captured. To disable it, you may pass JB_DISABLE_BUFFERING env. variable. But in this case, test output may go to the...
Read more >pytest: Save full error output to file | by Alexander Hultnér
I recently had a problem with some rare occurring failures in tests, they happened at a rate of about 1 time per 5000...
Read more >Step 9 - Pytest tutorial: Testing Exceptions - YouTube
Write tests that ensure exceptions are raised when expected.00:00 - Start00:25 - Write a test that triggers an error and expects an ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
This works perfectly:
Source file:
Patch used, exactly as you described:
All tests pass,
mypy
andpyright
are happy. Nice work, dude. That was lightning fast!Note a possibly typo: the commit message references #140 but this is #142.