-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[codegen][Python-experimental] Discriminator NPE fix, handle 'null' type, #4906 enhancements #5809
Conversation
…s schema oneof samples
…s schema oneof samples
@sebastien-rosset your 3rd point Fix NPE when at least one extension is defined but not x-discriminator-value is already addressed in my PR which will land before this one, right? |
This unit test uses that code path. |
How about we put that in the ModelComposed class docstring?
|
I have added this as is, but it's not clear exactly where you want this. Please submit a pull request if you want to change what I have done. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for adding those sample tests.
The remaining ask here is to add tests that require deserialization through the discriminators in a descendent (oneOf/anyOf schemas) or in an ancestor (allOf ancestor of self composed schema)
Also, ideally we will use existing sample schemas rather than adding additional
ones, because that helps keep our sample spec as small as possible.
Examining the added tests we are testing the following cases:
- using a discriminator map defined in a composed schema without using an ancestor or descendant's discriminator.
self is BiologyChordate and its discriminator contains Mammal, Primate, Reptile, and Hominid
so se we are using self's discriminator without using an ancestor or descendent's discriminator:
- biology.Mammal from BiologyChordate
- biology.Primate from BiologyChordate
- biology.Reptile from BiologyChordate
- biology.Hominid from BiologyChordate
Per:
@cached_property
def discriminator():
val = {
'biology.Hominid': biology_hominid.BiologyHominid,
'biology.Mammal': biology_mammal.BiologyMammal,
'biology.Primate': biology_primate.BiologyPrimate,
'biology.Reptile': biology_reptile.BiologyReptile,
}
if not val:
return None
return {'class_name': val}
- biology.Hominid from BiologyMammal
Per:
@cached_property
def discriminator():
val = {
'biology.Hominid': biology_hominid.BiologyHominid,
'biology.Primate': biology_primate.BiologyPrimate,
}
if not val:
return None
return {'class_name': val}
So these tests do not exercise new code and do not demonstrate ancestor or descedant deserialization.
They are functionally equivalent to
- test_deserialize_animal (only picks one class in cls.discriminator)
- test_deserialize_mammal (only picks one class in cls.discriminator)
The use the code path in get_discriminator_class line 720 where we use 1 discriminaotr map and get the class:
used_model_class = class_name_to_discr_class.get(discr_value)
Can you remove these tests and sample schemas because they are not exercising a new code path?
Ancestor deserialization:
- biology.Hominid from biology.Hominid
This test does deserialize using an ancestor's discriminator, but we can already do this using the
existing class/schema ParentPet. Can you:
Change this test so it deserializes ParentPet from ParentPet?
Remove the added biology.Hominid schema because we no longer need it?
Descendant Deserialization:
We have not yet demonstrated this yet, and we need to because we have added this feature.
We can do this by deserializing from a grandparent through a oneof child into a oneof child of that child.
So if we deserialize from a grandparent to a grandchild using oneOf relationships, we will demonstrate this.
Can you add a new schema with this relationship and add a test deserializing it?
- For example you could add a Pig schema, which is in mammal's oneOf class list
- Pig can OneOf define BasquePig and DanishPig, all of with include the className property
- Then deserializing a BasquePig payload from a mammal data_type will demonstrate descendent deserialization because BasquePig is not in mammal's discriminator map. It is in Pig's discriminator map.
used_model_class = None | ||
if discr_name in model_class.discriminator: | ||
class_name_to_discr_class = model_class.discriminator[discr_name] | ||
used_model_class = class_name_to_discr_class.get(discr_value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are not yet handling the use case where discr_name is not in model_class.discriminator here.
If we hit that case, then the discriminator propertyName is different than what we are looking for and we should return None early. Here, can you add:
else:
# the discr_name is not what we are looking for, we did not find the class that we are looking for
return None
Or maybe we should make it the clearer:
if discr_name not in model_class.discriminator:
# the discr_name is not what we are looking for, we did not find the class that we are looking for
return None
class_name_to_discr_class = model_class.discriminator[discr_name]
used_model_class = class_name_to_discr_class.get(discr_value)
There is a ancestor relationship with 'allOf' in chordate, primate, mammal, hominid and the unit test is exercising this, I don't understand why you are stating the tests do not include ancestors. Also see below some additional details.
Can you provide a specific example that you want? You are very opinionated about how things should be done, can you please provide a specific relationship using the existing spec? Otherwise I think it'll keep going for a long time.
I'm not sure why you are saying this. I modeled the schemas to address the missing recursion in the existing code when the discriminators are accessible through a traversal of the ancestors. The way I tested locally is I wrote the unit tests, wrote the code until the unit test passed, then I disabled the newly-introduced recursion in I will wait for you response before responding to your points below.
|
Thank you for describing what is going on in those unit tests more. I was wrong in saying that those first examples didn't use ancestor or descendent inheritance. They do use ancestor inheritance. Sorry about that. I was confused about what was going on because it looked like we were only moving through one discriminator map. First they move through the single discriminator in Chordate to select new_cls. Then they check the discriminator in the chosen new_cls and use the ancestor allOf relationship to decide to deserialize using new_cls. All of these tests demonstrate ancestor inheritance:
Let's continue this discussion on our call later.
|
Further work will be needed to resolve #4912. That work will be done at a later time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good.
From here on I will focus on not adding scope creep like I did here, and providing more specific feedback withe code snippets and branches to show what I mean.
Thank you for all of your hard work on this.
get_discriminator_class()
code for every generated class.get_discriminator_class()
function. IMO, another PR should be provided to provide a better error message when the input data does not contain the discriminator property.This PR along these:
will resolve #4912
PR checklist
./bin/
(or Windows batch scripts under.\bin\windows
) to update Petstore samples related to your fix. This is important, as CI jobs will verify all generator outputs of your HEAD commit, and these must match the expectations made by your contribution. You only need to run./bin/{LANG}-petstore.sh
,./bin/openapi3/{LANG}-petstore.sh
if updating the code or mustache templates for a language ({LANG}
) (e.g. php, ruby, python, etc).master
,4.3.x
,5.0.x
. Default:master
.