88from ..utils import safe_get
99from .diagnostics import invalid_spec
1010from .model import (
11+ AnyAnnotation ,
12+ DictAnnotation ,
1113 EnumDef ,
1214 FieldDef ,
15+ ListAnnotation ,
16+ LiteralAnnotation ,
17+ NamedAnnotation ,
1318 NormalizedSpec ,
1419 OperationDef ,
20+ TupleAnnotation ,
1521 TypeAliasDef ,
22+ TypeAnnotation ,
1623 TypedDictDef ,
24+ UnionAnnotation ,
1725)
1826
1927_METHODS = ("get" , "post" , "put" , "patch" , "delete" , "head" , "options" )
@@ -115,7 +123,7 @@ def _is_used_type_name(state: _TypeState, name: str) -> bool:
115123 )
116124
117125
118- def _is_unique_type_name (state : _TypeState , name : str ) -> str :
126+ def _unique_type_name (state : _TypeState , name : str ) -> str :
119127 if not _is_used_type_name (state , name ):
120128 return name
121129
@@ -214,50 +222,56 @@ def _without_processing(state: _TypeState, name: str) -> _TypeState:
214222 )
215223
216224
217- def _union (variants : Iterable [str ]) -> str :
218- unique : list [str ] = []
225+ def _union (variants : Iterable [TypeAnnotation ]) -> TypeAnnotation :
226+ unique : list [TypeAnnotation ] = []
219227 for item in variants :
220228 if item not in unique :
221229 unique .append (item )
222- return " | " .join (unique ) if unique else "Any"
230+ if not unique :
231+ return AnyAnnotation ()
232+ if len (unique ) == 1 :
233+ return unique [0 ]
234+ return UnionAnnotation (tuple (unique ))
223235
224236
225- def _ensure_component (state : _TypeState , name : str ) -> tuple [str , _TypeState ]:
237+ def _ensure_component (
238+ state : _TypeState , name : str
239+ ) -> tuple [TypeAnnotation , _TypeState ]:
226240 """
227241 Ensures that a component schema is registered as a type.
228242 """
229243 existing = state .component_type_names .get (name )
230244 if existing is not None :
231- return existing , state
245+ return NamedAnnotation ( existing ) , state
232246
233247 schema = safe_get (state .components , "schemas" , name , type = dict )
234248 if schema is None :
235249 raise invalid_spec ("Unresolved component schema reference" , name )
236250
237- type_name = _is_unique_type_name (state , _pascal (name ))
251+ type_name = _unique_type_name (state , _pascal (name ))
238252
239253 state = _with_component_type_name (state , name , type_name )
240254 annotation , state = _schema_to_type (state , schema , type_name , component_name = name )
241255 if not _is_registered_type_name (state , type_name ):
242256 state = _with_alias (state , TypeAliasDef (name = type_name , annotation = annotation ))
243- return type_name , state
257+ return NamedAnnotation ( type_name ) , state
244258
245259
246- def _nullable (annotation : str , nullable : bool ) -> str :
247- return f" { annotation } | None" if nullable else annotation
260+ def _nullable (annotation : TypeAnnotation , nullable : bool ) -> TypeAnnotation :
261+ return _union (( annotation , NamedAnnotation ( " None"))) if nullable else annotation
248262
249263
250264def _schema_enum_to_type (
251265 state : _TypeState ,
252266 schema : dict ,
253267 hint : str ,
254268 component_name : str | None ,
255- ) -> tuple [str , _TypeState ]:
269+ ) -> tuple [TypeAnnotation , _TypeState ]:
256270 values = schema ["enum" ]
257271 if component_name is not None :
258272 # Component-level enums are rendered as actual reusable Enum classes
259273 enum = EnumDef (name = hint , values = tuple (values ))
260- return enum .name , _with_enum (state , enum )
274+ return NamedAnnotation ( enum .name ) , _with_enum (state , enum )
261275
262276 # Inline enums are just rendered as literals
263277 title = str (schema .get ("title" ) or "" )
@@ -267,17 +281,16 @@ def _schema_enum_to_type(
267281 if signature is not None :
268282 existing = state .aliases_by_signature .get (signature )
269283 if existing :
270- return existing , state
284+ return NamedAnnotation ( existing ) , state
271285
272- literal_values = ", " .join (values_list )
273- alias = TypeAliasDef (name = alias_name , annotation = f"Literal[{ literal_values } ]" )
274- return alias_name , _with_alias (state , alias , signature )
286+ alias = TypeAliasDef (name = alias_name , annotation = LiteralAnnotation (tuple (values )))
287+ return NamedAnnotation (alias_name ), _with_alias (state , alias , signature )
275288
276289
277290def _schema_union_to_type (
278291 state : _TypeState , schemas : list , hint : str
279- ) -> tuple [str , _TypeState ]:
280- variants : list [str ] = []
292+ ) -> tuple [TypeAnnotation , _TypeState ]:
293+ variants : list [TypeAnnotation ] = []
281294 for item in schemas :
282295 item_type , state = _schema_to_type (state , item , f"{ hint } Variant" )
283296 variants .append (item_type )
@@ -286,11 +299,11 @@ def _schema_union_to_type(
286299
287300def _schema_type_list_to_type (
288301 state : _TypeState , schema_types : list , hint : str
289- ) -> tuple [str , _TypeState ]:
290- mapped : list [str ] = []
302+ ) -> tuple [TypeAnnotation , _TypeState ]:
303+ mapped : list [TypeAnnotation ] = []
291304 for schema_type in schema_types :
292305 if schema_type == "null" :
293- mapped .append ("None" )
306+ mapped .append (NamedAnnotation ( "None" ) )
294307 continue
295308 item_type , state = _schema_to_type (state , {"type" : schema_type }, hint )
296309 mapped .append (item_type )
@@ -299,39 +312,41 @@ def _schema_type_list_to_type(
299312
300313def _schema_array_to_type (
301314 state : _TypeState , schema : dict , hint : str
302- ) -> tuple [str , _TypeState ]:
315+ ) -> tuple [TypeAnnotation , _TypeState ]:
303316 nullable = bool (schema .get ("nullable" ))
304317 prefix_items = safe_get (schema , "prefixItems" , type = list )
305318 if prefix_items is not None :
306- item_types : list [str ] = []
319+ item_types : list [TypeAnnotation ] = []
307320 for item in prefix_items :
308321 item_type , state = _schema_to_type (state , item , f"{ hint } Item" )
309322 item_types .append (item_type )
310- return _nullable (f" tuple[ { ', ' . join (item_types )} ]" , nullable ), state
323+ return _nullable (TupleAnnotation ( tuple (item_types )) , nullable ), state
311324
312325 item_schema = safe_get (schema , "items" , type = dict ) or {}
313326 item_type , state = _schema_to_type (state , item_schema , f"{ hint } Item" )
314- return _nullable (f"list[ { item_type } ]" , nullable ), state
327+ return _nullable (ListAnnotation ( item_type ) , nullable ), state
315328
316329
317330def _schema_map_to_type (
318331 state : _TypeState , schema : dict , hint : str
319- ) -> tuple [str , _TypeState ]:
332+ ) -> tuple [TypeAnnotation , _TypeState ]:
320333 nullable = bool (schema .get ("nullable" ))
321334 additional_properties = schema ["additionalProperties" ]
322335 value_type , state = _schema_to_type (state , additional_properties , f"{ hint } Value" )
323- return _nullable (f"dict[str, { value_type } ]" , nullable ), state
336+ return _nullable (
337+ DictAnnotation (NamedAnnotation ("str" ), value_type ), nullable
338+ ), state
324339
325340
326341def _schema_object_to_type (
327342 state : _TypeState , schema : dict , hint : str
328- ) -> tuple [str , _TypeState ]:
343+ ) -> tuple [TypeAnnotation , _TypeState ]:
329344 nullable = bool (schema .get ("nullable" ))
330345 name = _type_name_from_hint (hint )
331346 if name in state .processing :
332- return name , state
347+ return NamedAnnotation ( name ) , state
333348 if name in state .typed_dicts :
334- return name , state
349+ return NamedAnnotation ( name ) , state
335350
336351 state = _with_processing (state , name )
337352 props = safe_get (schema , "properties" , type = dict ) or {}
@@ -353,7 +368,7 @@ def _schema_object_to_type(
353368
354369 state = _without_processing (state , name )
355370 state = _with_typeddict (state , TypedDictDef (name = name , fields = tuple (fields )))
356- return _nullable (name , nullable ), state
371+ return _nullable (NamedAnnotation ( name ) , nullable ), state
357372
358373
359374def _schema_to_type (
@@ -362,30 +377,30 @@ def _schema_to_type(
362377 hint : str ,
363378 * ,
364379 component_name : str | None = None ,
365- ) -> tuple [str , _TypeState ]:
380+ ) -> tuple [TypeAnnotation , _TypeState ]:
366381 """
367- Takes a JSON schema object and returns the corresponding Python
368- type annotation along with an updated state containing any new
382+ Takes a JSON schema object and returns the corresponding Python type
383+ annotation model along with an updated state containing any new
369384 type definitions.
370385 """
371386 if not schema :
372- return "Any" , state
387+ return AnyAnnotation () , state
373388
374389 schema_type = schema .get ("type" )
375390 nullable = bool (schema .get ("nullable" ))
376391 if schema_type == "null" :
377- return "None" , state
392+ return NamedAnnotation ( "None" ) , state
378393
379394 ref = schema .get ("$ref" )
380395 if isinstance (ref , str ) and ref .startswith ("#/components/schemas/" ):
381396 component = ref .rsplit ("/" , 1 )[- 1 ]
382397 return _ensure_component (state , component )
383398
384399 if "const" in schema :
385- return f"Literal[ { schema [' const' ]!r } ]" , state
400+ return LiteralAnnotation (( schema [" const" ],)) , state
386401
387402 if schema_type == "string" and schema .get ("format" ) == "binary" :
388- return "bytes" , state
403+ return NamedAnnotation ( "bytes" ) , state
389404
390405 if "enum" in schema and isinstance (schema ["enum" ], list ):
391406 return _schema_enum_to_type (state , schema , hint , component_name )
@@ -414,10 +429,15 @@ def _schema_to_type(
414429 return _schema_object_to_type (state , schema , hint )
415430
416431 base = _PRIMITIVES .get (str (schema_type ), "Any" )
417- return _nullable (base , nullable ), state
432+ annotation : TypeAnnotation = (
433+ AnyAnnotation () if base == "Any" else NamedAnnotation (base )
434+ )
435+ return _nullable (annotation , nullable ), state
418436
419437
420- def _schema_type (state : _TypeState , schema : dict , hint : str ) -> tuple [str , _TypeState ]:
438+ def _schema_type (
439+ state : _TypeState , schema : dict , hint : str
440+ ) -> tuple [TypeAnnotation , _TypeState ]:
421441 return _schema_to_type (state , schema or {}, hint )
422442
423443
@@ -507,10 +527,10 @@ def _bucket_type(
507527 state : _TypeState ,
508528 bucket : _ParameterBucket ,
509529 hint : str ,
510- default : str = "dict[str, Any]" ,
511- ) -> tuple [str , _TypeState ]:
530+ default : TypeAnnotation | None = None ,
531+ ) -> tuple [TypeAnnotation , _TypeState ]:
512532 if not bucket .props :
513- return default , state
533+ return default or DictAnnotation ( NamedAnnotation ( "str" ), AnyAnnotation ()) , state
514534 return _schema_type (
515535 state ,
516536 {
@@ -526,11 +546,11 @@ def _request_body_type(
526546 state : _TypeState ,
527547 operation : dict ,
528548 hint : str ,
529- ) -> tuple [str | None , bool , _TypeState ]:
549+ ) -> tuple [TypeAnnotation | None , bool , _TypeState ]:
530550 """
531551 Determines the type of the request body for an operation, if any.
532552 Returns a tuple of (body_type, required, updated_state).
533- The body_type is a string representing the Python type annotation for the request body.
553+ The body_type is the Python type annotation model for the request body.
534554 The required flag indicates whether the request body is required.
535555 The updated_state is the new _TypeState after processing the request body schema.
536556 """
@@ -549,9 +569,9 @@ def _request_body_type(
549569
550570def _response_type (
551571 state : _TypeState , operation : dict , hint : str
552- ) -> tuple [str , _TypeState ]:
572+ ) -> tuple [TypeAnnotation , _TypeState ]:
553573 responses = safe_get (operation , "responses" , type = dict ) or {}
554- response_types : list [str ] = []
574+ response_types : list [TypeAnnotation ] = []
555575
556576 for code in sorted (responses .keys ()):
557577 if not code .startswith ("2" ):
@@ -563,12 +583,12 @@ def _response_type(
563583 )
564584 if schema is not None :
565585 if not schema :
566- response_types .append ("None" )
586+ response_types .append (NamedAnnotation ( "None" ) )
567587 else :
568588 response_type , state = _schema_type (state , schema , hint )
569589 response_types .append (response_type )
570590 else :
571- response_types .append ("None" )
591+ response_types .append (NamedAnnotation ( "None" ) )
572592 return _union (response_types ), state
573593
574594
0 commit comments