Skip to content

Feat/detect discriminator bnch 49688 #143

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

Merged
merged 15 commits into from
Oct 6, 2022

Conversation

straz
Copy link

@straz straz commented Sep 12, 2022

Capture the discriminator property and mappings when parsing the OpenAPI spec.

See more extensive notes in the PR for aurelia.

The timing of merging this PR needs to be synchronized with the merge of the aurelia PR.

Effect on generated code
This effect requires changes from two PRs working together

  • (this PR) updates the parser to capture discriminator_property and discriminator_mappings extracted from openapi.yaml into a ModelProperty instance.
  • PR for aurelia augments the codegen templates to render these properties to the corresponding discriminator-aware code.

Before example

  class EntityOrInaccessibleResource:
        ...
    @classmethod
    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
        d = src_dict.copy()
        entity_or_inaccessible_resource = cls()

        entity_or_inaccessible_resource.additional_properties = d
        return entity_or_inaccessible_resource

After example

  class EntityOrInaccessibleResource:
        ...
    @classmethod
    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
        d = src_dict.copy()
        discriminator = d["resourceType"]

        if discriminator == "entity":
            entity_or_inaccessible_resource = EntityWithResourceType.from_dict(d)
        elif discriminator == "inaccessible":
	    entity_or_inaccessible_resource = InaccessibleResource.from_dict(d)
	else:
            raise ValueError(f"Unexpected discriminator value: {discriminator}")

        entity_or_inaccessible_resource.additional_properties = d
        return entity_or_inaccessible_resource

@straz straz marked this pull request as draft September 12, 2022 20:53
@straz straz marked this pull request as ready for review September 20, 2022 20:19
Copy link

@alexrejto alexrejto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a minor style change requested. I'll let @bowenwr be the blocking reviewer on this.

@@ -317,6 +317,11 @@ def build_model_property(
optional_properties.append(prop)
relative_imports.update(prop.get_imports(prefix=".."))

discriminator_mappings: Dict[str, Property] = {}
if data.discriminator is not None:
for k, v in (data.discriminator.mapping if data.discriminator else {}).items():

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this since the above line checks data.discriminator is not None?

Suggested change
for k, v in (data.discriminator.mapping if data.discriminator else {}).items():
for k, v in data.discriminator.mapping.items():

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, good catch, thanks!

Copy link
Author

@straz straz Sep 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, good catch, thanks!

Well, hmm. I think I had that there because data.discriminator might be {"mapping": None}.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If {"mapping": None} then this will still return None since the check is on data.discriminator right?
May be worth a closer look.

Copy link
Author

@straz straz Sep 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have something like this in mind:

from openapi_python_client.schema import Discriminator, Schema

data = Schema(discriminator=Discriminator(propertyName="resourceType"))

>>> data.discriminator is None
False
>>> data.discriminator.mapping is None
True
>>> [(k,v) for k,v in data.discriminator.mapping.items()]
*** AttributeError: 'NoneType' object has no attribute 'items'

Copy link

@dtkav dtkav Sep 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your intent makes sense.
To make your code match your intent I think you need to change the code from:

(data.discriminator.mapping if data.discriminator else {}).items():

to:

(data.discriminator.mapping if data.discriminator.mapping is not None else {}).items():

or:

(data.discriminator.mapping or {}).items():

alternatively you could replace both checks with EAFP

try:
    for k, v in data.discriminator.mapping.items():
        discriminator_mappings[k] = Reference.from_ref(v)
except AttributeError:
    # Either data.discriminator or data.discriminator.mapping was not set
    pass

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right indeed, thanks! hopefully fixed this.

@dtkav
Copy link

dtkav commented Sep 20, 2022

The timing of merging this PR needs to be synchronized with the merge of the aurelia PR.

It just needs to be landed first right? Or is there some other dependency I'm missing.

Copy link

@bowenwr bowenwr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please update this PR and/or the related aurelia PR with an example of before/after for code gen? It helps immensely to understand the change and its possible implications.

It might be in Slack already but there are some looooong threads in there and I'd rather centralize PRs are the source of truth.

@straz straz requested review from bowenwr and alexrejto September 21, 2022 19:24
Copy link

@bowenwr bowenwr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding context from Data Interfaces Office hours today in case this doesn't land before I'm OOO.

The core requirement is that when a user accesses a discriminated class, they receive the concrete discrimated type (and not a composite type).

One suggested implementation is that union classes shouldn't exist as class but as Union types.

So ideally something like:

InaccessibleResource = Union[Entity, InaccessibleResource]
Entity = Union[AaSequence, CustomEntity, DnaSequence, ...]

When deserialized in the SDK, you would receive the concrete class.

So as a user I would never receive a InaccessibleResource, I would receive a CustomEntity or InacessibleResource

The main impediment is here is that it breaks compatibility, as some users have resorted to their own type detection for classes like Entity to try to make it work. Although we could say this is unsupported and are willing to "break" the workaround, there's hesitation to do so because of previous customer pushback on "broken" releases.

I suggested that the main priority is satisfying the requirement. We should time box a compatibility investigation to see if there's some small, reasonable thing we might be able to do could help here.

Finally, get sign off for dev advocacy on any "breaking" changes, even if the breakage affects workarounds.

@daniel-deutsch-benchling daniel-deutsch-benchling removed their request for review September 23, 2022 14:04
@bowenwr bowenwr requested review from bowenwr and removed request for bowenwr September 23, 2022 21:23
@straz straz requested a review from GitOnUp October 5, 2022 20:34
@straz straz requested a review from bowenwr October 6, 2022 20:35
@straz straz merged commit cebe643 into main Oct 6, 2022
@eli-bl eli-bl deleted the feat/detect-discriminator-BNCH-49688 branch November 26, 2024 22:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants