11from dataclasses import dataclass
2- from typing import Any , ClassVar , Generic , Optional , TypeVar , overload
2+ from typing import Any , ClassVar , Generic , Literal , Optional , TypeVar , get_args , get_origin , overload
33
44from pydantic_core import core_schema
55from pydantic .json_schema import JsonSchemaValue
@@ -17,64 +17,19 @@ def _parse_typeid(value: Any) -> TypeID:
1717 Supports:
1818 - TypeID -> TypeID
1919 - str -> parse into TypeID
20-
21- Tries common parsing APIs to avoid coupling to one exact core method.
22- If none match, update this function to call your canonical parser.
2320 """
2421 if isinstance (value , TypeID ):
2522 return value
2623
2724 if isinstance (value , str ):
28- # Try the common names
29- for name in ("from_str" , "from_string" , "parse" ):
30- fn = getattr (TypeID , name , None )
31- if callable (fn ):
32- return fn (value ) # type: ignore[misc]
33- # Fallback: constructor accepts string
34- try :
35- return TypeID (value ) # type: ignore[call-arg]
36- except Exception as e :
37- raise TypeError (
38- "TypeID Pydantic integration couldn't parse a string. "
39- "Please implement TypeID.from_str(s: str) (or .parse/.from_string), "
40- "or make TypeID(s: str) work. Original error: "
41- f"{ e !r} "
42- ) from e
25+ return TypeID .from_string (value )
4326
4427 raise TypeError (f"TypeID must be str or TypeID, got { type (value ).__name__ } " )
4528
4629
47- def _get_prefix (tid : TypeID ) -> Optional [str ]:
48- """
49- Extract prefix from TypeID. Adjust this if your core uses a different attribute.
50- """
51- # Common: tid.prefix
52- pref = getattr (tid , "prefix" , None )
53- if isinstance (pref , str ):
54- return pref
55- return None
56-
57-
58- def _to_str (tid : TypeID ) -> str :
59- """
60- Convert TypeID to its canonical string representation.
61- """
62- # Prefer a dedicated method if you have one
63- for name in ("to_string" , "__str__" ):
64- fn = getattr (tid , name , None )
65- if callable (fn ):
66- try :
67- return fn () if name == "to_string" else str (tid )
68- except Exception :
69- pass
70- return str (tid )
71-
72-
7330@dataclass (frozen = True )
7431class _TypeIDMeta :
7532 expected_prefix : Optional [str ] = None
76- # Optional: if you have a known regex for full string form, set it for JSON schema
77- # pattern: Optional[str] = None
7833 pattern : Optional [str ] = None
7934 example : Optional [str ] = None
8035
@@ -93,9 +48,8 @@ def _validate(cls, v: Any) -> TypeID:
9348
9449 exp = cls ._typeid_meta .expected_prefix
9550 if exp is not None :
96- got = _get_prefix (tid )
97- if got != exp :
98- raise ValueError (f"TypeID prefix mismatch: expected '{ exp } ', got '{ got } '" )
51+ if tid .prefix != exp :
52+ raise ValueError (f"TypeID prefix mismatch: expected '{ exp } ', got '{ tid .prefix } '" )
9953
10054 return tid
10155
@@ -112,7 +66,7 @@ def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_sc
11266 return core_schema .no_info_plain_validator_function (
11367 cls ._validate ,
11468 serialization = core_schema .plain_serializer_function_ser_schema (
115- lambda v : _to_str (v ),
69+ lambda v : str (v ),
11670 when_used = "json" ,
11771 ),
11872 )
@@ -156,26 +110,42 @@ class User(BaseModel):
156110 """
157111
158112 @overload
159- def __class_getitem__ (cls , prefix : str ) -> type [TypeID ]: ...
113+ def __class_getitem__ (cls , prefix : str ) -> type [TypeID ]:
114+ ...
115+
160116 @overload
161- def __class_getitem__ (cls , prefix : tuple [str ]) -> type [TypeID ]: ...
117+ def __class_getitem__ (cls , prefix : tuple [str ]) -> type [TypeID ]:
118+ ...
162119
163120 def __class_getitem__ (cls , item : Any ) -> type [TypeID ]:
164- # Support TypeIDField["user"] or TypeIDField[("user",)]
121+ # Support:
122+ # - TypeIDField["user"]
123+ # - TypeIDField[Literal["user"]]
124+ # - TypeIDField[("user",)]
165125 if isinstance (item , tuple ):
166- if len (item ) != 1 or not isinstance (item [0 ], str ):
167- raise TypeError ("TypeIDField[...] expects a single string prefix, e.g. TypeIDField['user']" )
168- prefix = item [0 ]
169- else :
170- if not isinstance (item , str ):
171- raise TypeError ("TypeIDField[...] expects a string prefix, e.g. TypeIDField['user']" )
126+ if len (item ) != 1 :
127+ raise TypeError ("TypeIDField[...] expects a single prefix" )
128+ item = item [0 ]
129+
130+ # Literal["user"]
131+ if get_origin (item ) is Literal :
132+ args = get_args (item )
133+ if len (args ) != 1 or not isinstance (args [0 ], str ):
134+ raise TypeError ("TypeIDField[Literal['prefix']] expects a single string literal" )
135+ prefix = args [0 ]
136+
137+ # Plain "user"
138+ elif isinstance (item , str ):
172139 prefix = item
173140
141+ else :
142+ raise TypeError ("TypeIDField[...] expects a string prefix or Literal['prefix']" )
143+
174144 name = f"TypeIDField_{ prefix } "
175145
176146 # Optionally add a simple example that looks like TypeID format
177147 # You can improve this to a real example generator if your core has one.
178- example = f"{ prefix } _01hxxxxxxxxxxxxxxxxxxxxxxxxx "
148+ example = f"{ prefix } _01hxxxxxxxxxxxxxxxxxxxxxxx "
179149
180150 # Create a new subclass of _TypeIDFieldBase with fixed meta
181151 field_cls = type (
0 commit comments