Skip to content

Commit a017f3a

Browse files
authored
python-experimental updates ancestor + adds descendant discriminator tests (#6417)
* Updates comments in new method * Adds missing line in model_utils.py * Removes biology examples, adds ParentPet ancestor example + test, adds Pig schemas * Updates comment and var names in get_discriminator_class, adds testMammal test * Updates comment
1 parent dc48cfd commit a017f3a

File tree

32 files changed

+869
-520
lines changed

32 files changed

+869
-520
lines changed

modules/openapi-generator/src/main/resources/python/python-experimental/model_utils.mustache

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -83,26 +83,22 @@ class OpenApiModel(object):
8383
cls.discriminator is None or
8484
cls in visited_composed_classes
8585
):
86-
# This openapi schema (cls) does not have a discriminator
87-
# Or we have already visited this class before and are sure that we
88-
# want to instantiate it this time.
86+
# Use case 1: this openapi schema (cls) does not have a discriminator
87+
# Use case 2: we have already visited this class before and are sure that we
88+
# want to instantiate it this time. We have visited this class deserializing
89+
# a payload with a discriminator. During that process we traveled through
90+
# this class but did not make an instance of it. Now we are making an
91+
# instance of a composed class which contains cls in it, so this time make an instance of cls.
8992
#
90-
# If we are making an instance of a composed schema Descendent
91-
# which allOf includes Ancestor, then Ancestor contains
92-
# a discriminator that includes Descendent.
93-
# So if we make an instance of Descendent, we have to make an
94-
# instance of Ancestor to hold the allOf properties.
95-
# This code detects that use case and makes the instance of Ancestor
96-
# For example:
97-
# When making an instance of Dog, _visited_composed_classes = (Dog,)
98-
# then we make an instance of Animal to include in dog._composed_instances
99-
# so when we are here, cls is Animal
100-
# cls.discriminator != None
101-
# cls not in _visited_composed_classes
102-
# new_cls = Dog
103-
# but we know we know that we already have Dog
104-
# because it is in visited_composed_classes
105-
# so make Animal here
93+
# Here's an example of use case 2: If Animal has a discriminator
94+
# petType and we pass in "Dog", and the class Dog
95+
# allOf includes Animal, we move through Animal
96+
# once using the discriminator, and pick Dog.
97+
# Then in the composed schema dog Dog, we will make an instance of the
98+
# Animal class (because Dal has allOf: Animal) but this time we won't travel
99+
# through Animal's discriminator because we passed in
100+
# _visited_composed_classes = (Animal,)
101+
106102
return super(OpenApiModel, cls).__new__(cls)
107103

108104
# Get the name and value of the discriminator property.
@@ -141,7 +137,22 @@ class OpenApiModel(object):
141137
)
142138

143139
if new_cls in visited_composed_classes:
144-
# if we are coming from the chosen new_cls use cls instead
140+
# if we are making an instance of a composed schema Descendent
141+
# which allOf includes Ancestor, then Ancestor contains
142+
# a discriminator that includes Descendent.
143+
# So if we make an instance of Descendent, we have to make an
144+
# instance of Ancestor to hold the allOf properties.
145+
# This code detects that use case and makes the instance of Ancestor
146+
# For example:
147+
# When making an instance of Dog, _visited_composed_classes = (Dog,)
148+
# then we make an instance of Animal to include in dog._composed_instances
149+
# so when we are here, cls is Animal
150+
# cls.discriminator != None
151+
# cls not in _visited_composed_classes
152+
# new_cls = Dog
153+
# but we know we know that we already have Dog
154+
# because it is in visited_composed_classes
155+
# so make Animal here
145156
return super(OpenApiModel, cls).__new__(cls)
146157

147158
oneof_anyof_child = new_cls in oneof_anyof_classes
@@ -766,18 +777,20 @@ def get_discriminator_class(model_class,
766777
used_model_class = class_name_to_discr_class.get(discr_value)
767778
if used_model_class is None:
768779
# We didn't find a discriminated class in class_name_to_discr_class.
780+
# So look in the ancestor or descendant discriminators
769781
# The discriminator mapping may exist in a descendant (anyOf, oneOf)
770782
# or ancestor (allOf).
771-
# Ancestor example: in the "Dog -> Mammal -> Chordate -> Animal"
783+
# Ancestor example: in the GrandparentAnimal -> ParentPet -> ChildCat
772784
# hierarchy, the discriminator mappings may be defined at any level
773-
# in the hieararchy.
774-
# Descendant example: a schema is oneOf[Plant, Mammal], and each
775-
# oneOf child may itself be an allOf with some arbitrary hierarchy,
776-
# and a graph traversal is required to find the discriminator.
777-
composed_children = model_class._composed_schemas.get('oneOf', ()) + \
778-
model_class._composed_schemas.get('anyOf', ()) + \
779-
model_class._composed_schemas.get('allOf', ())
780-
for cls in composed_children:
785+
# in the hierarchy.
786+
# Descendant example: mammal -> whale/zebra/Pig -> BasquePig/DanishPig
787+
# if we try to make BasquePig from mammal, we need to travel through
788+
# the oneOf descendant discriminators to find BasquePig
789+
descendant_classes = model_class._composed_schemas.get('oneOf', ()) + \
790+
model_class._composed_schemas.get('anyOf', ())
791+
ancestor_classes = model_class._composed_schemas.get('allOf', ())
792+
possible_classes = descendant_classes + ancestor_classes
793+
for cls in possible_classes:
781794
# Check if the schema has inherited discriminators.
782795
if hasattr(cls, 'discriminator') and cls.discriminator is not None:
783796
used_model_class = get_discriminator_class(

modules/openapi-generator/src/test/resources/3_0/python-experimental/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,11 +1833,9 @@ components:
18331833
oneOf:
18341834
- $ref: '#/components/schemas/whale'
18351835
- $ref: '#/components/schemas/zebra'
1836+
- $ref: '#/components/schemas/Pig'
18361837
discriminator:
18371838
propertyName: className
1838-
mapping:
1839-
whale: '#/components/schemas/whale'
1840-
zebra: '#/components/schemas/zebra'
18411839
whale:
18421840
type: object
18431841
properties:
@@ -1862,6 +1860,26 @@ components:
18621860
type: string
18631861
required:
18641862
- className
1863+
Pig:
1864+
oneOf:
1865+
- $ref: '#/components/schemas/BasquePig'
1866+
- $ref: '#/components/schemas/DanishPig'
1867+
discriminator:
1868+
propertyName: className
1869+
BasquePig:
1870+
type: object
1871+
properties:
1872+
className:
1873+
type: string
1874+
required:
1875+
- className
1876+
DanishPig:
1877+
type: object
1878+
properties:
1879+
className:
1880+
type: string
1881+
required:
1882+
- className
18651883
gmFruit:
18661884
properties:
18671885
color:
@@ -1992,26 +2010,23 @@ components:
19922010
allOf:
19932011
- $ref: '#/components/schemas/ShapeInterface'
19942012
- $ref: '#/components/schemas/QuadrilateralInterface'
1995-
# The following hierarchy is used to test discriminators
1996-
# that require recursive lookups.
1997-
biology.Chordate:
2013+
GrandparentAnimal:
19982014
type: object
1999-
discriminator:
2000-
propertyName: className
20012015
required:
2002-
- className
2016+
- pet_type
20032017
properties:
2004-
className:
2018+
pet_type:
20052019
type: string
2006-
biology.Reptile:
2007-
allOf:
2008-
- $ref: '#/components/schemas/biology.Chordate'
2009-
biology.Mammal:
2010-
allOf:
2011-
- $ref: '#/components/schemas/biology.Chordate'
2012-
biology.Primate:
2020+
discriminator:
2021+
propertyName: pet_type
2022+
ParentPet:
2023+
type: object
20132024
allOf:
2014-
- $ref: '#/components/schemas/biology.Mammal'
2015-
biology.Hominid:
2025+
- $ref: '#/components/schemas/GrandparentAnimal'
2026+
ChildCat:
20162027
allOf:
2017-
- $ref: '#/components/schemas/biology.Primate'
2028+
- $ref: '#/components/schemas/ParentPet'
2029+
- type: object
2030+
properties:
2031+
name:
2032+
type: string

samples/client/petstore/python-experimental/petstore_api/model_utils.py

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -155,26 +155,22 @@ def __new__(cls, *args, **kwargs):
155155
cls.discriminator is None or
156156
cls in visited_composed_classes
157157
):
158-
# This openapi schema (cls) does not have a discriminator
159-
# Or we have already visited this class before and are sure that we
160-
# want to instantiate it this time.
158+
# Use case 1: this openapi schema (cls) does not have a discriminator
159+
# Use case 2: we have already visited this class before and are sure that we
160+
# want to instantiate it this time. We have visited this class deserializing
161+
# a payload with a discriminator. During that process we traveled through
162+
# this class but did not make an instance of it. Now we are making an
163+
# instance of a composed class which contains cls in it, so this time make an instance of cls.
161164
#
162-
# If we are making an instance of a composed schema Descendent
163-
# which allOf includes Ancestor, then Ancestor contains
164-
# a discriminator that includes Descendent.
165-
# So if we make an instance of Descendent, we have to make an
166-
# instance of Ancestor to hold the allOf properties.
167-
# This code detects that use case and makes the instance of Ancestor
168-
# For example:
169-
# When making an instance of Dog, _visited_composed_classes = (Dog,)
170-
# then we make an instance of Animal to include in dog._composed_instances
171-
# so when we are here, cls is Animal
172-
# cls.discriminator != None
173-
# cls not in _visited_composed_classes
174-
# new_cls = Dog
175-
# but we know we know that we already have Dog
176-
# because it is in visited_composed_classes
177-
# so make Animal here
165+
# Here's an example of use case 2: If Animal has a discriminator
166+
# petType and we pass in "Dog", and the class Dog
167+
# allOf includes Animal, we move through Animal
168+
# once using the discriminator, and pick Dog.
169+
# Then in the composed schema dog Dog, we will make an instance of the
170+
# Animal class (because Dal has allOf: Animal) but this time we won't travel
171+
# through Animal's discriminator because we passed in
172+
# _visited_composed_classes = (Animal,)
173+
178174
return super(OpenApiModel, cls).__new__(cls)
179175

180176
# Get the name and value of the discriminator property.
@@ -213,7 +209,22 @@ def __new__(cls, *args, **kwargs):
213209
)
214210

215211
if new_cls in visited_composed_classes:
216-
# if we are coming from the chosen new_cls use cls instead
212+
# if we are making an instance of a composed schema Descendent
213+
# which allOf includes Ancestor, then Ancestor contains
214+
# a discriminator that includes Descendent.
215+
# So if we make an instance of Descendent, we have to make an
216+
# instance of Ancestor to hold the allOf properties.
217+
# This code detects that use case and makes the instance of Ancestor
218+
# For example:
219+
# When making an instance of Dog, _visited_composed_classes = (Dog,)
220+
# then we make an instance of Animal to include in dog._composed_instances
221+
# so when we are here, cls is Animal
222+
# cls.discriminator != None
223+
# cls not in _visited_composed_classes
224+
# new_cls = Dog
225+
# but we know we know that we already have Dog
226+
# because it is in visited_composed_classes
227+
# so make Animal here
217228
return super(OpenApiModel, cls).__new__(cls)
218229

219230
oneof_anyof_child = new_cls in oneof_anyof_classes
@@ -1033,18 +1044,20 @@ def get_discriminator_class(model_class,
10331044
used_model_class = class_name_to_discr_class.get(discr_value)
10341045
if used_model_class is None:
10351046
# We didn't find a discriminated class in class_name_to_discr_class.
1047+
# So look in the ancestor or descendant discriminators
10361048
# The discriminator mapping may exist in a descendant (anyOf, oneOf)
10371049
# or ancestor (allOf).
1038-
# Ancestor example: in the "Dog -> Mammal -> Chordate -> Animal"
1050+
# Ancestor example: in the GrandparentAnimal -> ParentPet -> ChildCat
10391051
# hierarchy, the discriminator mappings may be defined at any level
1040-
# in the hieararchy.
1041-
# Descendant example: a schema is oneOf[Plant, Mammal], and each
1042-
# oneOf child may itself be an allOf with some arbitrary hierarchy,
1043-
# and a graph traversal is required to find the discriminator.
1044-
composed_children = model_class._composed_schemas.get('oneOf', ()) + \
1045-
model_class._composed_schemas.get('anyOf', ()) + \
1046-
model_class._composed_schemas.get('allOf', ())
1047-
for cls in composed_children:
1052+
# in the hierarchy.
1053+
# Descendant example: mammal -> whale/zebra/Pig -> BasquePig/DanishPig
1054+
# if we try to make BasquePig from mammal, we need to travel through
1055+
# the oneOf descendant discriminators to find BasquePig
1056+
descendant_classes = model_class._composed_schemas.get('oneOf', ()) + \
1057+
model_class._composed_schemas.get('anyOf', ())
1058+
ancestor_classes = model_class._composed_schemas.get('allOf', ())
1059+
possible_classes = descendant_classes + ancestor_classes
1060+
for cls in possible_classes:
10481061
# Check if the schema has inherited discriminators.
10491062
if hasattr(cls, 'discriminator') and cls.discriminator is not None:
10501063
used_model_class = get_discriminator_class(

samples/openapi3/client/petstore/python-experimental/.openapi-generator/FILES

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,17 @@ docs/ArrayOfNumberOnly.md
1414
docs/ArrayTest.md
1515
docs/Banana.md
1616
docs/BananaReq.md
17-
docs/BiologyChordate.md
18-
docs/BiologyHominid.md
19-
docs/BiologyMammal.md
20-
docs/BiologyPrimate.md
21-
docs/BiologyReptile.md
17+
docs/BasquePig.md
2218
docs/Capitalization.md
2319
docs/Cat.md
2420
docs/CatAllOf.md
2521
docs/Category.md
22+
docs/ChildCat.md
23+
docs/ChildCatAllOf.md
2624
docs/ClassModel.md
2725
docs/Client.md
2826
docs/ComplexQuadrilateral.md
27+
docs/DanishPig.md
2928
docs/DefaultApi.md
3029
docs/Dog.md
3130
docs/DogAllOf.md
@@ -43,6 +42,7 @@ docs/FormatTest.md
4342
docs/Fruit.md
4443
docs/FruitReq.md
4544
docs/GmFruit.md
45+
docs/GrandparentAnimal.md
4646
docs/HasOnlyReadOnly.md
4747
docs/HealthCheckResult.md
4848
docs/InlineObject.md
@@ -69,8 +69,10 @@ docs/OuterEnum.md
6969
docs/OuterEnumDefaultValue.md
7070
docs/OuterEnumInteger.md
7171
docs/OuterEnumIntegerDefaultValue.md
72+
docs/ParentPet.md
7273
docs/Pet.md
7374
docs/PetApi.md
75+
docs/Pig.md
7476
docs/Quadrilateral.md
7577
docs/QuadrilateralInterface.md
7678
docs/ReadOnlyFirst.md
@@ -115,18 +117,17 @@ petstore_api/models/array_of_number_only.py
115117
petstore_api/models/array_test.py
116118
petstore_api/models/banana.py
117119
petstore_api/models/banana_req.py
118-
petstore_api/models/biology_chordate.py
119-
petstore_api/models/biology_hominid.py
120-
petstore_api/models/biology_mammal.py
121-
petstore_api/models/biology_primate.py
122-
petstore_api/models/biology_reptile.py
120+
petstore_api/models/basque_pig.py
123121
petstore_api/models/capitalization.py
124122
petstore_api/models/cat.py
125123
petstore_api/models/cat_all_of.py
126124
petstore_api/models/category.py
125+
petstore_api/models/child_cat.py
126+
petstore_api/models/child_cat_all_of.py
127127
petstore_api/models/class_model.py
128128
petstore_api/models/client.py
129129
petstore_api/models/complex_quadrilateral.py
130+
petstore_api/models/danish_pig.py
130131
petstore_api/models/dog.py
131132
petstore_api/models/dog_all_of.py
132133
petstore_api/models/drawing.py
@@ -141,6 +142,7 @@ petstore_api/models/format_test.py
141142
petstore_api/models/fruit.py
142143
petstore_api/models/fruit_req.py
143144
petstore_api/models/gm_fruit.py
145+
petstore_api/models/grandparent_animal.py
144146
petstore_api/models/has_only_read_only.py
145147
petstore_api/models/health_check_result.py
146148
petstore_api/models/inline_object.py
@@ -167,7 +169,9 @@ petstore_api/models/outer_enum.py
167169
petstore_api/models/outer_enum_default_value.py
168170
petstore_api/models/outer_enum_integer.py
169171
petstore_api/models/outer_enum_integer_default_value.py
172+
petstore_api/models/parent_pet.py
170173
petstore_api/models/pet.py
174+
petstore_api/models/pig.py
171175
petstore_api/models/quadrilateral.py
172176
petstore_api/models/quadrilateral_interface.py
173177
petstore_api/models/read_only_first.py

samples/openapi3/client/petstore/python-experimental/README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,18 +133,17 @@ Class | Method | HTTP request | Description
133133
- [array_test.ArrayTest](docs/ArrayTest.md)
134134
- [banana.Banana](docs/Banana.md)
135135
- [banana_req.BananaReq](docs/BananaReq.md)
136-
- [biology_chordate.BiologyChordate](docs/BiologyChordate.md)
137-
- [biology_hominid.BiologyHominid](docs/BiologyHominid.md)
138-
- [biology_mammal.BiologyMammal](docs/BiologyMammal.md)
139-
- [biology_primate.BiologyPrimate](docs/BiologyPrimate.md)
140-
- [biology_reptile.BiologyReptile](docs/BiologyReptile.md)
136+
- [basque_pig.BasquePig](docs/BasquePig.md)
141137
- [capitalization.Capitalization](docs/Capitalization.md)
142138
- [cat.Cat](docs/Cat.md)
143139
- [cat_all_of.CatAllOf](docs/CatAllOf.md)
144140
- [category.Category](docs/Category.md)
141+
- [child_cat.ChildCat](docs/ChildCat.md)
142+
- [child_cat_all_of.ChildCatAllOf](docs/ChildCatAllOf.md)
145143
- [class_model.ClassModel](docs/ClassModel.md)
146144
- [client.Client](docs/Client.md)
147145
- [complex_quadrilateral.ComplexQuadrilateral](docs/ComplexQuadrilateral.md)
146+
- [danish_pig.DanishPig](docs/DanishPig.md)
148147
- [dog.Dog](docs/Dog.md)
149148
- [dog_all_of.DogAllOf](docs/DogAllOf.md)
150149
- [drawing.Drawing](docs/Drawing.md)
@@ -159,6 +158,7 @@ Class | Method | HTTP request | Description
159158
- [fruit.Fruit](docs/Fruit.md)
160159
- [fruit_req.FruitReq](docs/FruitReq.md)
161160
- [gm_fruit.GmFruit](docs/GmFruit.md)
161+
- [grandparent_animal.GrandparentAnimal](docs/GrandparentAnimal.md)
162162
- [has_only_read_only.HasOnlyReadOnly](docs/HasOnlyReadOnly.md)
163163
- [health_check_result.HealthCheckResult](docs/HealthCheckResult.md)
164164
- [inline_object.InlineObject](docs/InlineObject.md)
@@ -185,7 +185,9 @@ Class | Method | HTTP request | Description
185185
- [outer_enum_default_value.OuterEnumDefaultValue](docs/OuterEnumDefaultValue.md)
186186
- [outer_enum_integer.OuterEnumInteger](docs/OuterEnumInteger.md)
187187
- [outer_enum_integer_default_value.OuterEnumIntegerDefaultValue](docs/OuterEnumIntegerDefaultValue.md)
188+
- [parent_pet.ParentPet](docs/ParentPet.md)
188189
- [pet.Pet](docs/Pet.md)
190+
- [pig.Pig](docs/Pig.md)
189191
- [quadrilateral.Quadrilateral](docs/Quadrilateral.md)
190192
- [quadrilateral_interface.QuadrilateralInterface](docs/QuadrilateralInterface.md)
191193
- [read_only_first.ReadOnlyFirst](docs/ReadOnlyFirst.md)

0 commit comments

Comments
 (0)