-
Notifications
You must be signed in to change notification settings - Fork 218
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] Initial fixes using simple oneOf
s with discriminator
#4435
Changes from 10 commits
4a19c6a
944ef1a
2396b63
8a7a778
56679bf
95342cf
adc2076
b2d4971
5112a5a
b46369a
c3f3d38
4eb8ff7
474f19a
c4d8e02
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 |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import pytest | ||
|
||
from kiota_serialization_json.json_parse_node_factory import JsonParseNodeFactory | ||
from kiota_serialization_json.json_serialization_writer_factory import JsonSerializationWriterFactory | ||
|
||
from client.models.component import Component | ||
from client.models.component1 import Component1 | ||
from client.models.component2 import Component2 | ||
|
||
@pytest.mark.asyncio | ||
async def test_component1_deser(): | ||
factory = JsonParseNodeFactory() | ||
root = factory.get_root_parse_node('application/json', '{"objectType": "obj1", "one": "foo"}'.encode('utf-8')) | ||
result = root.get_object_value(Component) | ||
assert hasattr(result, "component1") | ||
assert not hasattr(result, "component2") | ||
assert isinstance(result.component1, Component1) | ||
assert result.component1.object_type == "obj1" | ||
assert result.component1.one == "foo" | ||
|
||
@pytest.mark.asyncio | ||
async def test_component2_deser(): | ||
factory = JsonParseNodeFactory() | ||
root = factory.get_root_parse_node('application/json', '{"objectType": "obj2", "two": "bar"}'.encode('utf-8')) | ||
result = root.get_object_value(Component) | ||
assert hasattr(result, "component2") | ||
assert not hasattr(result, "component1") | ||
assert isinstance(result.component2, Component2) | ||
assert result.component2.object_type == "obj2" | ||
assert result.component2.two == "bar" | ||
|
||
@pytest.mark.asyncio | ||
async def test_component1_ser(): | ||
obj = Component() | ||
obj1 = Component1() | ||
obj1.object_type = "obj1" | ||
obj1.one = "foo" | ||
obj.component1 = obj1 | ||
factory = JsonSerializationWriterFactory() | ||
writer = factory.get_serialization_writer('application/json') | ||
obj.serialize(writer) | ||
content = writer.get_serialized_content().decode('utf-8') | ||
assert content == '{"objectType": "obj1", "one": "foo"}' | ||
|
||
@pytest.mark.asyncio | ||
async def test_component2_ser(): | ||
obj = Component() | ||
obj2 = Component2() | ||
obj2.object_type = "obj2" | ||
obj2.two = "bar" | ||
obj.component2 = obj2 | ||
factory = JsonSerializationWriterFactory() | ||
writer = factory.get_serialization_writer('application/json') | ||
obj.serialize(writer) | ||
content = writer.get_serialized_content().decode('utf-8') | ||
assert content == '{"objectType": "obj2", "two": "bar"}' |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,8 +77,14 @@ public override IEnumerable<CodeProperty> AddProperty(params CodeProperty[] prop | |
if (properties.Length == 0) | ||
throw new ArgumentOutOfRangeException(nameof(properties)); | ||
|
||
return properties.Select(property => | ||
return properties.GroupBy(static x => x.Name).Select(static x => x.First()).Select(property => | ||
andreaTP marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
// Prevent duplicates | ||
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. Those changes are fixing #4178 Please note that two properties with colliding serialization names are going to collide anyway causing just an even more subtle runtime error. 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. To test in other projects I used a pre-release pipeline: 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. @andreaTP The PR looks good from my end. I just need to run some tests on the graph api metadata and I'll confirm if everything is good 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. 🙏 thanks! 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. Looking at microsoftgraph/msgraph-sdk-dotnet@dev...kiota/v1.0/pipelinebuild/142930 It looks like there's something we need to rethink here. It looks like kiota expects the property to be added to the codeDom either way if it exists up the hierarchy. The relevant writer/refiner of the language will then use the 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. The groupby here is fine, it'd prevent somebody from adding twice the same property from the same schema. Unlikely because of the conventions of JSON schema, but fine. 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. Without the check, a discriminator property is added multiple times (as reported in other issues) and it breaks serialization/deserialization. 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. and that discriminator property being added multiple times only happens whenever we have a union type? 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 don't know, it happens for sure when we have a union type, if this is the only case I cannot tell. 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. You can verify the behavior by using the reproducer: https://github.com/microsoft/kiota/pull/4435/files#diff-2862479147a7917233ba0a4bc0e346b2e297c170b54b431a1c3f511765e2ca53 |
||
if (FindPropertyByNameInTypeHierarchy(property.Name) != null) | ||
{ | ||
return null; // this property is going to collide, skipping | ||
} | ||
|
||
if (property.IsOfKind(CodePropertyKind.Custom, CodePropertyKind.QueryParameter)) | ||
{ | ||
if (GetOriginalPropertyDefinedFromBaseType(property.WireName) is CodeProperty original) | ||
|
@@ -98,7 +104,7 @@ public override IEnumerable<CodeProperty> AddProperty(params CodeProperty[] prop | |
} | ||
var result = base.AddProperty(property).First(); | ||
return PropertiesByWireName.GetOrAdd(result.WireName, result); | ||
}).ToArray(); | ||
}).OfType<CodeProperty>().ToArray(); | ||
} | ||
public override void RenameChildElement(string oldName, string newName) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -137,7 +137,14 @@ private void WriteFactoryMethodBodyForInheritedModel(CodeClass parentClass, Lang | |
private const string ResultVarName = "result"; | ||
private void WriteFactoryMethodBodyForUnionModel(CodeMethod codeElement, CodeClass parentClass, CodeParameter parseNodeParameter, LanguageWriter writer) | ||
{ | ||
writer.WriteLine($"{ResultVarName} = {parentClass.Name}()"); | ||
var className = parentClass.Name; | ||
if (parentClass.Parent is CodeClass pc && | ||
!string.IsNullOrEmpty(pc.Name) && | ||
pc.InnerClasses.Any(x => x == parentClass)) | ||
{ | ||
className = $"{pc.Name}.{parentClass.Name}"; | ||
} | ||
writer.WriteLine($"{ResultVarName} = {className}()"); | ||
Comment on lines
+140
to
+147
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 struggling to understand what's happening here. By definition the wrapper class cannot/shouldn't be one of the member 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. 🤷♂️ I just changed the code that was not compiling, not sure if there is a more principled fix for the issue. |
||
var includeElse = false; | ||
foreach (var property in parentClass.GetPropertiesOfKind(CodePropertyKind.Custom) | ||
.OrderBy(static x => x, CodePropertyTypeForwardComparer) | ||
|
@@ -508,7 +515,7 @@ private void WriteDeserializerBodyForUnionModel(CodeMethod method, CodeClass par | |
.ThenBy(static x => x.Name) | ||
.Select(static x => x.Name)) | ||
{ | ||
writer.StartBlock($"if self.{otherPropName}:"); | ||
writer.StartBlock($"if hasattr(self, \"{otherPropName}\"):"); | ||
writer.WriteLine($"return self.{otherPropName}.{method.Name}()"); | ||
writer.DecreaseIndent(); | ||
} | ||
|
@@ -659,7 +666,7 @@ private void WriteSerializerBodyForUnionModel(CodeClass parentClass, LanguageWri | |
.OrderBy(static x => x, CodePropertyTypeForwardComparer) | ||
.ThenBy(static x => x.Name)) | ||
{ | ||
writer.StartBlock($"{(includeElse ? "el" : string.Empty)}if self.{otherProp.Name}:"); | ||
writer.StartBlock($"{(includeElse ? "el" : string.Empty)}if hasattr(self, \"{otherProp.Name}\"):"); | ||
writer.WriteLine($"writer.{GetSerializationMethodName(otherProp.Type)}(None, self.{otherProp.Name})"); | ||
writer.DecreaseIndent(); | ||
if (!includeElse) | ||
|
@@ -676,7 +683,7 @@ private void WriteSerializerBodyForIntersectionModel(CodeClass parentClass, Lang | |
.OrderBy(static x => x, CodePropertyTypeBackwardComparer) | ||
.ThenBy(static x => x.Name)) | ||
{ | ||
writer.StartBlock($"{(includeElse ? "el" : string.Empty)}if self.{otherProp.Name}:"); | ||
writer.StartBlock($"{(includeElse ? "el" : string.Empty)}if hasattr(self, \"{otherProp.Name}\"):"); | ||
writer.WriteLine($"writer.{GetSerializationMethodName(otherProp.Type)}(None, self.{otherProp.Name})"); | ||
writer.DecreaseIndent(); | ||
if (!includeElse) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
openapi: 3.0.0 | ||
info: | ||
title: "Discriminator API" | ||
version: "1.0.0" | ||
servers: | ||
- url: https://mytodos.doesnotexist/ | ||
paths: | ||
/discriminateme: | ||
post: | ||
description: Return something | ||
responses: | ||
"200": | ||
description: OK | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/Component" | ||
requestBody: | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/Component" | ||
components: | ||
schemas: | ||
Component: | ||
oneOf: | ||
- $ref: "#/components/schemas/Component1" | ||
- $ref: "#/components/schemas/Component2" | ||
discriminator: | ||
propertyName: objectType | ||
mapping: | ||
obj1: "#/components/schemas/Component1" | ||
obj2: "#/components/schemas/Component2" | ||
Component1: | ||
type: object | ||
required: | ||
- objectType | ||
properties: | ||
objectType: | ||
type: string | ||
one: | ||
type: string | ||
Component2: | ||
type: object | ||
required: | ||
- objectType | ||
properties: | ||
objectType: | ||
type: string | ||
two: | ||
type: string |
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 needs to be moved to the unreleased section
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.
ok, I'll do it