Description
From the new documentation for type aliases, e.g.
[1]: type x = int
[2]: x
>>> 'int'
Is this expected behavior? I realize mypy probably doesn't care if typehints are str or not, but runtime typechecking (say, isinstance
, or --- what i'm testing here --- beartype
) needs class instances to operate.
Using eval
can be a nasty work around for this, but I ran into a problem if the type aliases are used in future definitions.
type E_ID = int | str
type Entity[Data<=bt.Iterable] = (E_ID; Data?)
[3]: eval(E_ID) # it's not an alias, it's just the classes
>>> int | str
[4]: eval(Entity) # now we're getting relative fwd refs?
>>> typing.Tuple[ForwardRef('int | str'), typing.Optional[~_coconut_typevar_Data_1]]
This nested generic type unfortunately can't be used with isinstance
, but it's a normal check e.g. for beartype (which I thought the door API would pair lovely with coconut! 😄):
from beartype.door import TypeHint as th
def eth(typehint) = typehint |> eval |> th # need to eval, then wrap in TypeHint
[5]: eth(Entity).is_bearable(('hi',(2,3,4)))
python3.10/site-packages/beartype/_util/hint/pep/utilpeptest.py:345: BeartypeDecorHintPep585DeprecationWarning: PEP 484 type hint typing.Tuple[ForwardRef('int | str'), typing.Optional[~_coconut_typevar_Data_1]] deprecated by PEP 585 scheduled for removal in the first Python version released after October 5th, 2025. To resolve this, import this hint from "beartype.typing" rather than "typing". See this discussion for further details and alternatives:
https://github.com/beartype/beartype#pep-585-deprecations
warn(
[...]
File ~/miniconda3/envs/grabble/lib/python3.10/site-packages/beartype/_check/checkmake.py:234, in make_func_tester(hint, conf, exception_cls)
207 # If this hint contains one or more relative forward references, this hint
208 # is non-portable across lexical scopes. Why? Because this hint is relative
209 # to and thus valid only with respect to the caller's current lexical scope.
(...)
231 # factory (e.g., our increasingly popular public beartype.door.is_bearable()
232 # tester) sufficiently slow as to be pragmatically infeasible.
233 if hint_forwardrefs_class_basename:
--> 234 raise BeartypeDecorHintForwardRefException(
235 f'{EXCEPTION_PLACEHOLDER}type hint {repr(hint)} '
236 f'contains one or more relative forward references:\n'
237 f'\t{repr(hint_forwardrefs_class_basename)}\n'
238 f'Beartype prohibits relative forward references outside of '
239 f'@beartype-decorated callables. For your own personal safety and '
240 f'those of the codebases you love, consider canonicalizing these '
241 f'relative forward references into absolute forward references '
242 f'(e.g., by replacing "MuhClass" with "muh_module.MuhClass").'
243 )
244 # Else, this hint contains *NO* relative forward references.
245
246 # Unqualified basename of this tester function, uniquified by suffixing an
247 # arbitrary integer guaranteed to be unique to this tester function.
248 func_tester_name = (
249 f'{FUNC_TESTER_NAME_PREFIX}{next(_func_tester_name_counter)}')
BeartypeDecorHintForwardRefException: is_bearable() type hint typing.Tuple[ForwardRef('int | str'), typing.Optional[~_coconut_typevar_Data_1]] contains one or more relative forward references:
('int | str',)
Beartype prohibits relative forward references outside of @beartype-decorated callables. For your own personal safety and those of the codebases you love, consider canonicalizing these relative forward references into absolute forward references (e.g., by replacing "MuhClass" with "muh_module.MuhClass").
as they point out, this use of ForwardRef
probably won't be around forever, if I'm understanding?
It works fine without the new syntax:
E_ID = bt.Union[int,str]
DataType = bt.TypeVar('DataType', bound=bt.Iterable)
Entity = bt.Tuple[E_ID, bt.Optional[DataType]]
[0]: (th(Entity).is_bearable(('hi',(2,3,4))),
th(Entity).is_bearable((1.20,(2,3,4))),
th(Entity).is_bearable((0,None)))
>>> (True, False, True)
As an aside here, I do think friendly interop with beartype
has a lot of potential for functional style e.g. coconut, since it clarifies a ton of nasty situations like type(MyEnum.member) is MyEnum
, and runs very fast. Providing an option for beartype users to run coconut with e.g. a beartype.typing
backend would be very nice. See some other examples if this is interesting to you, e.g. the explanation for numerary
or ... this other proposal I'm on, dealing with faster type-based dispatch via plum
. I'm hoping to use beartype+plum for some kind of typeclass
approximation soon, for rich type-based ad hoc polymorphism 😅 (rather than just pattern matching... more like rust traits)