question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Bug] Type-checking violations for lambda-style functional validators defined in IPython REPL omit source code

See original GitHub issue

I really like the idea of functional validators. They make the source code much more robust while keeping it lean and clean. My only concern is that when the bear roars, the user has no idea what she/he did wrong. Probably, I am using beartype the wrong way.

Using a simple type the user gets an kind of helpful warning, e.g.,

>>> import numpy as np
>>> from beartype import beartype
>>> from beartype.vale import Is
>>> from numpy.typing import NDArray
>>> from typing_extensions import Annotated
>>> @beartype
... def func(a: int) -> int:
...     return a
... 
>>> 
>>> func(5)
5
>>> func(5.)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<@beartype(__main__.func) at 0x7f6f2cb03940>", line 21, in func
  File "/home/user/anaconda3/envs/py3.8/lib/python3.8/site-packages/beartype/_decor/_error/errormain.py", line 301, in raise_pep_call_exception
    raise exception_cls(  # type: ignore[misc]
beartype.roar.BeartypeCallHintParamViolation: @beartyped func() parameter a=5.0 violates type hint <class 'int'>, as 5.0 not instance of int.

so it is clear, that an integer was expected. In this case it is trivial, but probably it would be helpful to also print the type of the input variable. So that it becomes clearer. Using a validator these error are getting very cryptic. Lets define three super handy validators.

>>> Float2DArray = Annotated[
...     NDArray[np.floating],
...     Is[lambda arr: arr.ndim == 2],
... ]
>>> FloatMatrix = Annotated[
...     Float2DArray,
...     Is[lambda arr: arr.shape[0] == arr.shape[1]],
... ]
>>> SymmetricFloatMatrix = Annotated[
...     FloatMatrix,
...     Is[lambda arr: np.allclose(arr, arr.T)],
... ]
>>> 
>>> @beartype
... def func(arr: SymmetricFloatMatrix) -> SymmetricFloatMatrix:
...     return arr
...
>>> 
>>> # this runs fine
>>> mat = np.zeros((4, 4))
>>> func(mat)
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])

So far, I am happy. But now parsing an array of wrong shape,

>>> # not 2D
>>> mat = np.zeros((4, ))
>>> func(mat)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<@beartype(__main__.func) at 0x7f6f2f37e820>", line 53, in func
  File "/home/user/anaconda3/envs/py3.8/lib/python3.8/site-packages/beartype/_decor/_error/errormain.py", line 301, in raise_pep_call_exception
    raise exception_cls(  # type: ignore[misc]
beartype.roar.BeartypeCallHintParamViolation: @beartyped func() parameter arr="array([0., 0., 0., 0.])" violates type hint typing_extensions.Annotated[numpy.ndarray, Is[<lambda>], Is[<lambda>], Is[<lambda>]], as "array([0., 0., 0., 0.])" violates validator Is[<lambda>]:
    False == Is[<lambda>].

a non quadratic array (matrix), or

>>> # not quadratic
>>> mat = np.zeros((4, 5))
>>> func(mat)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<@beartype(__main__.func) at 0x7f6f2f37e820>", line 53, in func
  File "/home/user/anaconda3/envs/py3.8/lib/python3.8/site-packages/beartype/_decor/_error/errormain.py", line 301, in raise_pep_call_exception
    raise exception_cls(  # type: ignore[misc]
beartype.roar.BeartypeCallHintParamViolation: @beartyped func() parameter arr="array([[0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
    ...[ violates type hint typing_extensions.Annotated[numpy.ndarray, Is[<lambda>], Is[<lambda>], Is[<lambda>]], as "array([[0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
    ...[ violates validator Is[<lambda>]:
    False == Is[<lambda>].

a non symmetric matrix,

>>> # not symmetric
>>> mat = np.zeros((4, 4))
>>> mat[0, 1] = 2
>>> func(mat)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<@beartype(__main__.func) at 0x7f6f2f37e820>", line 53, in func
  File "/home/user/anaconda3/envs/py3.8/lib/python3.8/site-packages/beartype/_decor/_error/errormain.py", line 301, in raise_pep_call_exception
    raise exception_cls(  # type: ignore[misc]
beartype.roar.BeartypeCallHintParamViolation: @beartyped func() parameter arr="array([[0., 2., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0... violates type hint typing_extensions.Annotated[numpy.ndarray, Is[<lambda>], Is[<lambda>], Is[<lambda>]], as "array([[0., 2., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0... violates validator Is[<lambda>]:
    False == Is[<lambda>].

In each case I am getting errors that some Is[<lambda>] are not fulfiled. If I remember correctly, older versions of beartype did print the lambda functions code. Using a wrong type, the error is more helpful, e.g.,

>>> func(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<@beartype(__main__.func) at 0x7f6f2f37e820>", line 53, in func
  File "/home/user/anaconda3/envs/py3.8/lib/python3.8/site-packages/beartype/_decor/_error/errormain.py", line 301, in raise_pep_call_exception
    raise exception_cls(  # type: ignore[misc]
beartype.roar.BeartypeCallHintParamViolation: @beartyped func() parameter arr=5 violates type hint typing_extensions.Annotated[numpy.ndarray, Is[<lambda>], Is[<lambda>], Is[<lambda>]], as 5 not instance of <protocol "numpy.ndarray">.

I would love to see a list with requirements that the variable needs to fulfill. So something like:

beartype.roar.BeartypeCallHintParamViolation: @beartyped func() parameter arr="array([[0., 2., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0..
violates type hint SymmetricFloatMatrix:
       - numpy.typing.NDArray[numpy.floating]
       - lambda arr: arr.ndim == 2
       - lambda arr: arr.shape[0] == arr.shape[1]
       - lambda arr: np.allclose(arr, arr.T)

So back to the original question. Is is possible to improve readability of the errors?

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:13 (7 by maintainers)

github_iconTop GitHub Comments

2reactions
dycwcommented, Apr 21, 2022

My $0.02 here: out of habit, I have my all validators defined as functions, e.g.

def is_array_2d(arr):
    return arr.ndim == 2

so failures can be immediately recognized by the function name.

Note that beartype.vale is pretty powerful, and some of the things you’d want to check can be implemented using native primitives. For example,

NDim2 = IsAttr['ndim', IsEqual[2]]

works, or if you want to get fancy:

class _NDimMeta(type):
    def __getitem__(cls, n):
        return IsAttr['ndim', IsEqual[n]]

class NDim(metaclass=_NDimMeta):
    pass

NDim[1], NDim[2], ...

would all work too. HTH!

1reaction
leyceccommented, Apr 22, 2022

Not sure why you’re using a metaclass there actually.

Because… it is hot.

Seriously, though. Good catch – as always! KISS or GTFO. But what is this is shadow madness:

@object.__new__

furious Googling intensifies

EDIT: zomg. I am lounging in stunned disbelief. @object.__new__ is actually a well-known crazy idiom for implicitly instantiating an object of the decorated type with the same name as that type, which then shadows that type in that scope, which the pyright static type-checker insanely recognizes as valid at static analysis time: e.g.,

# This wacky-tabacky idiom...
@object.__new__
class MuhObjectTypeCombo:
    ...

# ...is equivalent to this equally smelly code.
class MuhObjectTypeCombo:
    ...

MuhObjectTypeCombo = MuhObjectTypeCombo()

Both of these exude the suspicious odour of “wtf, bro” – but @object.__new__ really takes the cake and then splatters it all over the vinyl upholstery while recapitulating Bon Jovi’s trash-tier “It’s my life!”.

Relatedly, if we’re maximizing terseness for all the lols:

class NDim:
    def __class_getitem__(self, n):  # <-- PEP 560, yo
        return IsAttr['ndim', IsEqual[n]]

💪

Read more comments on GitHub >

github_iconTop Results From Across the Web

Recognise the ``@object.__new__`` decorator on a class ...
[Bug] Type-checking violations for lambda-style functional validators defined in IPython REPL omit source code beartype/beartype#123.
Read more >
Schema Validation - python-jsonschema - Read the Docs
The Basics: The simplest way to validate an instance under a given schema is to use the validate function. The Validator Protocol: jsonschema...
Read more >
python - Notebook Validation Failed | Jupyter - Stack Overflow
So somehow your notebook was partially updated to have id tags, but not the nbformat listed in the notebook's metadata. This has also...
Read more >
How to Validate Your Data with Custom Validators of Pydantic ...
To create a custom validator, we need to import the validator function which works like a decorator and decorates some methods of the...
Read more >
Messaging in IPython — IPython 3.2.1 documentation
Shell: this single ROUTER socket allows multiple incoming connections from frontends, and this is the socket where requests for code execution, object ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found