@@ -53,17 +53,27 @@ def build(
5353
5454 type_list_data = []
5555 if isinstance (data .type , list ) and not (data .anyOf or data .oneOf ):
56+ # The schema specifies "type:" with a list of allowable types. If there is *not* also an "anyOf"
57+ # or "oneOf", then we should treat that as a shorthand for a oneOf where each variant is just
58+ # a single "type:". For example:
59+ # {"type": ["string", "int"]} becomes
60+ # {"oneOf": [{"type": "string"}, {"type": "int"}]}
61+ # However, if there *is* also an "anyOf" or "oneOf" list, then the information from "type:" is
62+ # redundant since every allowable variant type is already fully described in the list.
5663 for _type in data .type :
5764 type_list_data .append (data .model_copy (update = {"type" : _type , "default" : None }))
65+ # Here we're copying properties from the top-level union schema that might apply to one
66+ # of the type variants, like "format" for a string. But we don't copy "default" because
67+ # default values will be handled at the top level by the UnionProperty.
5868
5969 def process_items (
60- preserve_name_for_item : Schema | Reference | None = None ,
70+ use_original_name_for : oai . Schema | oai . Reference | None = None ,
6171 ) -> tuple [list [PropertyProtocol ] | PropertyError , Schemas ]:
6272 props : list [PropertyProtocol ] = []
6373 new_schemas = schemas
64- items_with_classes : list [Schema | Reference ] = []
74+ schemas_with_classes : list [oai . Schema | oai . Reference ] = []
6575 for i , sub_prop_data in enumerate (chain (data .anyOf , data .oneOf , type_list_data )):
66- sub_prop_name = name if sub_prop_data is preserve_name_for_item else f"{ name } _type_{ i } "
76+ sub_prop_name = name if sub_prop_data is use_original_name_for else f"{ name } _type_{ i } "
6777 sub_prop , new_schemas = property_from_data (
6878 name = sub_prop_name ,
6979 required = True ,
@@ -75,15 +85,19 @@ def process_items(
7585 if isinstance (sub_prop , PropertyError ):
7686 return PropertyError (detail = f"Invalid property in union { name } " , data = sub_prop_data ), new_schemas
7787 if isinstance (sub_prop , HasNamedClass ):
78- items_with_classes .append (sub_prop_data )
88+ schemas_with_classes .append (sub_prop_data )
7989 props .append (sub_prop )
8090
81- if (not preserve_name_for_item ) and (len (items_with_classes ) == 1 ):
82- # After our first pass, if it turns out that there was exactly one enum or model in the list,
83- # then we'll do a second pass where we use the original name for that item instead of a
84- # "xyz_type_n" synthetic name. Enum and model are the only types that would get their own
85- # Python class.
86- return process_items (items_with_classes [0 ])
91+ if (not use_original_name_for ) and len (schemas_with_classes ) == 1 :
92+ # An example of this scenario is a oneOf where one of the variants is an inline enum or
93+ # model, and the other is a simple value like null. If the name of the union property is
94+ # "foo" then it's desirable for the enum or model class to be named "Foo", not "FooType1".
95+ # So, we'll do a second pass where we tell ourselves to use the original property name
96+ # for that item instead of "{name}_type_{i}".
97+ # This only makes a functional difference if the variant was an inline schema, because
98+ # we wouldn't be generating a class otherwise, but even if it wasn't inline this will
99+ # save on pointlessly long variable names inside from_dict/to_dict.
100+ return process_items (use_original_name_for = schemas_with_classes [0 ])
87101
88102 return props , new_schemas
89103
0 commit comments