-
-
Notifications
You must be signed in to change notification settings - Fork 7.2k
adds Recursive Discriminator support #4913
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -592,8 +592,11 @@ def deserialize_model(model_data, model_class, path_to_item, check_type, | |
|
||
used_model_class = model_class | ||
if model_class.discriminator() is not None: | ||
used_model_class = model_class.get_discriminator_class( | ||
from_server, model_data) | ||
used_model_class = recursive_discriminator_lookup(model_class, from_server, model_data) | ||
|
||
# This is the case if the used_model_class isn't a subclass but rather the parent class itself | ||
if not used_model_class: | ||
used_model_class = model_class | ||
|
||
if issubclass(used_model_class, ModelSimple): | ||
instance = used_model_class(value=model_data, **kw_args) | ||
|
@@ -609,6 +612,35 @@ def deserialize_model(model_data, model_class, path_to_item, check_type, | |
instance = used_model_class(**kw_args) | ||
return instance | ||
|
||
recursive_discriminators_cache = {} | ||
|
||
def recursive_discriminators(model_class): | ||
""" | ||
Return the discriminator mapping for all possible subtypes (recursively found down to the leaves) | ||
:param model_class: | ||
:return: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you describe the structure of the dict that we are returning here? |
||
""" | ||
if model_class in recursive_discriminators_cache: | ||
return recursive_discriminators_cache[model_class] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How are we sure that this model class is valid? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, not sure I understand. The only purpose of this method is to search through the discriminators and add them to the cache - not to return the model class itself. That is done in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I understand |
||
recursive_discriminated_types = model_class.discriminator() | ||
if not recursive_discriminated_types: | ||
return {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our old invocation of get_discriminator_class returns a class. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't equivalent to get_discriminator_class for performance reasons. This instead is equivalent to model_class.discriminator. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to return an empty dict if we will never key into it? |
||
for discriminator_key, discriminator_dict in model_class.discriminator().items(): | ||
for child_class in discriminator_dict.values(): | ||
for child_discriminator_key, child_discriminator_dict in recursive_discriminators(child_class).items(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you use clearer names here? |
||
if child_discriminator_key in recursive_discriminated_types: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to do a check to make sure that child_discriminator_key == discriminator_key? |
||
recursive_discriminated_types[child_discriminator_key].update(child_discriminator_dict) | ||
else: | ||
recursive_discriminated_types[child_discriminator_key] = child_discriminator_dict | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a test of this function that shows what we store going down both forks of this if/else? |
||
recursive_discriminators_cache[model_class] = recursive_discriminated_types | ||
return recursive_discriminated_types | ||
|
||
def recursive_discriminator_lookup(model_class, from_server, model_data): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why aren't we recursively calling this?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had done this before, but the issue was that we have a recursive lookup for a single type potentially many, many times. So instead, I put in a simple cache and changed the logic to 'lookup the recursive discriminator in the cache for the given model_class'. If it isn't there, I calculate the whole recursive discriminator for that type and I store it in the cache. Then any subsequent lookup is just a single dictionary lookup. I had the exact logic that you had just laid out and the deserialization process took 30 seconds+ for a complex model. The cache brought that down to ~3 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense. When we get this working, please remove the get_discriminator_class if we are no longer using it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a clearer description of our use case here?
|
||
discr_propertyname_py = list(model_class.discriminator().keys())[0] | ||
try: | ||
return recursive_discriminators(model_class)[discr_propertyname_py][model_data[model_class.attribute_map[discr_propertyname_py]]] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one line is doing the following:
Please separate this into multiple lines for readability and ease of understanding |
||
except KeyError as e: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not raise an exception? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm refactoring this to be identical to get_discriminator_class. That should resolve a couple of these comments - I agree it is fast and dirty ATM |
||
return | ||
|
||
def deserialize_file(response_data, configuration, content_disposition=None): | ||
"""Deserializes body to file | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -848,6 +848,10 @@ def deserialize_model(model_data, model_class, path_to_item, check_type, | |
used_model_class = model_class.get_discriminator_class( | ||
from_server, model_data) | ||
|
||
# This is the case if the used_model_class isn't a subclass but rather the parent class itself | ||
if not used_model_class: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our old code always returned a model class. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was actually the original error I was looking at before I started looking at the recursive discriminator issue. The method returns nothing when the discriminator specified is not in the discriminator mapping. I believe in that case, a reasonable solution is to return the base class itself, but perhaps we should instead return an error here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per the spec here: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#composition-and-inheritance-polymorphism |
||
used_model_class = model_class | ||
|
||
if issubclass(used_model_class, ModelSimple): | ||
instance = used_model_class(value=model_data, **kw_args) | ||
return instance | ||
|
Uh oh!
There was an error while loading. Please reload this page.
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.
In a comment can you describe the structure of the dict that we are storing here?
What are the keys what are the values?