1
1
from __future__ import annotations
2
2
3
3
from itertools import chain
4
- from typing import Any , ClassVar , cast
4
+ from typing import Any , Callable , ClassVar , cast
5
5
6
6
from attr import define , evolve
7
7
8
8
from openapi_python_client .parser .properties .has_named_class import HasNamedClass
9
- from openapi_python_client .schema .openapi_schema_pydantic .reference import Reference
10
- from openapi_python_client .schema .openapi_schema_pydantic .schema import Schema
11
9
12
10
from ... import Config
13
11
from ... import schema as oai
@@ -66,14 +64,46 @@ def build(
66
64
# of the type variants, like "format" for a string. But we don't copy "default" because
67
65
# default values will be handled at the top level by the UnionProperty.
68
66
67
+ def _add_index_suffix_to_variant_names (index : int ) -> str :
68
+ return f"{ name } _type_{ index } "
69
+
70
+ def _add_index_suffix_to_variant_names (index : int ) -> str :
71
+ return f"{ name } _type_{ index } "
72
+
69
73
def process_items (
70
- use_original_name_for : oai . Schema | oai . Reference | None = None ,
74
+ variant_name_from_index_func : Callable [[ int ], str ] = _add_index_suffix_to_variant_names ,
71
75
) -> tuple [list [PropertyProtocol ] | PropertyError , Schemas ]:
72
76
props : list [PropertyProtocol ] = []
73
77
new_schemas = schemas
74
- schemas_with_classes : list [oai .Schema | oai .Reference ] = []
75
78
for i , sub_prop_data in enumerate (chain (data .anyOf , data .oneOf , type_list_data )):
76
- sub_prop_name = name if sub_prop_data is use_original_name_for else f"{ name } _type_{ i } "
79
+ sub_prop_name = variant_name_from_index_func (i )
80
+
81
+ # The sub_prop_name logic is what makes this a bit complicated. That value is used only
82
+ # if sub_prop is an *inline* schema and needs us to make up a name for it. For instance,
83
+ # in the following schema--
84
+ #
85
+ # MyModel:
86
+ # properties:
87
+ # unionThing:
88
+ # oneOf:
89
+ # - type: object
90
+ # properties: ...
91
+ # - type: object
92
+ # properties: ...
93
+ #
94
+ # --both of the variants under oneOf are inline schemas. And since they're objects, we
95
+ # will be creating model classes for them, which need names. Inline schemas are named by
96
+ # concatenating names of parents; so, when we're in UnionProperty.build() for unionThing,
97
+ # the value of "name" is "my_model_union_thing", and then we set sub_prop_name to
98
+ # "my_model_union_thing_type_0" and "my_model_union_thing_type_1" for the two variants,
99
+ # and their model classes will be MyModelUnionThingType0 and MyModelUnionThingType1.
100
+ #
101
+ # However, in this example, if the second variant was just a scalar type instead of an
102
+ # object (like "type: null" or "type: string"), so that the first variant is the only
103
+ # one that needs a class... then it would be friendlier to call the first variant's
104
+ # class just MyModelUnionThing, not MyModelUnionThingType0. We'll check for that special
105
+ # case below; we can't know if that's the situation until after we've processed them all.
106
+
77
107
sub_prop , new_schemas = property_from_data (
78
108
name = sub_prop_name ,
79
109
required = True ,
@@ -84,26 +114,30 @@ def process_items(
84
114
)
85
115
if isinstance (sub_prop , PropertyError ):
86
116
return PropertyError (detail = f"Invalid property in union { name } " , data = sub_prop_data ), new_schemas
87
- if isinstance (sub_prop , HasNamedClass ):
88
- schemas_with_classes .append (sub_prop_data )
89
117
props .append (sub_prop )
90
118
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 ])
101
-
102
119
return props , new_schemas
103
120
104
- sub_properties , schemas = process_items ()
121
+ sub_properties , new_schemas = process_items ()
122
+ # Here's the check for the special case described above. If just one of the variants is
123
+ # an inline schema whose name matters, then we'll re-process them to simplify the naming.
124
+ # Unfortunately we do have to re-process them all; we can't just modify that one variant
125
+ # in place, because new_schemas already contains several references to its old name.
126
+ if (
127
+ not isinstance (sub_properties , PropertyError )
128
+ and len ([p for p in sub_properties if isinstance (p , HasNamedClass )]) == 1
129
+ ):
130
+ def _use_same_name_as_parent_for_that_one_variant (index : int ) -> str :
131
+ for i , p in enumerate (sub_properties ):
132
+ if i == index and isinstance (p , HasNamedClass ):
133
+ return name
134
+ return _add_index_suffix_to_variant_names (index )
135
+
136
+ sub_properties , new_schemas = process_items (_use_same_name_as_parent_for_that_one_variant )
137
+
105
138
if isinstance (sub_properties , PropertyError ):
106
139
return sub_properties , schemas
140
+ schemas = new_schemas
107
141
108
142
def flatten_union_properties (sub_properties : list [PropertyProtocol ]) -> list [PropertyProtocol ]:
109
143
flattened = []
0 commit comments