@@ -310,6 +310,7 @@ def validate_null_type(cls, value: Any) -> Any: # noqa: N805
310310 return value
311311
312312 items : Optional [Union [list [JsonSchemaObject ], JsonSchemaObject , bool ]] = None # noqa: UP007, UP045
313+ prefixItems : Optional [list [JsonSchemaObject ]] = None # noqa: N815, UP045
313314 uniqueItems : Optional [bool ] = None # noqa: N815, UP045
314315 type : Optional [Union [str , list [str ]]] = None # noqa: UP007, UP045
315316 format : Optional [str ] = None # noqa: UP045
@@ -383,7 +384,7 @@ def is_object(self) -> bool:
383384 @cached_property
384385 def is_array (self ) -> bool :
385386 """Check if the schema represents an array type."""
386- return self .items is not None or self .type == "array"
387+ return self .items is not None or self .prefixItems is not None or self . type == "array"
387388
388389 @cached_property
389390 def ref_object_name (self ) -> str : # pragma: no cover
@@ -1014,13 +1015,23 @@ def get_object_field( # noqa: PLR0913
10141015 original_field_name : str | None ,
10151016 ) -> DataModelFieldBase :
10161017 """Create a data model field from a JSON Schema object field."""
1018+ constraints = field .dict () if self .is_constraints_field (field ) else None
1019+ # Suppress minItems/maxItems for fixed-length tuples
1020+ if (
1021+ constraints
1022+ and field .prefixItems is not None
1023+ and field .minItems == field .maxItems == len (field .prefixItems )
1024+ and field .items in {None , False }
1025+ ):
1026+ constraints .pop ("minItems" , None )
1027+ constraints .pop ("maxItems" , None )
10171028 return self .data_model_field_type (
10181029 name = field_name ,
10191030 default = field .default ,
10201031 data_type = field_type ,
10211032 required = required ,
10221033 alias = alias ,
1023- constraints = field . dict () if self . is_constraints_field ( field ) else None ,
1034+ constraints = constraints ,
10241035 nullable = field .nullable if self .strict_nullable and (field .has_default or required ) else None ,
10251036 strip_default_none = self .strip_default_none ,
10261037 extras = self .get_field_extras (field ),
@@ -2366,7 +2377,7 @@ def parse_list_item(
23662377 for index , item in enumerate (target_items )
23672378 ]
23682379
2369- def parse_array_fields (
2380+ def parse_array_fields ( # noqa: PLR0912
23702381 self ,
23712382 name : str ,
23722383 obj : JsonSchemaObject ,
@@ -2384,13 +2395,23 @@ def parse_array_fields(
23842395 else :
23852396 required = not obj .nullable and required
23862397 nullable = None
2387- match obj .items :
2388- case JsonSchemaObject ():
2389- items : list [JsonSchemaObject ] = [obj .items ]
2390- case list ():
2391- items = obj .items
2392- case _:
2393- items = []
2398+ is_tuple = False
2399+ suppress_item_constraints = False
2400+ if isinstance (obj .items , JsonSchemaObject ):
2401+ items : list [JsonSchemaObject ] = [obj .items ]
2402+ elif isinstance (obj .items , list ):
2403+ items = obj .items
2404+ elif (
2405+ obj .prefixItems is not None
2406+ and obj .minItems == obj .maxItems == len (obj .prefixItems )
2407+ and obj .items in {None , False }
2408+ ):
2409+ # Suppress minItems/maxItems constraints for fixed-length tuples
2410+ suppress_item_constraints = True
2411+ items = obj .prefixItems
2412+ is_tuple = True
2413+ else :
2414+ items = []
23942415
23952416 if items :
23962417 item_data_types = self .parse_list_item (
@@ -2406,7 +2427,8 @@ def parse_array_fields(
24062427 data_types : list [DataType ] = [
24072428 self .data_type (
24082429 data_types = item_data_types ,
2409- is_list = True ,
2430+ is_tuple = is_tuple ,
2431+ is_list = not is_tuple ,
24102432 )
24112433 ]
24122434 # TODO: decide special path word for a combined data model.
@@ -2416,11 +2438,15 @@ def parse_array_fields(
24162438 data_types .append (self .parse_object (name , obj , get_special_path ("object" , path )))
24172439 if obj .enum and not self .ignore_enum_constraints :
24182440 data_types .append (self .parse_enum (name , obj , get_special_path ("enum" , path )))
2441+ constraints = obj .dict ()
2442+ if suppress_item_constraints :
2443+ constraints .pop ("minItems" , None )
2444+ constraints .pop ("maxItems" , None )
24192445 return self .data_model_field_type (
24202446 data_type = self .data_type (data_types = data_types ),
24212447 default = obj .default ,
24222448 required = required ,
2423- constraints = obj . dict () ,
2449+ constraints = constraints ,
24242450 nullable = nullable ,
24252451 strip_default_none = self .strip_default_none ,
24262452 extras = self .get_field_extras (obj ),
@@ -2921,6 +2947,9 @@ def _traverse_schema_objects( # noqa: PLR0912
29212947 case list () as items :
29222948 for item in items :
29232949 self ._traverse_schema_objects (item , path , callback , include_one_of = include_one_of )
2950+ if obj .prefixItems :
2951+ for item in obj .prefixItems :
2952+ self ._traverse_schema_objects (item , path , callback , include_one_of = include_one_of )
29242953 if isinstance (obj .additionalProperties , JsonSchemaObject ):
29252954 self ._traverse_schema_objects (obj .additionalProperties , path , callback , include_one_of = include_one_of )
29262955 if obj .patternProperties :
@@ -2943,10 +2972,32 @@ def _resolve_ref_callback(self, obj: JsonSchemaObject, path: list[str]) -> None:
29432972 if obj .ref :
29442973 self .resolve_ref (obj .ref )
29452974
2946- def _add_id_callback (self , obj : JsonSchemaObject , path : list [str ]) -> None :
2975+ def _add_id_callback (self , obj : JsonSchemaObject , path : list [str ]) -> None : # noqa: PLR0912
29472976 """Add $id to model resolver."""
29482977 if obj .id :
29492978 self .model_resolver .add_id (obj .id , path )
2979+ if obj .items :
2980+ if isinstance (obj .items , JsonSchemaObject ):
2981+ self .parse_id (obj .items , path )
2982+ elif isinstance (obj .items , list ):
2983+ for item in obj .items :
2984+ self .parse_id (item , path )
2985+ if obj .prefixItems :
2986+ for item in obj .prefixItems :
2987+ self .parse_id (item , path )
2988+ if isinstance (obj .additionalProperties , JsonSchemaObject ):
2989+ self .parse_id (obj .additionalProperties , path )
2990+ if obj .patternProperties :
2991+ for value in obj .patternProperties .values ():
2992+ self .parse_id (value , path )
2993+ for item in obj .anyOf :
2994+ self .parse_id (item , path )
2995+ for item in obj .allOf :
2996+ self .parse_id (item , path )
2997+ if obj .properties :
2998+ for property_value in obj .properties .values ():
2999+ if isinstance (property_value , JsonSchemaObject ):
3000+ self .parse_id (property_value , path )
29503001
29513002 def parse_ref (self , obj : JsonSchemaObject , path : list [str ]) -> None :
29523003 """Recursively parse all $ref references in a schema object."""
0 commit comments