@@ -38,6 +38,8 @@ def test_type_hints(self, MyModel, Unset):
3838
3939@with_generated_client_fixture (
4040"""
41+ # Various use cases for oneOf
42+
4143components:
4244 schemas:
4345 ThingA:
@@ -152,6 +154,203 @@ def test_type_hints(self, ModelWithUnion, ModelWithRequiredUnion, ModelWithUnion
152154 )
153155
154156
157+ @with_generated_client_fixture (
158+ """
159+ # Various use cases for a oneOf where one of the variants is null, since these are handled
160+ # a bit differently in the generator
161+
162+ components:
163+ schemas:
164+ MyEnum:
165+ type: string
166+ enum: ["a", "b"]
167+ MyObject:
168+ type: object
169+ properties:
170+ name:
171+ type: string
172+ MyModel:
173+ properties:
174+ nullableEnumProp:
175+ oneOf:
176+ - {"$ref": "#/components/schemas/MyEnum"}
177+ - type: "null"
178+ nullableObjectProp:
179+ oneOf:
180+ - {"$ref": "#/components/schemas/MyObject"}
181+ - type: "null"
182+ inlineNullableObject:
183+ # Note, the generated class for this should be called "MyModelInlineNullableObject",
184+ # since the generator's rule for inline schemas that require their own class is to
185+ # concatenate the property name to the parent schema name.
186+ oneOf:
187+ - type: object
188+ properties:
189+ name:
190+ type: string
191+ - type: "null"
192+ """ )
193+ @with_generated_code_imports (
194+ ".models.MyEnum" ,
195+ ".models.MyObject" ,
196+ ".models.MyModel" ,
197+ ".models.MyModelInlineNullableObject" ,
198+ ".types.Unset" ,
199+ )
200+ class TestUnionsWithNull :
201+ def test_nullable_enum_prop (self , MyModel , MyEnum ):
202+ assert_model_decode_encode (MyModel , {"nullableEnumProp" : "b" }, MyModel (nullable_enum_prop = MyEnum .B ))
203+ assert_model_decode_encode (MyModel , {"nullableEnumProp" : None }, MyModel (nullable_enum_prop = None ))
204+
205+ def test_nullable_object_prop (self , MyModel , MyObject ):
206+ assert_model_decode_encode ( MyModel , {"nullableObjectProp" : None }, MyModel (nullable_object_prop = None ))
207+ assert_model_decode_encode ( MyModel , {"nullableObjectProp" : None }, MyModel (nullable_object_prop = None ))
208+
209+ def test_nullable_object_prop_with_inline_schema (self , MyModel , MyModelInlineNullableObject ):
210+ assert_model_decode_encode (
211+ MyModel ,
212+ {"inlineNullableObject" : {"name" : "a" }},
213+ MyModel (inline_nullable_object = MyModelInlineNullableObject (name = "a" )),
214+ )
215+ assert_model_decode_encode ( MyModel , {"inlineNullableObject" : None }, MyModel (inline_nullable_object = None ))
216+
217+ def test_type_hints (self , MyModel , MyEnum , Unset ):
218+ assert_model_property_type_hint (MyModel , "nullable_enum_prop" , Union [MyEnum , None , Unset ])
219+ assert_model_property_type_hint (MyModel , "nullable_object_prop" , Union [ForwardRef ("MyObject" ), None , Unset ])
220+ assert_model_property_type_hint (
221+ MyModel ,
222+ "inline_nullable_object" ,
223+ Union [ForwardRef ("MyModelInlineNullableObject" ), None , Unset ],
224+ )
225+
226+
227+ @with_generated_client_fixture (
228+ """
229+ # Tests for combining the OpenAPI 3.0 "nullable" attribute with an enum
230+
231+ openapi: 3.0.0
232+
233+ components:
234+ schemas:
235+ MyEnum:
236+ type: string
237+ enum: ["a", "b"]
238+ MyEnumIncludingNull:
239+ type: string
240+ nullable: true
241+ enum: ["a", "b", null]
242+ MyModel:
243+ properties:
244+ nullableEnumProp:
245+ allOf:
246+ - {"$ref": "#/components/schemas/MyEnum"}
247+ nullable: true
248+ enumIncludingNullProp: {"$ref": "#/components/schemas/MyEnumIncludingNull"}
249+ """ )
250+ @with_generated_code_imports (
251+ ".models.MyEnum" ,
252+ ".models.MyEnumIncludingNull" ,
253+ ".models.MyModel" ,
254+ ".types.Unset" ,
255+ )
256+ class TestNullableEnumsInOpenAPI30 :
257+ def test_nullable_enum_prop (self , MyModel , MyEnum , MyEnumIncludingNull ):
258+ assert_model_decode_encode (MyModel , {"nullableEnumProp" : "b" }, MyModel (nullable_enum_prop = MyEnum .B ))
259+ assert_model_decode_encode (MyModel , {"nullableEnumProp" : None }, MyModel (nullable_enum_prop = None ))
260+ assert_model_decode_encode (
261+ MyModel ,
262+ {"enumIncludingNullProp" : "a" },
263+ MyModel (enum_including_null_prop = MyEnumIncludingNull .A ),
264+ )
265+ assert_model_decode_encode ( MyModel , {"enumIncludingNullProp" : None }, MyModel (enum_including_null_prop = None ))
266+
267+ def test_type_hints (self , MyModel , MyEnum , MyEnumIncludingNull , Unset ):
268+ assert_model_property_type_hint (MyModel , "nullable_enum_prop" , Union [MyEnum , None , Unset ])
269+ assert_model_property_type_hint (MyModel , "enum_including_null_prop" , Union [MyEnumIncludingNull , None , Unset ])
270+
271+
272+ @with_generated_client_fixture (
273+ """
274+ # Test use cases where there's a union of types *and* an explicit list of multiple "type:"s
275+
276+ components:
277+ schemas:
278+ MyStringEnum:
279+ type: string
280+ enum: ["a", "b"]
281+ MyIntEnum:
282+ type: integer
283+ enum: [1, 2]
284+ MyEnumIncludingNull:
285+ type: ["string", "null"]
286+ enum: ["a", "b", null]
287+ MyObject:
288+ type: object
289+ properties:
290+ name:
291+ type: string
292+ MyModel:
293+ properties:
294+ enumsWithListOfTypesProp:
295+ type: ["string", "integer"]
296+ oneOf:
297+ - {"$ref": "#/components/schemas/MyStringEnum"}
298+ - {"$ref": "#/components/schemas/MyIntEnum"}
299+ enumIncludingNullProp: {"$ref": "#/components/schemas/MyEnumIncludingNull"}
300+ nullableObjectWithListOfTypesProp:
301+ type: ["string", "object"]
302+ oneOf:
303+ - {"$ref": "#/components/schemas/MyObject"}
304+ - type: "null"
305+ """ )
306+ @with_generated_code_imports (
307+ ".models.MyStringEnum" ,
308+ ".models.MyIntEnum" ,
309+ ".models.MyEnumIncludingNull" ,
310+ ".models.MyObject" ,
311+ ".models.MyModel" ,
312+ ".types.Unset" ,
313+ )
314+ class TestUnionsWithExplicitListOfTypes :
315+ # This covers some use cases where combining "oneOf" with "type: [list of types]" (which is fine
316+ # to do in OpenAPI) used to generate enum/model classes incorrectly.
317+
318+ def test_union_of_enums (self , MyModel , MyStringEnum , MyIntEnum ):
319+ assert_model_decode_encode (
320+ MyModel ,
321+ {"enumsWithListOfTypesProp" : "b" },
322+ MyModel (enums_with_list_of_types_prop = MyStringEnum .B ),
323+ )
324+ assert_model_decode_encode (
325+ MyModel ,
326+ {"enumsWithListOfTypesProp" : 2 },
327+ MyModel (enums_with_list_of_types_prop = MyIntEnum .VALUE_2 ),
328+ )
329+
330+ def test_union_of_enum_with_null (self , MyModel , MyEnumIncludingNull ):
331+ assert_model_decode_encode (
332+ MyModel ,
333+ {"enumIncludingNullProp" : "b" },
334+ MyModel (enum_including_null_prop = MyEnumIncludingNull .B ),
335+ )
336+ assert_model_decode_encode (
337+ MyModel ,
338+ {"enumIncludingNullProp" : None },
339+ MyModel (enum_including_null_prop = None ),
340+ )
341+
342+ def test_nullable_object_with_list_of_types (self , MyModel , MyObject ):
343+ assert_model_decode_encode (
344+ MyModel ,
345+ {"nullableObjectWithListOfTypesProp" : {"name" : "a" }},
346+ MyModel (nullable_object_with_list_of_types_prop = MyObject (name = "a" )),
347+ )
348+ assert_model_decode_encode (
349+ MyModel ,
350+ {"nullableObjectWithListOfTypesProp" : None },
351+ MyModel (nullable_object_with_list_of_types_prop = None ),
352+ )
353+
155354@with_generated_client_fixture (
156355"""
157356components:
0 commit comments