44from pathlib import Path
55from typing import Any , Iterable , Literal , Type , get_origin , get_type_hints
66
7- from annotated_types import BaseMetadata , GroupedMetadata
7+ from annotated_types import BaseMetadata , GroupedMetadata , Len
88
99from . import DatetimeTag , SelectTag , Tag
1010from .callback_tag import CallbackTag
@@ -105,11 +105,48 @@ def tag_factory(
105105 new .annotation = annotation or field_type .__origin__
106106 # Annotated[date, Tag(name="hello")] = datetime.fromisoformat(...) -> DatetimeTag(date=True)
107107 tag = tag_assure_type (new ._fetch_from (Tag (* args , ** kwargs ), include_ref = True ))
108- elif isinstance (metadata , (BaseMetadata , GroupedMetadata )):
108+ elif isinstance (metadata , (BaseMetadata , Len )):
109+ # Why not checking `GroupedMetadata` instead of `Len`? See below. You won't believe.
109110 validators .append (metadata )
110111 if not tag :
111112 tag = tag_assure_type (Tag (val , description , annotation , * args , ** kwargs ))
112113
113114 if validators : # we prepend annotated_types validators to the current validator
114115 tag ._add_validation (validators )
115116 return tag
117+
118+ # NOTE I'd like to check `GroupedMetadata` instead of the `Len` (the only currently supported GroupedMetadata).
119+ # However, that's not possible because a mere checking of some things from the typing module,
120+ # like `isinstance(Literal[2], GroupedMetadata)`, will add a trailing __annotations__ to some objects in the typing module.
121+ #
122+ # Once upon a day, I thought I debug a pretty obscure case from tyro, concerning global imports of a primitive specs registry
123+ # when doing tests in paralel. The bug appeared reliably but only on the server, never on the localhost, and for Python3.12+
124+ # only (which I am running to on localhost). I find out a test fails when another specific test is there. But it was not the end,
125+ # the problem lied not in tyro but deeper, some chances are there is a bug in the Python itself. It turned out
126+ # a mere presence of a Literal in an Annotated statement cause a bug in an independent test.
127+ #
128+ # The dark magic happens in typing._proto_hook at line
129+ # `getattr(base, '__annotations__', {})`
130+ # while `base = _LiteralSpecialForm)`
131+ #
132+ # This utterly obscure case will make tyro cycle when handling unions,
133+ # Union must not have trailing __annotations__
134+ # `runm([Subc1, Subc2])` # will cycle
135+ # So the code breaks up a different time on another place. Tremendous.
136+ #
137+ # https://github.com/annotated-types/annotated-types/issues/94
138+ #
139+ # ```python
140+ # from typing import Literal, Optional, Union
141+ # from annotated_types import GroupedMetadata, Len
142+ # print(hasattr(Union[1, 2],"__annotations__")) # False
143+ # print(hasattr(Optional,"__annotations__")) # False
144+ # print(hasattr(Literal,"__annotations__")) # False
145+ #
146+ # isinstance(Literal, GroupedMetadata)
147+ #
148+ # print(Union[1, 2].__annotations__) # {}
149+ # print(Optional.__annotations__) # {}
150+ # print(Literal.__annotations__) # {}
151+ # ````
152+ #
0 commit comments