Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Generalize beartype.vale.Is[...] to accept __call__()-style callable classes #360

Closed
sylvorg opened this issue Apr 6, 2024 · 6 comments

Comments

@sylvorg
Copy link

sylvorg commented Apr 6, 2024

🎶 Guess who's back, back again? @sylvorg's back, tell a friend! 🎶

Code:

from rich.traceback import install

install(show_locals=True)

from beartype.door import is_bearable
from beartype.vale import Is
from typing import Annotated


class Metaclass(type):
    pass


class Class(metaclass=Metaclass):
    def __call__(self):
        return True


is_bearable(0, Annotated[int, Is[Class()]])

Traceback:

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /mnt/wsl/sylvorg/sylvorg/sylveon/siluam/oreo/./test9.py:22 in <module>                           │
│                                                                                                  │
│   19 │   │   return True                                                                         │
│   20                                                                                             │
│   21                                                                                             │
│ ❱ 22 is_bearable(0, Annotated[int, Is[Class()]])                                                 │
│   23                                                                                             │
│                                                                                                  │
│ ╭───────────────────────────── locals ──────────────────────────────╮                            │
│ │   Annotated = <class 'typing.Annotated'>                          │                            │
│ │       Class = <class '__main__.Class'>                            │                            │
│ │     install = <function install at 0x7f0c07817ce0>                │                            │
│ │          Is = <beartype.vale._IsFactory object at 0x7f0c06f2b590> │                            │
│ │ is_bearable = <function is_bearable at 0x7f0c06f01800>            │                            │
│ │   Metaclass = <class '__main__.Metaclass'>                        │                            │
│ ╰───────────────────────────────────────────────────────────────────╯                            │
│                                                                                                  │
│ /nix/store/27gmfk9yy9pm56s1x4296vnl4d2klakx-python3-3.11.8-env/lib/python3.11/site-packages/bear │
│ type/vale/_is/_valeis.py:332 in __getitem__                                                      │
│                                                                                                  │
│   329 │   │   # Else, this class was subscripted by exactly one argument.                        │330 │   │                                                                                      │
│   331 │   │   # If that callable is *NOT* a validator tester, raise an exception.                │
│ ❱ 332 │   │   die_unless_validator_tester(is_valid)                                              │
│   333 │   │   # Else, that callable is a validator tester.                                       │334 │   │                                                                                      │
│   335 │   │   # Lambda function dynamically generating the machine-readable                      │
│                                                                                                  │
│ ╭──────────────────────────── locals ────────────────────────────╮                               │
│ │ is_valid = <__main__.Class object at 0x7f0c0783dc50>           │                               │
│ │     self = <beartype.vale._IsFactory object at 0x7f0c06f2b590> │                               │
│ ╰────────────────────────────────────────────────────────────────╯                               │
│                                                                                                  │
│ /nix/store/27gmfk9yy9pm56s1x4296vnl4d2klakx-python3-3.11.8-env/lib/python3.11/site-packages/bear │
│ type/vale/_util/_valeutilfunc.py:46 in die_unless_validator_tester                               │
│                                                                                                  │
│   43# If this validator is either uncallable, a C-based callable, *OR* a                    │44# pure-Python callable accepting more or less than one parameter, raise                 │45# an exception.                                                                         │
│ ❱ 46die_unless_func_args_len_flexible_equal(                                                │
│   47 │   │   func=validator_tester,                                                              │
│   48 │   │   func_args_len_flexible=1,                                                           │
│   49 │   │   exception_cls=BeartypeValeSubscriptionException,                                    │
│                                                                                                  │
│ ╭─────────────────────────── locals ───────────────────────────╮                                 │
│ │ validator_tester = <__main__.Class object at 0x7f0c0783dc50> │                                 │
│ ╰──────────────────────────────────────────────────────────────╯                                 │
│                                                                                                  │
│ /nix/store/27gmfk9yy9pm56s1x4296vnl4d2klakx-python3-3.11.8-env/lib/python3.11/site-packages/bear │
│ type/_util/func/arg/utilfuncargtest.py:93 in die_unless_func_args_len_flexible_equal             │
│                                                                                                  │
│    90 │   │   get_func_args_flexible_len)                                                        │
│    91 │                                                                                          │
│    92# Number of flexible parameters accepted by this callable.                             │
│ ❱  93func_args_len_flexible_actual = get_func_args_flexible_len(                            │
│    94 │   │   func=func,                                                                         │
│    95 │   │   is_unwrap=is_unwrap,                                                               │
│    96 │   │   exception_cls=exception_cls,                                                       │
│                                                                                                  │
│ ╭──────────────────────────────────────── locals ────────────────────────────────────────╮       │
│ │              exception_cls = <class 'beartype.roar.BeartypeValeSubscriptionException'> │       │
│ │           exception_prefix = ''                                                        │       │
│ │                       func = <__main__.Class object at 0x7f0c0783dc50>                 │       │
│ │     func_args_len_flexible = 1                                                         │       │
│ │ get_func_args_flexible_len = <function get_func_args_flexible_len at 0x7f0c06f83240>   │       │
│ │                  is_unwrap = True                                                      │       │
│ ╰────────────────────────────────────────────────────────────────────────────────────────╯       │
│                                                                                                  │
│ /nix/store/27gmfk9yy9pm56s1x4296vnl4d2klakx-python3-3.11.8-env/lib/python3.11/site-packages/bear │
│ type/_util/func/arg/utilfuncargget.py:127 in get_func_args_flexible_len                          │
│                                                                                                  │
│   124 │   '''                                                                                    │
│   125 │                                                                                          │
│   126 │   # Code object underlying the passed pure-Python callable unwrapped.                    │
│ ❱ 127 │   func_codeobj = get_func_codeobj(                                                       │
│   128 │   │   func=func,                                                                         │
│   129 │   │   is_unwrap=is_unwrap,                                                               │
│   130 │   │   exception_cls=exception_cls,                                                       │
│                                                                                                  │
│ ╭─────────────────────────────────── locals ───────────────────────────────────╮                 │
│ │    exception_cls = <class 'beartype.roar.BeartypeValeSubscriptionException'> │                 │
│ │ exception_prefix = ''                                                        │                 │
│ │             func = <__main__.Class object at 0x7f0c0783dc50>                 │                 │
│ │        is_unwrap = True                                                      │                 │
│ ╰──────────────────────────────────────────────────────────────────────────────╯                 │
│                                                                                                  │
│ /nix/store/27gmfk9yy9pm56s1x4296vnl4d2klakx-python3-3.11.8-env/lib/python3.11/site-packages/bear │
│ type/_util/func/utilfunccodeobj.py:123 in get_func_codeobj                                       │
│                                                                                                  │
│   120 │   │   from beartype._util.func.utilfunctest import die_unless_func_python                │
│   121 │   │                                                                                      │
│   122 │   │   # Raise an exception.                                                              │
│ ❱ 123 │   │   die_unless_func_python(                                                            │
│   124 │   │   │   func=func,                                                                     │
│   125 │   │   │   exception_cls=exception_cls,                                                   │
│   126 │   │   │   exception_prefix=exception_prefix,                                             │
│                                                                                                  │
│ ╭────────────────────────────────────── locals ──────────────────────────────────────╮           │
│ │ die_unless_func_python = <function die_unless_func_python at 0x7f0c06f82660>       │           │
│ │          exception_cls = <class 'beartype.roar.BeartypeValeSubscriptionException'> │           │
│ │       exception_prefix = ''                                                        │           │
│ │                   func = <__main__.Class object at 0x7f0c0783dc50>                 │           │
│ │           func_codeobj = None                                                      │           │
│ │              is_unwrap = True                                                      │           │
│ ╰────────────────────────────────────────────────────────────────────────────────────╯           │
│                                                                                                  │
│ /nix/store/27gmfk9yy9pm56s1x4296vnl4d2klakx-python3-3.11.8-env/lib/python3.11/site-packages/bear │
│ type/_util/func/utilfunctest.py:113 in die_unless_func_python                                    │
│                                                                                                  │
│   110 │   │   # Else, that callable is callable.                                                 │
│   111 │   │                                                                                      │
│   112 │   │   # Raise a human-readable exception.                                                │
│ ❱ 113 │   │   raise exception_cls(                                                               │
│   114 │   │   │   f'{exception_prefix}{repr(func)} not pure-Python function.')                   │
│   115 │   # Else, that callable is pure-Python.                                                  │
│   116                                                                                            │
│                                                                                                  │
│ ╭─────────────────────────────────── locals ───────────────────────────────────╮                 │
│ │    exception_cls = <class 'beartype.roar.BeartypeValeSubscriptionException'> │                 │
│ │ exception_prefix = ''                                                        │                 │
│ │             func = <__main__.Class object at 0x7f0c0783dc50>                 │                 │
│ ╰──────────────────────────────────────────────────────────────────────────────╯                 │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
BeartypeValeSubscriptionException: <__main__.Class object at 0x7f0c0783dc50> not pure-Python function.

Is this supposed to be happening?

Sorry for disturbing you so many times! I'll get out of your hair now! 😅

@leycec
Copy link
Member

leycec commented Apr 9, 2024

🎶 Walk up to the hot issues and I'm like "What's up, GitHubbers? My name's Shady @leycec!" 🎶

Yay. It's @sylvorg. My favourite student of the arcane computer arts is back with a brand new mission to break @beartype over their youthful knees – and I'm here for it. The answer to this question...

Is this supposed to be happening?

No! Never! @beartype should never raise exceptions that make no sense. The answer to your question is: "We are so sorry you did this rather than study. Also, we hope you rocked those finals. Of course you rocked those finals. You're you. Still, we hope."

On topic: beartype.value.Is[...] should accept all possible pure-Python callables, including __call__()-style classes. Never thought anyone would try that. But you're right. That's a valid use case. Let's resolve this for the good of @sylvorg's GPA.

@leycec leycec changed the title ... Sigh... BeartypeValeSubscriptionException: <__main__.Class object at 0x7f0c0783dc50> not pure-Python function. [Feature Request] Generalize beartype.vale.Is[...] to accept __call__()-style callable classes Apr 9, 2024
leycec added a commit that referenced this issue Apr 12, 2024
This commit is the first in a commit chain generalizing the
`beartype.vale.Is[...]` validator factory to accept **callable objects**
(i.e., high-level pure-Python objects whose classes define the
`__call__()` dunder method, rendering those objects callable), en-route
to resolving feature request #360 kindly submitted by "Computer Graphics
and Visualization" sufferer @sylvorg, who graciously sacrificed his
entire undergraduate GPA for the gradual betterment of @beartype. Your
journey of woe and hardship will *not* be forgotten, @sylvorg!
Specifically, @beartype now accepts class-based beartype validators
resembling:

```python
from beartype.door import is_bearable
from beartype.typing import Annotated
from beartype.vale import Is
from functools import partial

class TruthSeeker(object):
    def __call__(self, obj: object) -> bool:
        '''
        Tester method returning :data:`True` only if the passed object
        evaluates to :data:`True` when coerced into a boolean and whose
        first parameter is ignorable.
        '''

        return bool(obj)

# Beartype validator matching only objects that evaluate to "True".
Truthy = Annotated[object, Is[TruthSeeker()]]

assert is_bearable('', Truthy) is False
assert is_bearable('Even lies are true now, huh?', Truthy) is True
```

Is this valuable? I have no idea. Let's pretend I did something useful
tonight so that I can sleep without self-recrimination. (*Pungent puns, gentlemen!*)
@sylvorg
Copy link
Author

sylvorg commented Apr 12, 2024

Sorry, I will read and reply to all your replies in detail later, but for now, with the exact same test code above, and with commit 769c7cd2d037bdf0458f41828c6ff0ed17a7804e:

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /mnt/wsl/sylvorg/sylvorg/sylveon/siluam/oreo/./test9.py:22 in <module>                           │
│                                                                                                  │
│   19 │   │   return True                                                                         │
│   20                                                                                             │
│   21                                                                                             │
│ ❱ 22 is_bearable(0, Annotated[int, Is[Class()]])                                                 │
│   23                                                                                             │
│                                                                                                  │
│ ╭───────────────────────────── locals ──────────────────────────────╮                            │
│ │   Annotated = <class 'typing.Annotated'>                          │                            │
│ │       Class = <class '__main__.Class'>                            │                            │
│ │     install = <function install at 0x7f25aff63ce0>                │                            │
│ │          Is = <beartype.vale._IsFactory object at 0x7f25af457b10> │                            │
│ │ is_bearable = <function is_bearable at 0x7f25af65e200>            │                            │
│ │   Metaclass = <class '__main__.Metaclass'>                        │                            │
│ ╰───────────────────────────────────────────────────────────────────╯                            │
│                                                                                                  │
│ /nix/store/nn5d6in1ah7sizr2lswm5495jinxcwhs-python3-3.11.8-env/lib/python3.11/site-packages/bear │
│ type/vale/_is/_valeis.py:334 in __getitem__                                                      │
│                                                                                                  │
│   331 │   │   # Else, this class was subscripted by exactly one argument.                        │332 │   │                                                                                      │
│   333 │   │   # If that callable is *NOT* a validator tester, raise an exception.                │
│ ❱ 334 │   │   die_unless_validator_tester(is_valid)                                              │
│   335 │   │   # Else, that callable is a validator tester.                                       │336 │   │                                                                                      │
│   337 │   │   # Lambda function dynamically generating the machine-readable                      │
│                                                                                                  │
│ ╭──────────────────────────── locals ────────────────────────────╮                               │
│ │ is_valid = <__main__.Class object at 0x7f25aff89c10>           │                               │
│ │     self = <beartype.vale._IsFactory object at 0x7f25af457b10> │                               │
│ ╰────────────────────────────────────────────────────────────────╯                               │
│                                                                                                  │
│ /nix/store/nn5d6in1ah7sizr2lswm5495jinxcwhs-python3-3.11.8-env/lib/python3.11/site-packages/bear │
│ type/vale/_util/_valeutilfunc.py:46 in die_unless_validator_tester                               │
│                                                                                                  │
│   43# If this validator is either uncallable, a C-based callable, *OR* a                    │44# pure-Python callable accepting more or less than one parameter, raise                 │45# an exception.                                                                         │
│ ❱ 46die_unless_func_args_len_flexible_equal(                                                │
│   47 │   │   func=validator_tester,                                                              │
│   48 │   │   func_args_len_flexible=1,                                                           │
│   49 │   │   exception_cls=BeartypeValeSubscriptionException,                                    │
│                                                                                                  │
│ ╭─────────────────────────── locals ───────────────────────────╮                                 │
│ │ validator_tester = <__main__.Class object at 0x7f25aff89c10> │                                 │
│ ╰──────────────────────────────────────────────────────────────╯                                 │
│                                                                                                  │
│ /nix/store/nn5d6in1ah7sizr2lswm5495jinxcwhs-python3-3.11.8-env/lib/python3.11/site-packages/bear │
│ type/_util/func/arg/utilfuncargtest.py:108 in die_unless_func_args_len_flexible_equal            │
│                                                                                                  │
│   105 │   │   assert isinstance(exception_prefix, str), (                                        │
│   106 │   │   │   f'{repr(exception_prefix)} not string.')                                       │
│   107 │   │                                                                                      │
│ ❱ 108 │   │   raise exception_cls(                                                               │
│   109 │   │   │   f'{exception_prefix}callable {repr(func)} flexible argument count '            │
│   110 │   │   │   f'{func_args_len_flexible_actual} != {func_args_len_flexible} '                │
│   111 │   │   │   f'(i.e., {repr(func)} accepts {func_args_len_flexible_actual} '                │
│                                                                                                  │
│ ╭───────────────────────────────────────── locals ──────────────────────────────────────────╮    │
│ │                 exception_cls = <class 'beartype.roar.BeartypeValeSubscriptionException'> │    │
│ │              exception_prefix = ''                                                        │    │
│ │                          func = <__main__.Class object at 0x7f25aff89c10>                 │    │
│ │        func_args_len_flexible = 1                                                         │    │
│ │ func_args_len_flexible_actual = 0                                                         │    │
│ │    get_func_args_flexible_len = <function get_func_args_flexible_len at 0x7f25af6d7600>   │    │
│ │                     is_unwrap = True                                                      │    │
│ ╰───────────────────────────────────────────────────────────────────────────────────────────╯    │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
BeartypeValeSubscriptionException: Callable <__main__.Class object at 0x7f25aff89c10> flexible argument count 0 != 1 (i.e., <__main__.Class object at 0x7f25aff89c10>
accepts 0 rather than 1 positional and/or keyword parameters).

leycec added a commit that referenced this issue Apr 13, 2024
This commit is the last in a commit chain generalizing the
`beartype.vale.Is[...]` validator factory to accept **callable objects**
(i.e., high-level pure-Python objects whose classes define the
`__call__()` dunder method, rendering those objects callable), resolving
feature request #360 kindly submitted by "Computer Graphics and
Visualization" sufferer @sylvorg, who graciously sacrificed his entire
undergraduate GPA for the gradual betterment of @beartype. Your journey
of woe and hardship will *not* be forgotten, @sylvorg! Specifically,
@beartype now accepts class-based beartype validators resembling:

```python
from beartype.door import is_bearable
from beartype.typing import Annotated
from beartype.vale import Is
from functools import partial

class TruthSeeker(object):
    def __call__(self, obj: object) -> bool:
        '''
        Tester method returning :data:`True` only if the passed object
        evaluates to :data:`True` when coerced into a boolean and whose
        first parameter is ignorable.
        '''

        return bool(obj)

# Beartype validator matching only objects that evaluate to "True".
Truthy = Annotated[object, Is[TruthSeeker()]]

assert is_bearable('', Truthy) is False
assert is_bearable('Even lies are true now, huh?', Truthy) is True
```

Is this valuable? I have no idea. Let's pretend I did something useful
tonight so that I can sleep without self-recrimination. (*Painful rain full of insanely manly manes!*)
@leycec
Copy link
Member

leycec commented Apr 13, 2024

Resolved by 75e64bc. Flex it, @beartype. Flex it proudly. 💪 🐻

Sorry, I will read and reply to all your replies in detail later, but for now, with the exact same test code above, and with commit 769c7cd:

...heh. Shouldn't you be enjoying your newfound freedom from bothersome university by binge-reading Nisio Isin's seminal light novel masterpiece Bakemonogatari rather than corrupting my issue tracker with your justifiable concerns? Thankfully, in this case, @beartype is actually right for once. I too remain stunned. 😨

You are now thinking, "The hell @leycec say!?" Yes. It's all true. Beartype validators are required to accept the object currently being type-checked. Otherwise, they're not actually validating anything – right? If you refactor your above example to instead pass an additional parameter to your Class.__call__() method, you'll find that everything now behaves as @leycec once promised upon a falling apocalyptic meteoroid the size of Jupiter:

from beartype.door import is_bearable
from beartype.vale import Is
from typing import Annotated

class Metaclass(type):
    pass

class Class(metaclass=Metaclass):
    def __call__(self, obj: object) -> bool:
        return True  # <-- not a great validator, but you do you

assert is_bearable(0, Annotated[int, Is[Class()]]) is True

Please have an awesome Spring break. You've earned your pay here. Give this student a high GPA now, Universe!

@leycec leycec closed this as completed Apr 13, 2024
@sylvorg
Copy link
Author

sylvorg commented Apr 13, 2024

Beartype validators are required to accept the object currently being type-checked.

Ah, yes; that bit was entirely on me. My bad. 😅 However!

from rich.traceback import install

install(show_locals=True)

from beartype.door import is_bearable
from beartype.vale import Is
from typing import Annotated


class Metaclass(type):
    pass


class Class(metaclass=Metaclass):
    def __call__(self, *args, **kwargs):
        return True


is_bearable(0, Annotated[int, Is[Class()]])

This is my real problem; my custom Pipe class needs to take an arbitrary number of arguments or keyword arguments and pass them on to a composite function. When using *args and **kwargs, I'm getting the following error:

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /mnt/wsl/sylvorg/sylvorg/sylveon/siluam/oreo/./test9.py:22 in <module>                           │
│                                                                                                  │
│   19 │   │   return True                                                                         │
│   20                                                                                             │
│   21                                                                                             │
│ ❱ 22 is_bearable(0, Annotated[int, Is[Class()]])                                                 │
│   23                                                                                             │
│                                                                                                  │
│ ╭───────────────────────────── locals ──────────────────────────────╮                            │
│ │   Annotated = <class 'typing.Annotated'>                          │                            │
│ │       Class = <class '__main__.Class'>                            │                            │
│ │     install = <function install at 0x7f9c2b343ce0>                │                            │
│ │          Is = <beartype.vale._IsFactory object at 0x7f9c2aa53e10> │                            │
│ │ is_bearable = <function is_bearable at 0x7f9c2aa362a0>            │                            │
│ │   Metaclass = <class '__main__.Metaclass'>                        │                            │
│ ╰───────────────────────────────────────────────────────────────────╯                            │
│                                                                                                  │
│ /nix/store/6fwf54ixhqylkszncw3yz3kgg630r2lv-python3-3.11.8-env/lib/python3.11/site-packages/bear │
│ type/vale/_is/_valeis.py:334 in __getitem__                                                      │
│                                                                                                  │
│   331 │   │   # Else, this class was subscripted by exactly one argument.                        │332 │   │                                                                                      │
│   333 │   │   # If that callable is *NOT* a validator tester, raise an exception.                │
│ ❱ 334 │   │   die_unless_validator_tester(is_valid)                                              │
│   335 │   │   # Else, that callable is a validator tester.                                       │336 │   │                                                                                      │
│   337 │   │   # Lambda function dynamically generating the machine-readable                      │
│                                                                                                  │
│ ╭──────────────────────────── locals ────────────────────────────╮                               │
│ │ is_valid = <__main__.Class object at 0x7f9c2b368210>           │                               │
│ │     self = <beartype.vale._IsFactory object at 0x7f9c2aa53e10> │                               │
│ ╰────────────────────────────────────────────────────────────────╯                               │
│                                                                                                  │
│ /nix/store/6fwf54ixhqylkszncw3yz3kgg630r2lv-python3-3.11.8-env/lib/python3.11/site-packages/bear │
│ type/vale/_util/_valeutilfunc.py:46 in die_unless_validator_tester                               │
│                                                                                                  │
│   43# If this validator is either uncallable, a C-based callable, *OR* a                    │44# pure-Python callable accepting more or less than one parameter, raise                 │45# an exception.                                                                         │
│ ❱ 46die_unless_func_args_len_flexible_equal(                                                │
│   47 │   │   func=validator_tester,                                                              │
│   48 │   │   func_args_len_flexible=1,                                                           │
│   49 │   │   exception_cls=BeartypeValeSubscriptionException,                                    │
│                                                                                                  │
│ ╭─────────────────────────── locals ───────────────────────────╮                                 │
│ │ validator_tester = <__main__.Class object at 0x7f9c2b368210> │                                 │
│ ╰──────────────────────────────────────────────────────────────╯                                 │
│                                                                                                  │
│ /nix/store/6fwf54ixhqylkszncw3yz3kgg630r2lv-python3-3.11.8-env/lib/python3.11/site-packages/bear │
│ type/_util/func/arg/utilfuncargtest.py:108 in die_unless_func_args_len_flexible_equal            │
│                                                                                                  │
│   105 │   │   assert isinstance(exception_prefix, str), (                                        │
│   106 │   │   │   f'{repr(exception_prefix)} not string.')                                       │
│   107 │   │                                                                                      │
│ ❱ 108 │   │   raise exception_cls(                                                               │
│   109 │   │   │   f'{exception_prefix}callable {repr(func)} flexible argument count '            │
│   110 │   │   │   f'{func_args_len_flexible_actual} != {func_args_len_flexible} '                │
│   111 │   │   │   f'(i.e., {repr(func)} accepts {func_args_len_flexible_actual} '                │
│                                                                                                  │
│ ╭───────────────────────────────────────── locals ──────────────────────────────────────────╮    │
│ │                 exception_cls = <class 'beartype.roar.BeartypeValeSubscriptionException'> │    │
│ │              exception_prefix = ''                                                        │    │
│ │                          func = <__main__.Class object at 0x7f9c2b368210>                 │    │
│ │        func_args_len_flexible = 1                                                         │    │
│ │ func_args_len_flexible_actual = 0                                                         │    │
│ │    get_func_args_flexible_len = <function get_func_args_flexible_len at 0x7f9c2aaaf600>   │    │
│ │                     is_unwrap = True                                                      │    │
│ ╰───────────────────────────────────────────────────────────────────────────────────────────╯    │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
BeartypeValeSubscriptionException: Callable <__main__.Class object at 0x7f9c2b368210> flexible argument count 0 != 1 (i.e., <__main__.Class object at 0x7f9c2b368210>
accepts 0 rather than 1 positional and/or keyword parameters).

Again, I'm really, really sorry for bothering you so many times, and not accounting for most of what you're saying at the moment, but, as you noted, I'm really not good at Graphics, especially without a cheat sheet.

Sigh... Lovely professor, bit strict with external help...

@leycec
Copy link
Member

leycec commented Apr 13, 2024

This is my real problem; my custom Pipe class needs to take an arbitrary number of arguments or keyword arguments and pass them on to a composite function.

Oh, ho, ho. The truth comes out at last, huh? Indeed. It is a conundrum. @beartype really wants to ensure that your validator will accept exactly one parameter. After all, @beartype only passes your validator exactly one parameter – right? The signature *args, **kwargs accepts literally everything and thus fails to satisfy @beartype's safety concerns. @beartype and Archen are thus both sad. Would it be feasible for your __call__() method to additionally accept a positional obj argument? Like this:

from beartype.door import is_bearable
from beartype.vale import Is
from typing import Annotated


class Metaclass(type):
    pass


class Class(metaclass=Metaclass):
    def __call__(self, obj, *args, **kwargs):  # <-- "obj". "obj" is what Archen is sayin'.
        return True


is_bearable(0, Annotated[int, Is[Class()]])  # <-- totally works. but will "Pipe" ever be satisfied!?

@beartype wants to cheerlead you, but your Pipe dreams ...heh challenge @beartype's will to type. 😆 😭

But... yeah. Perhaps @beartype could and/or should relax its safety concerns here. After all, the signature *args, **kwargs does technically accept a single parameter – and literally anything else too. But maybe that's "fine"? Let's contemplate this even as I probably do very little to address any of this.

@sylvorg
Copy link
Author

sylvorg commented Apr 27, 2024

Welp! Solved by creating a subclass specifically for type checking, with the __call__ method exactly as you described! It's good enough for now, considering I may never use the damn thing! 😹

The thing is, if the function(s) held in the pipe accept a variable number of arguments, they may already have something to deal with a lack thereof, thereby ensuring there's always a boolean value at the end, somewhat like how all with an empty iterable returns true by default, but checks non-empty iterables and returns the appropriate value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants