@@ -728,6 +728,82 @@ def should_parse_enum_as_literal(self, obj: JsonSchemaObject) -> bool:
728728 self .enum_field_as_literal == LiteralType .One and len (obj .enum ) == 1
729729 )
730730
731+ @classmethod
732+ def _extract_const_enum_from_combined ( # noqa: PLR0912
733+ cls , items : list [JsonSchemaObject ], parent_type : str | list [str ] | None
734+ ) -> tuple [list [Any ], list [str ], str | None , bool ] | None :
735+ """Extract enum values from oneOf/anyOf const pattern."""
736+ enum_values : list [Any ] = []
737+ varnames : list [str ] = []
738+ nullable = False
739+ inferred_type : str | None = None
740+
741+ for item in items :
742+ if item .type == "null" and "const" not in item .extras :
743+ nullable = True
744+ continue
745+
746+ if "const" not in item .extras :
747+ return None
748+
749+ if item .ref or item .properties or item .oneOf or item .anyOf or item .allOf :
750+ return None
751+
752+ const_value = item .extras ["const" ]
753+ enum_values .append (const_value )
754+
755+ if item .title :
756+ varnames .append (item .title )
757+ else :
758+ varnames .append (str (const_value ))
759+
760+ if inferred_type is None and const_value is not None :
761+ if isinstance (const_value , str ):
762+ inferred_type = "string"
763+ elif isinstance (const_value , bool ):
764+ inferred_type = "boolean"
765+ elif isinstance (const_value , int ):
766+ inferred_type = "integer"
767+ elif isinstance (const_value , float ):
768+ inferred_type = "number"
769+
770+ if not enum_values : # pragma: no cover
771+ return None
772+
773+ final_type : str | None
774+ if isinstance (parent_type , str ):
775+ final_type = parent_type
776+ elif isinstance (parent_type , list ):
777+ non_null_types = [t for t in parent_type if t != "null" ]
778+ final_type = non_null_types [0 ] if non_null_types else inferred_type
779+ if "null" in parent_type :
780+ nullable = True
781+ else :
782+ final_type = inferred_type
783+
784+ return (enum_values , varnames , final_type , nullable )
785+
786+ def _create_synthetic_enum_obj (
787+ self ,
788+ original : JsonSchemaObject ,
789+ enum_values : list [Any ],
790+ varnames : list [str ],
791+ enum_type : str | None ,
792+ nullable : bool , # noqa: FBT001
793+ ) -> JsonSchemaObject :
794+ """Create a synthetic JsonSchemaObject for enum parsing."""
795+ final_enum = [* enum_values , None ] if nullable else enum_values
796+ final_varnames = varnames if len (varnames ) == len (enum_values ) else []
797+
798+ return self .SCHEMA_OBJECT_TYPE (
799+ type = enum_type ,
800+ enum = final_enum ,
801+ title = original .title ,
802+ description = original .description ,
803+ x_enum_varnames = final_varnames ,
804+ default = original .default if original .has_default else None ,
805+ )
806+
731807 def is_constraints_field (self , obj : JsonSchemaObject ) -> bool :
732808 """Check if a field should include constraints."""
733809 return obj .is_array or (
@@ -1786,8 +1862,22 @@ def parse_item( # noqa: PLR0911, PLR0912
17861862 if item .discriminator and parent and parent .is_array and (item .oneOf or item .anyOf ):
17871863 return self .parse_root_type (name , item , path )
17881864 if item .anyOf :
1865+ const_enum_data = self ._extract_const_enum_from_combined (item .anyOf , item .type )
1866+ if const_enum_data is not None :
1867+ enum_values , varnames , enum_type , nullable = const_enum_data
1868+ synthetic_obj = self ._create_synthetic_enum_obj (item , enum_values , varnames , enum_type , nullable )
1869+ if self .should_parse_enum_as_literal (synthetic_obj ):
1870+ return self .parse_enum_as_literal (synthetic_obj )
1871+ return self .parse_enum (name , synthetic_obj , get_special_path ("enum" , path ), singular_name = singular_name )
17891872 return self .data_type (data_types = self .parse_any_of (name , item , get_special_path ("anyOf" , path )))
17901873 if item .oneOf :
1874+ const_enum_data = self ._extract_const_enum_from_combined (item .oneOf , item .type )
1875+ if const_enum_data is not None :
1876+ enum_values , varnames , enum_type , nullable = const_enum_data
1877+ synthetic_obj = self ._create_synthetic_enum_obj (item , enum_values , varnames , enum_type , nullable )
1878+ if self .should_parse_enum_as_literal (synthetic_obj ):
1879+ return self .parse_enum_as_literal (synthetic_obj )
1880+ return self .parse_enum (name , synthetic_obj , get_special_path ("enum" , path ), singular_name = singular_name )
17911881 return self .data_type (data_types = self .parse_one_of (name , item , get_special_path ("oneOf" , path )))
17921882 if item .allOf :
17931883 all_of_path = get_special_path ("allOf" , path )
@@ -1964,7 +2054,7 @@ def parse_array(
19642054 self .results .append (data_model_root )
19652055 return self .data_type (reference = reference )
19662056
1967- def parse_root_type ( # noqa: PLR0912
2057+ def parse_root_type ( # noqa: PLR0912, PLR0915
19682058 self ,
19692059 name : str ,
19702060 obj : JsonSchemaObject ,
@@ -1983,18 +2073,28 @@ def parse_root_type( # noqa: PLR0912
19832073 name , obj , get_special_path ("array" , path )
19842074 ).data_type # pragma: no cover
19852075 elif obj .anyOf or obj .oneOf :
1986- reference = self .model_resolver .add (path , name , loaded = True , class_name = True )
1987- if obj .anyOf :
1988- data_types : list [DataType ] = self .parse_any_of (name , obj , get_special_path ("anyOf" , path ))
2076+ combined_items = obj .anyOf or obj .oneOf
2077+ const_enum_data = self ._extract_const_enum_from_combined (combined_items , obj .type )
2078+ if const_enum_data is not None : # pragma: no cover
2079+ enum_values , varnames , enum_type , nullable = const_enum_data
2080+ synthetic_obj = self ._create_synthetic_enum_obj (obj , enum_values , varnames , enum_type , nullable )
2081+ if self .should_parse_enum_as_literal (synthetic_obj ):
2082+ data_type = self .parse_enum_as_literal (synthetic_obj )
2083+ else :
2084+ data_type = self .parse_enum (name , synthetic_obj , path )
19892085 else :
1990- data_types = self .parse_one_of (name , obj , get_special_path ("oneOf" , path ))
1991-
1992- if len (data_types ) > 1 : # pragma: no cover
1993- data_type = self .data_type (data_types = data_types )
1994- elif not data_types : # pragma: no cover
1995- return EmptyDataType ()
1996- else : # pragma: no cover
1997- data_type = data_types [0 ]
2086+ reference = self .model_resolver .add (path , name , loaded = True , class_name = True )
2087+ if obj .anyOf :
2088+ data_types : list [DataType ] = self .parse_any_of (name , obj , get_special_path ("anyOf" , path ))
2089+ else :
2090+ data_types = self .parse_one_of (name , obj , get_special_path ("oneOf" , path ))
2091+
2092+ if len (data_types ) > 1 : # pragma: no cover
2093+ data_type = self .data_type (data_types = data_types )
2094+ elif not data_types : # pragma: no cover
2095+ return EmptyDataType ()
2096+ else : # pragma: no cover
2097+ data_type = data_types [0 ]
19982098 elif obj .patternProperties :
19992099 data_type = self .parse_pattern_properties (name , obj .patternProperties , path )
20002100 elif obj .enum :
@@ -2408,7 +2508,7 @@ def parse_raw_obj(
24082508 )
24092509 self .parse_obj (name , obj , path )
24102510
2411- def parse_obj (
2511+ def parse_obj ( # noqa: PLR0912
24122512 self ,
24132513 name : str ,
24142514 obj : JsonSchemaObject ,
@@ -2420,9 +2520,19 @@ def parse_obj(
24202520 elif obj .allOf :
24212521 self .parse_all_of (name , obj , path )
24222522 elif obj .oneOf or obj .anyOf :
2423- data_type = self .parse_root_type (name , obj , path )
2424- if isinstance (data_type , EmptyDataType ) and obj .properties :
2425- self .parse_object (name , obj , path ) # pragma: no cover
2523+ combined_items = obj .oneOf or obj .anyOf
2524+ const_enum_data = self ._extract_const_enum_from_combined (combined_items , obj .type )
2525+ if const_enum_data is not None :
2526+ enum_values , varnames , enum_type , nullable = const_enum_data
2527+ synthetic_obj = self ._create_synthetic_enum_obj (obj , enum_values , varnames , enum_type , nullable )
2528+ if not self .should_parse_enum_as_literal (synthetic_obj ):
2529+ self .parse_enum (name , synthetic_obj , path )
2530+ else :
2531+ self .parse_root_type (name , synthetic_obj , path )
2532+ else :
2533+ data_type = self .parse_root_type (name , obj , path )
2534+ if isinstance (data_type , EmptyDataType ) and obj .properties :
2535+ self .parse_object (name , obj , path ) # pragma: no cover
24262536 elif obj .properties :
24272537 if obj .has_multiple_types and isinstance (obj .type , list ):
24282538 self ._parse_multiple_types_with_properties (name , obj , obj .type , path )
0 commit comments