Skip to content

Commit

Permalink
python-experimental updates ancestor + adds descendant discriminator …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
spacether authored May 28, 2020
1 parent dc48cfd commit a017f3a
Show file tree
Hide file tree
Showing 32 changed files with 869 additions and 520 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,26 +83,22 @@ class OpenApiModel(object):
cls.discriminator is None or
cls in visited_composed_classes
):
# This openapi schema (cls) does not have a discriminator
# Or we have already visited this class before and are sure that we
# want to instantiate it this time.
# Use case 1: this openapi schema (cls) does not have a discriminator
# Use case 2: we have already visited this class before and are sure that we
# want to instantiate it this time. We have visited this class deserializing
# a payload with a discriminator. During that process we traveled through
# this class but did not make an instance of it. Now we are making an
# instance of a composed class which contains cls in it, so this time make an instance of cls.
#
# If we are making an instance of a composed schema Descendent
# which allOf includes Ancestor, then Ancestor contains
# a discriminator that includes Descendent.
# So if we make an instance of Descendent, we have to make an
# instance of Ancestor to hold the allOf properties.
# This code detects that use case and makes the instance of Ancestor
# For example:
# When making an instance of Dog, _visited_composed_classes = (Dog,)
# then we make an instance of Animal to include in dog._composed_instances
# so when we are here, cls is Animal
# cls.discriminator != None
# cls not in _visited_composed_classes
# new_cls = Dog
# but we know we know that we already have Dog
# because it is in visited_composed_classes
# so make Animal here
# Here's an example of use case 2: If Animal has a discriminator
# petType and we pass in "Dog", and the class Dog
# allOf includes Animal, we move through Animal
# once using the discriminator, and pick Dog.
# Then in the composed schema dog Dog, we will make an instance of the
# Animal class (because Dal has allOf: Animal) but this time we won't travel
# through Animal's discriminator because we passed in
# _visited_composed_classes = (Animal,)

return super(OpenApiModel, cls).__new__(cls)

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

if new_cls in visited_composed_classes:
# if we are coming from the chosen new_cls use cls instead
# if we are making an instance of a composed schema Descendent
# which allOf includes Ancestor, then Ancestor contains
# a discriminator that includes Descendent.
# So if we make an instance of Descendent, we have to make an
# instance of Ancestor to hold the allOf properties.
# This code detects that use case and makes the instance of Ancestor
# For example:
# When making an instance of Dog, _visited_composed_classes = (Dog,)
# then we make an instance of Animal to include in dog._composed_instances
# so when we are here, cls is Animal
# cls.discriminator != None
# cls not in _visited_composed_classes
# new_cls = Dog
# but we know we know that we already have Dog
# because it is in visited_composed_classes
# so make Animal here
return super(OpenApiModel, cls).__new__(cls)

oneof_anyof_child = new_cls in oneof_anyof_classes
Expand Down Expand Up @@ -766,18 +777,20 @@ def get_discriminator_class(model_class,
used_model_class = class_name_to_discr_class.get(discr_value)
if used_model_class is None:
# We didn't find a discriminated class in class_name_to_discr_class.
# So look in the ancestor or descendant discriminators
# The discriminator mapping may exist in a descendant (anyOf, oneOf)
# or ancestor (allOf).
# Ancestor example: in the "Dog -> Mammal -> Chordate -> Animal"
# Ancestor example: in the GrandparentAnimal -> ParentPet -> ChildCat
# hierarchy, the discriminator mappings may be defined at any level
# in the hieararchy.
# Descendant example: a schema is oneOf[Plant, Mammal], and each
# oneOf child may itself be an allOf with some arbitrary hierarchy,
# and a graph traversal is required to find the discriminator.
composed_children = model_class._composed_schemas.get('oneOf', ()) + \
model_class._composed_schemas.get('anyOf', ()) + \
model_class._composed_schemas.get('allOf', ())
for cls in composed_children:
# in the hierarchy.
# Descendant example: mammal -> whale/zebra/Pig -> BasquePig/DanishPig
# if we try to make BasquePig from mammal, we need to travel through
# the oneOf descendant discriminators to find BasquePig
descendant_classes = model_class._composed_schemas.get('oneOf', ()) + \
model_class._composed_schemas.get('anyOf', ())
ancestor_classes = model_class._composed_schemas.get('allOf', ())
possible_classes = descendant_classes + ancestor_classes
for cls in possible_classes:
# Check if the schema has inherited discriminators.
if hasattr(cls, 'discriminator') and cls.discriminator is not None:
used_model_class = get_discriminator_class(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1833,11 +1833,9 @@ components:
oneOf:
- $ref: '#/components/schemas/whale'
- $ref: '#/components/schemas/zebra'
- $ref: '#/components/schemas/Pig'
discriminator:
propertyName: className
mapping:
whale: '#/components/schemas/whale'
zebra: '#/components/schemas/zebra'
whale:
type: object
properties:
Expand All @@ -1862,6 +1860,26 @@ components:
type: string
required:
- className
Pig:
oneOf:
- $ref: '#/components/schemas/BasquePig'
- $ref: '#/components/schemas/DanishPig'
discriminator:
propertyName: className
BasquePig:
type: object
properties:
className:
type: string
required:
- className
DanishPig:
type: object
properties:
className:
type: string
required:
- className
gmFruit:
properties:
color:
Expand Down Expand Up @@ -1992,26 +2010,23 @@ components:
allOf:
- $ref: '#/components/schemas/ShapeInterface'
- $ref: '#/components/schemas/QuadrilateralInterface'
# The following hierarchy is used to test discriminators
# that require recursive lookups.
biology.Chordate:
GrandparentAnimal:
type: object
discriminator:
propertyName: className
required:
- className
- pet_type
properties:
className:
pet_type:
type: string
biology.Reptile:
allOf:
- $ref: '#/components/schemas/biology.Chordate'
biology.Mammal:
allOf:
- $ref: '#/components/schemas/biology.Chordate'
biology.Primate:
discriminator:
propertyName: pet_type
ParentPet:
type: object
allOf:
- $ref: '#/components/schemas/biology.Mammal'
biology.Hominid:
- $ref: '#/components/schemas/GrandparentAnimal'
ChildCat:
allOf:
- $ref: '#/components/schemas/biology.Primate'
- $ref: '#/components/schemas/ParentPet'
- type: object
properties:
name:
type: string
Original file line number Diff line number Diff line change
Expand Up @@ -155,26 +155,22 @@ def __new__(cls, *args, **kwargs):
cls.discriminator is None or
cls in visited_composed_classes
):
# This openapi schema (cls) does not have a discriminator
# Or we have already visited this class before and are sure that we
# want to instantiate it this time.
# Use case 1: this openapi schema (cls) does not have a discriminator
# Use case 2: we have already visited this class before and are sure that we
# want to instantiate it this time. We have visited this class deserializing
# a payload with a discriminator. During that process we traveled through
# this class but did not make an instance of it. Now we are making an
# instance of a composed class which contains cls in it, so this time make an instance of cls.
#
# If we are making an instance of a composed schema Descendent
# which allOf includes Ancestor, then Ancestor contains
# a discriminator that includes Descendent.
# So if we make an instance of Descendent, we have to make an
# instance of Ancestor to hold the allOf properties.
# This code detects that use case and makes the instance of Ancestor
# For example:
# When making an instance of Dog, _visited_composed_classes = (Dog,)
# then we make an instance of Animal to include in dog._composed_instances
# so when we are here, cls is Animal
# cls.discriminator != None
# cls not in _visited_composed_classes
# new_cls = Dog
# but we know we know that we already have Dog
# because it is in visited_composed_classes
# so make Animal here
# Here's an example of use case 2: If Animal has a discriminator
# petType and we pass in "Dog", and the class Dog
# allOf includes Animal, we move through Animal
# once using the discriminator, and pick Dog.
# Then in the composed schema dog Dog, we will make an instance of the
# Animal class (because Dal has allOf: Animal) but this time we won't travel
# through Animal's discriminator because we passed in
# _visited_composed_classes = (Animal,)

return super(OpenApiModel, cls).__new__(cls)

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

if new_cls in visited_composed_classes:
# if we are coming from the chosen new_cls use cls instead
# if we are making an instance of a composed schema Descendent
# which allOf includes Ancestor, then Ancestor contains
# a discriminator that includes Descendent.
# So if we make an instance of Descendent, we have to make an
# instance of Ancestor to hold the allOf properties.
# This code detects that use case and makes the instance of Ancestor
# For example:
# When making an instance of Dog, _visited_composed_classes = (Dog,)
# then we make an instance of Animal to include in dog._composed_instances
# so when we are here, cls is Animal
# cls.discriminator != None
# cls not in _visited_composed_classes
# new_cls = Dog
# but we know we know that we already have Dog
# because it is in visited_composed_classes
# so make Animal here
return super(OpenApiModel, cls).__new__(cls)

oneof_anyof_child = new_cls in oneof_anyof_classes
Expand Down Expand Up @@ -1033,18 +1044,20 @@ def get_discriminator_class(model_class,
used_model_class = class_name_to_discr_class.get(discr_value)
if used_model_class is None:
# We didn't find a discriminated class in class_name_to_discr_class.
# So look in the ancestor or descendant discriminators
# The discriminator mapping may exist in a descendant (anyOf, oneOf)
# or ancestor (allOf).
# Ancestor example: in the "Dog -> Mammal -> Chordate -> Animal"
# Ancestor example: in the GrandparentAnimal -> ParentPet -> ChildCat
# hierarchy, the discriminator mappings may be defined at any level
# in the hieararchy.
# Descendant example: a schema is oneOf[Plant, Mammal], and each
# oneOf child may itself be an allOf with some arbitrary hierarchy,
# and a graph traversal is required to find the discriminator.
composed_children = model_class._composed_schemas.get('oneOf', ()) + \
model_class._composed_schemas.get('anyOf', ()) + \
model_class._composed_schemas.get('allOf', ())
for cls in composed_children:
# in the hierarchy.
# Descendant example: mammal -> whale/zebra/Pig -> BasquePig/DanishPig
# if we try to make BasquePig from mammal, we need to travel through
# the oneOf descendant discriminators to find BasquePig
descendant_classes = model_class._composed_schemas.get('oneOf', ()) + \
model_class._composed_schemas.get('anyOf', ())
ancestor_classes = model_class._composed_schemas.get('allOf', ())
possible_classes = descendant_classes + ancestor_classes
for cls in possible_classes:
# Check if the schema has inherited discriminators.
if hasattr(cls, 'discriminator') and cls.discriminator is not None:
used_model_class = get_discriminator_class(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,17 @@ docs/ArrayOfNumberOnly.md
docs/ArrayTest.md
docs/Banana.md
docs/BananaReq.md
docs/BiologyChordate.md
docs/BiologyHominid.md
docs/BiologyMammal.md
docs/BiologyPrimate.md
docs/BiologyReptile.md
docs/BasquePig.md
docs/Capitalization.md
docs/Cat.md
docs/CatAllOf.md
docs/Category.md
docs/ChildCat.md
docs/ChildCatAllOf.md
docs/ClassModel.md
docs/Client.md
docs/ComplexQuadrilateral.md
docs/DanishPig.md
docs/DefaultApi.md
docs/Dog.md
docs/DogAllOf.md
Expand All @@ -43,6 +42,7 @@ docs/FormatTest.md
docs/Fruit.md
docs/FruitReq.md
docs/GmFruit.md
docs/GrandparentAnimal.md
docs/HasOnlyReadOnly.md
docs/HealthCheckResult.md
docs/InlineObject.md
Expand All @@ -69,8 +69,10 @@ docs/OuterEnum.md
docs/OuterEnumDefaultValue.md
docs/OuterEnumInteger.md
docs/OuterEnumIntegerDefaultValue.md
docs/ParentPet.md
docs/Pet.md
docs/PetApi.md
docs/Pig.md
docs/Quadrilateral.md
docs/QuadrilateralInterface.md
docs/ReadOnlyFirst.md
Expand Down Expand Up @@ -115,18 +117,17 @@ petstore_api/models/array_of_number_only.py
petstore_api/models/array_test.py
petstore_api/models/banana.py
petstore_api/models/banana_req.py
petstore_api/models/biology_chordate.py
petstore_api/models/biology_hominid.py
petstore_api/models/biology_mammal.py
petstore_api/models/biology_primate.py
petstore_api/models/biology_reptile.py
petstore_api/models/basque_pig.py
petstore_api/models/capitalization.py
petstore_api/models/cat.py
petstore_api/models/cat_all_of.py
petstore_api/models/category.py
petstore_api/models/child_cat.py
petstore_api/models/child_cat_all_of.py
petstore_api/models/class_model.py
petstore_api/models/client.py
petstore_api/models/complex_quadrilateral.py
petstore_api/models/danish_pig.py
petstore_api/models/dog.py
petstore_api/models/dog_all_of.py
petstore_api/models/drawing.py
Expand All @@ -141,6 +142,7 @@ petstore_api/models/format_test.py
petstore_api/models/fruit.py
petstore_api/models/fruit_req.py
petstore_api/models/gm_fruit.py
petstore_api/models/grandparent_animal.py
petstore_api/models/has_only_read_only.py
petstore_api/models/health_check_result.py
petstore_api/models/inline_object.py
Expand All @@ -167,7 +169,9 @@ petstore_api/models/outer_enum.py
petstore_api/models/outer_enum_default_value.py
petstore_api/models/outer_enum_integer.py
petstore_api/models/outer_enum_integer_default_value.py
petstore_api/models/parent_pet.py
petstore_api/models/pet.py
petstore_api/models/pig.py
petstore_api/models/quadrilateral.py
petstore_api/models/quadrilateral_interface.py
petstore_api/models/read_only_first.py
Expand Down
12 changes: 7 additions & 5 deletions samples/openapi3/client/petstore/python-experimental/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,17 @@ Class | Method | HTTP request | Description
- [array_test.ArrayTest](docs/ArrayTest.md)
- [banana.Banana](docs/Banana.md)
- [banana_req.BananaReq](docs/BananaReq.md)
- [biology_chordate.BiologyChordate](docs/BiologyChordate.md)
- [biology_hominid.BiologyHominid](docs/BiologyHominid.md)
- [biology_mammal.BiologyMammal](docs/BiologyMammal.md)
- [biology_primate.BiologyPrimate](docs/BiologyPrimate.md)
- [biology_reptile.BiologyReptile](docs/BiologyReptile.md)
- [basque_pig.BasquePig](docs/BasquePig.md)
- [capitalization.Capitalization](docs/Capitalization.md)
- [cat.Cat](docs/Cat.md)
- [cat_all_of.CatAllOf](docs/CatAllOf.md)
- [category.Category](docs/Category.md)
- [child_cat.ChildCat](docs/ChildCat.md)
- [child_cat_all_of.ChildCatAllOf](docs/ChildCatAllOf.md)
- [class_model.ClassModel](docs/ClassModel.md)
- [client.Client](docs/Client.md)
- [complex_quadrilateral.ComplexQuadrilateral](docs/ComplexQuadrilateral.md)
- [danish_pig.DanishPig](docs/DanishPig.md)
- [dog.Dog](docs/Dog.md)
- [dog_all_of.DogAllOf](docs/DogAllOf.md)
- [drawing.Drawing](docs/Drawing.md)
Expand All @@ -159,6 +158,7 @@ Class | Method | HTTP request | Description
- [fruit.Fruit](docs/Fruit.md)
- [fruit_req.FruitReq](docs/FruitReq.md)
- [gm_fruit.GmFruit](docs/GmFruit.md)
- [grandparent_animal.GrandparentAnimal](docs/GrandparentAnimal.md)
- [has_only_read_only.HasOnlyReadOnly](docs/HasOnlyReadOnly.md)
- [health_check_result.HealthCheckResult](docs/HealthCheckResult.md)
- [inline_object.InlineObject](docs/InlineObject.md)
Expand All @@ -185,7 +185,9 @@ Class | Method | HTTP request | Description
- [outer_enum_default_value.OuterEnumDefaultValue](docs/OuterEnumDefaultValue.md)
- [outer_enum_integer.OuterEnumInteger](docs/OuterEnumInteger.md)
- [outer_enum_integer_default_value.OuterEnumIntegerDefaultValue](docs/OuterEnumIntegerDefaultValue.md)
- [parent_pet.ParentPet](docs/ParentPet.md)
- [pet.Pet](docs/Pet.md)
- [pig.Pig](docs/Pig.md)
- [quadrilateral.Quadrilateral](docs/Quadrilateral.md)
- [quadrilateral_interface.QuadrilateralInterface](docs/QuadrilateralInterface.md)
- [read_only_first.ReadOnlyFirst](docs/ReadOnlyFirst.md)
Expand Down
Loading

0 comments on commit a017f3a

Please sign in to comment.