[Feature Request] Generalize `beartype.door.is_subhint()` to accept type tuples... just 'cause
... Two more day 'til you get a deluge of replies!
Would it be possible to use is_subhint like issubclass, with a tuple of types? For example:
issubclass(str, (int, str)) # True
is_subhint(str, (int, str)) # ...
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ in <module>:1 │
│ ╭───────────────────────────── locals ─────────────────────────────╮ │
│ │ BaseModel = <class 'pydantic.main.BaseModel'> │ │
│ │ Generic = <class 'typing.Generic'> │ │
│ │ getfullargspec = <function getfullargspec at 0x000001880578F380> │ │
│ │ install = <function install at 0x0000018806D27A60> │ │
│ │ is_bearable = <function is_bearable at 0x0000018806D0AC00> │ │
│ │ is_subhint = <function is_subhint at 0x0000018806D0AB60> │ │
│ │ Literal = typing.Literal │ │
│ │ Test = <class '__main__.Test'> │ │
│ │ test = <function test at 0x0000018806D24A40> │ │
│ │ TestModel = <class '__main__.TestModel'> │ │
│ │ Union = typing.Union │ │
│ ╰──────────────────────────────────────────────────────────────────╯ │
│ │
│ C:\Users\jeetr\anaconda3\Lib\site-packages\beartype\door\_doorcheck.py:168 in is_subhint │
│ │
│ 165 │ from beartype.door._cls.doorsuper import TypeHint │
│ 166 │ │
│ 167 │ # The one-liner is mightier than the... many-liner. │
│ ❱ 168 │ return TypeHint(subhint).is_subhint(TypeHint(superhint)) │
│ 169 │
│ 170 # ....................{ TESTERS ~ is_bearable }.................... │
│ 171 def is_bearable( │
│ │
│ ╭────────────────────────── locals ───────────────────────────╮ │
│ │ subhint = <class 'str'> │ │
│ │ superhint = (<class 'int'>, <class 'str'>) │ │
│ │ TypeHint = <class 'beartype.door._cls.doorsuper.TypeHint'> │ │
│ ╰─────────────────────────────────────────────────────────────╯ │
│ │
│ C:\Users\jeetr\anaconda3\Lib\site-packages\beartype\door\_cls\doormeta.py:146 in __call__ │
│ │
│ 143 │ │ # each hint that evaluates to the same key is wrapped by the same │
│ 144 │ │ # instance of the "TypeHint" class under this Python interpreter. │
│ 145 │ │ wrapper = ( │
│ ❱ 146 │ │ │ _HINT_KEY_TO_WRAPPER.cache_or_get_cached_func_return_passed_arg( │
│ 147 │ │ │ │ # Cache this wrapper singleton under this key. │
│ 148 │ │ │ │ key=hint_key, │
│ 149 │ │ │ │ # If a wrapper singleton has yet to be instantiated for this │
│ │
│ ╭────────────────────────── locals ──────────────────────────╮ │
│ │ cls = <class 'beartype.door._cls.doorsuper.TypeHint'> │ │
│ │ hint = (<class 'int'>, <class 'str'>) │ │
│ │ hint_key = 1683745922240 │ │
│ │ TypeHint = <class 'beartype.door._cls.doorsuper.TypeHint'> │ │
│ ╰────────────────────────────────────────────────────────────╯ │
│ │
│ C:\Users\jeetr\anaconda3\Lib\site-packages\beartype\_util\cache\map\utilmapbig.py:231 in │
│ cache_or_get_cached_func_return_passed_arg │
│ │
│ 228 │ │ │ │
│ 229 │ │ │ # Value created by this factory function, localized for negligible │
│ 230 │ │ │ # efficiency to avoid the unnecessary subsequent dictionary lookup. │
│ ❱ 231 │ │ │ value = value_factory(arg) │
│ 232 │ │ │ │
│ 233 │ │ │ # Cache this key with this value. │
│ 234 │ │ │ self._key_to_value_set(key, value) │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ _SENTINEL = <beartype._util.utilobject.Iota object at 0x0000018806B6B480> │ │
│ │ arg = (<class 'int'>, <class 'str'>) │ │
│ │ key = 1683745922240 │ │
│ │ self = <beartype._util.cache.map.utilmapbig.CacheUnboundedStrong object at │ │
│ │ 0x0000018806D13EC0> │ │
│ │ value_factory = <bound method _TypeHintMeta._make_wrapper of <class │ │
│ │ 'beartype.door._cls.doorsuper.TypeHint'>> │ │
│ │ value_old = <beartype._util.utilobject.Iota object at 0x0000018806B6B480> │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ C:\Users\jeetr\anaconda3\Lib\site-packages\beartype\door\_cls\doormeta.py:218 in _make_wrapper │
│ │
│ 215 │ │ # Concrete "TypeHint" subclass handling this hint if this hint is │
│ 216 │ │ # supported by an existing "TypeHint" subclass *OR* raise an exception │
│ 217 │ │ # otherwise (i.e., if this hint is currently unsupported). │
│ ❱ 218 │ │ wrapper_subclass = get_typehint_subclass(hint) │
│ 219 │ │ # print(f'!!!!!!!!!!!!! [ in {repr(cls)}.__new__() ] !!!!!!!!!!!!!!!') │
│ 220 │ │ │
│ 221 │ │ # Type hint wrapper wrapping this hint as a new singleton instance of │
│ │
│ ╭──────────────────────────────────── locals ────────────────────────────────────╮ │
│ │ cls = <class 'beartype.door._cls.doorsuper.TypeHint'> │ │
│ │ get_typehint_subclass = <function get_typehint_subclass at 0x0000018806D271A0> │ │
│ │ hint = (<class 'int'>, <class 'str'>) │ │
│ ╰────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ C:\Users\jeetr\anaconda3\Lib\site-packages\beartype\door\_doordata.py:109 in │
│ get_typehint_subclass │
│ │
│ 106 │ │ │ wrapper_subclass = ClassTypeHint │
│ 107 │ │ # Else, raise an exception. │
│ 108 │ │ else: │
│ ❱ 109 │ │ │ raise BeartypeDoorNonpepException( │
│ 110 │ │ │ │ f'Type hint {repr(hint)} ' │
│ 111 │ │ │ │ f'currently unsupported by "beartype.door.TypeHint".' │
│ 112 │ │ │ ) │
│ │
│ ╭───────────────────── locals ──────────────────────╮ │
│ │ hint = (<class 'int'>, <class 'str'>) │ │
│ │ hint_sign = None │ │
│ │ wrapper_subclass = None │ │
│ ╰───────────────────────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
BeartypeDoorNonpepException: Type hint (<class 'int'>, <class 'str'>) currently unsupported by "beartype.door.TypeHint".
Thank you kindly for the help!
.. Two more day 'til you get a deluge of replies!
It's happening! It's happening already and it's only Wednesday. Truly, your dire prophecy is unfolding as we type. :scream_cat:
Would it be possible to use
is_subhintlikeissubclass, with a tuple of types? For example:
Fascinating request. In theory, sure. In practice, is there much point? After all, @beartype already more-or-less supports this exact use case with:
-
Standard PEP 484- and 604-compliant unions: e.g.,
>>> from beartype.door import is_subhint >>> is_subhint(str, int | str) # <-- PEP 604 union for the win, 'cause you just don't care True -
A non-standard function that you yourself define that dynamically coerces tuples into PEP 484-compliant unions: e.g.,
>>> from beartype.door import is_subhint >>> from beartype.typing import Union # Function coercing tuples into unions. @beartype actually does this *A LOT* internally. # So, you just know that (A) it works and (B) it's questionable dark magic. >>> def tuple_to_union(hints: tuple) -> object: return Union.__getitem__(hints) >>> is_subhint(str, tuple_to_union((int, str))) # <-- tuple of types for the win, 'cause you do actually care True
Do either of those two examples support your use case? The second is really close to what you want. There's a tuple of types, which is good. There's also an intermediary conversion function, which is perhaps less good.
And now for the Ultimate Badness™. Since @beartype already more-or-less supports this, this is really low priority stuff for us... I've got five feature requests to feed this week. You wouldn't want to starve an orphan feature request, would you!?
... Well, seems like it took a week for that deluge; I was more tuckered out than I realized! 😅 Ah, well; you win some, you lose some.
I ended up using Union[*types], but it would be nice to bring is_subhint in line with issinstance, issubclass, and even is_bearable! 😅