-
-
Notifications
You must be signed in to change notification settings - Fork 50
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
Comments
🎶 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...
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: |
BeartypeValeSubscriptionException: <__main__.Class object at 0x7f0c0783dc50> not pure-Python function.
beartype.vale.Is[...]
to accept __call__()
-style callable classes
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!*)
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 ╭─────────────────────────────── 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. │
│ ❱ 46 │ die_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). |
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!*)
Resolved by 75e64bc. Flex it, @beartype. Flex it proudly. 💪 🐻
...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 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! |
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 ╭─────────────────────────────── 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. │
│ ❱ 46 │ die_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... |
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 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 But... yeah. Perhaps @beartype could and/or should relax its safety concerns here. After all, the signature |
Welp! Solved by creating a subclass specifically for type checking, with the 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 |
🎶 Guess who's back, back again? @sylvorg's back, tell a friend! 🎶
Code:
Traceback:
Is this supposed to be happening?
Sorry for disturbing you so many times! I'll get out of your hair now! 😅
The text was updated successfully, but these errors were encountered: