@@ -53,17 +53,27 @@ def build(
53
53
54
54
type_list_data = []
55
55
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.
56
63
for _type in data .type :
57
64
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.
58
68
59
69
def process_items (
60
- preserve_name_for_item : Schema | Reference | None = None ,
70
+ use_original_name_for : oai . Schema | oai . Reference | None = None ,
61
71
) -> tuple [list [PropertyProtocol ] | PropertyError , Schemas ]:
62
72
props : list [PropertyProtocol ] = []
63
73
new_schemas = schemas
64
- items_with_classes : list [Schema | Reference ] = []
74
+ schemas_with_classes : list [oai . Schema | oai . Reference ] = []
65
75
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 } "
67
77
sub_prop , new_schemas = property_from_data (
68
78
name = sub_prop_name ,
69
79
required = True ,
@@ -75,15 +85,19 @@ def process_items(
75
85
if isinstance (sub_prop , PropertyError ):
76
86
return PropertyError (detail = f"Invalid property in union { name } " , data = sub_prop_data ), new_schemas
77
87
if isinstance (sub_prop , HasNamedClass ):
78
- items_with_classes .append (sub_prop_data )
88
+ schemas_with_classes .append (sub_prop_data )
79
89
props .append (sub_prop )
80
90
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 ])
87
101
88
102
return props , new_schemas
89
103
0 commit comments