-
Notifications
You must be signed in to change notification settings - Fork 214
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
[Python] Generated code for simple oneOf
s doesn't work
#4433
Comments
I will add on to here The openapi spec I'm trying to generate a client for has somethings like this:
Generated code for the discriminator is
which fails at (C# generated code also stumbles with this) |
@geometrikal I published a pre-release version from my PR: it would be great if you could test my changes from there or rebuilding kiota from my branch. |
@andreaTP no it still fails, although the repeated fields are not longer there. I extended one of your test yamls to show the bug: openapi: 3.0.0
info:
title: "Derived Types API"
version: "1.0.0"
servers:
- url: https://example.org/
paths:
/fruits: # this method will not downcast to OpenAPI v2 because oneOf is not supported
get:
responses:
200:
description: ok
content:
application/json:
# The code generator will need to be clever and instead of generating a fruitResponse class
# with a property for each of the properties, it needs to detect that apple and orange derive from fruit.
# It can then declare the requestExecutors as returning the base type.
schema:
oneOf:
- $ref: "#/components/schemas/fruit" # Allowing the base class allows enables evolvabilty
- $ref: "#/components/schemas/apple"
- $ref: "#/components/schemas/orange"
/fruitsWithDiscriminator:
get:
responses:
200:
description: ok
content:
application/json:
schema:
discriminator:
propertyName: fruitType # This only works if fruitType has the exact schema name
allOf:
- $ref: "#/components/schemas/fruit" # Allowing the base class allows enables evolvabilty
/fruitsWithDiscriminatorWithMapping:
get:
responses:
200:
description: ok
content:
application/json:
schema:
discriminator:
propertyName: fruitType
mapping: # If mapping doesn't exist, then fallback to base type'
apple: '#/components/schemas/apple'
orange: '#/components/schemas/orange'
allOf:
- $ref: "#/components/schemas/fruit" # Allowing the base class allows enables evolvabilty
/fruitsWithOneOfComponent:
get:
responses:
200:
description: ok
content:
application/json:
schema:
$ref: "#/components/schemas/oneOfFruit"
components:
schemas:
fruit:
type: object
title: fruit # required temporarily due to a bug in Kiota codemodel
properties:
name:
type: string
fruitType:
type: string
oneOfFruit:
oneOf:
- $ref: '#/components/schemas/apple'
- $ref: '#/components/schemas/orange'
apple:
allOf:
- $ref: '#/components/schemas/fruit'
type: object
title: apple
properties:
edible:
type: boolean
fruitType:
x-const: apple # the const keyword is only supported int OpenAPI 3.1
orange:
allOf:
- $ref: '#/components/schemas/fruit'
type: object
title: orange
properties:
seedless:
type: boolean
fruitType:
x-const: orange which creates this model with the offending from __future__ import annotations
from dataclasses import dataclass, field
from kiota_abstractions.serialization import ComposedTypeWrapper, Parsable, ParseNode, SerializationWriter
from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING, Union
if TYPE_CHECKING:
from .apple import Apple
from .orange import Orange
@dataclass
class OneOfFruit(ComposedTypeWrapper, Parsable):
"""
Composed type wrapper for classes Apple, Orange
"""
@staticmethod
def create_from_discriminator_value(parse_node: Optional[ParseNode] = None) -> OneOfFruit:
"""
Creates a new instance of the appropriate class based on discriminator value
param parse_node: The parse node to use to read the discriminator value and create the object
Returns: OneOfFruit
"""
if not parse_node:
raise TypeError("parse_node cannot be null.")
try:
mapping_value = parse_node.get_child_node("").get_str_value()
except AttributeError:
mapping_value = None
result = OneOfFruit()
if mapping_value and mapping_value.casefold() == "apple".casefold():
from .apple import Apple
result.apple = Apple()
elif mapping_value and mapping_value.casefold() == "orange".casefold():
from .orange import Orange
result.orange = Orange()
return result
def get_field_deserializers(self,) -> Dict[str, Callable[[ParseNode], None]]:
"""
The deserialization information for the current model
Returns: Dict[str, Callable[[ParseNode], None]]
"""
from .apple import Apple
from .orange import Orange
if hasattr(self, "apple"):
return self.apple.get_field_deserializers()
if hasattr(self, "orange"):
return self.orange.get_field_deserializers()
return {}
def serialize(self,writer: SerializationWriter) -> None:
"""
Serializes information the current object
param writer: Serialization writer to use to serialize this model
Returns: None
"""
if not writer:
raise TypeError("writer cannot be null.")
if hasattr(self, "apple"):
writer.write_object_value(None, self.apple)
elif hasattr(self, "orange"):
writer.write_object_value(None, self.orange) |
@geometrikal I see a few additional edge cases being triggered by your description:
I would reduce/bisect the invariants before moving on in the analysis of your description if possible. |
@andreaTP I stole the yaml from here: https://github.com/microsoft/kiota/blob/main/tests/Kiota.Builder.IntegrationTests/ModelWithDerivedTypes.yaml and just added the The actual specification I'm trying to generate for is v261 of cvat https://github.com/cvat-ai/cvat which uses a few components that just consist of a oneOf with two types. |
Thanks for the context! Waiting for guidance from @baywet or @darrelmiller here |
Notes from our community call on 2024-04-12 on this topic. openapi: 3.1.0
components:
schemas:
baseObject:
type: object
properties:
id:
type: string
name:
type: string
complex:
type: object
properties:
a:
type: string
b:
type: string
c:
type: string
derivedObject:
allOf:
- $ref: '#/components/schemas/baseObject'
- type: object
properties:
derivedProperty:
type: string
derivedComplex:
$id: '/complex'
type: object
properties:
d:
type: string
e:
type: string
f:
type: string
derivedObject2:
allOf:
- $ref: '#/components/schemas/baseObject'
- type: object
properties:
derivedProperty2:
type: string
derivedComplex:
type: object
properties:
g:
type: string
h:
type: string
i:
type: string
diamondObject:
type: object
properties: # Processed first
derivedComplex:
type: number
allOf:
- type: object # Processed second
properties:
derivedComplex:
type: number
- $ref: '#/components/schemas/derivedObject'
- $ref: '#/components/schemas/derivedObject2'
The $id value in JSON Schema 2020-12 will make this problem much easier to solve because schemas will have an identity. While waiting for $id support in OpenAPI 3.1 and the v2 library we came to this proposal during the meeting. Duplicate properties targeting the merged type will use the first defined schema for the property based on the array order of the allOf.
|
@darrelmiller did you mean to add this response to another issue? because the whole thread is reference to oneOf and this last reply refers to allOf? or maybe I missing something from the meeting? |
This comment was marked as off-topic.
This comment was marked as off-topic.
@andreaTP @geometrikal could you try your scenarios again with the latest preview from yesterday and confirm whether you observe the problem? We've made significant improvements to the handling of allof edge scenarios with #4668 and #4381 |
Sorry for the delay here @baywet , I can confirm that the resent improvements on This is my reproducer triggered in the and here you can find the failure: I hope this helps 🙏 |
@andreaTP I believe the issue with the duplicate/superfluous field was resolved with the PR at #5657. I've added the intergration tests you shared at https://github.com/microsoft/kiota/compare/main...andreaTP:kiota:discriminator-test?expand=1 in the PR #5742 to add the validation to the suite. Let us know if anything is missed there. Otherwise, I believe we can close this this once that is merged. |
I'm minimizing some issues found when using
oneOf
s in OpenAPI descriptions.I'll open a PR shortly with the initial findings.
The text was updated successfully, but these errors were encountered: