Skip to content

Commit

Permalink
Merge pull request #4058 from microsoft/bugfix/scalar-member-composed…
Browse files Browse the repository at this point in the history
…-type-serialization

Python fix/scalar member composed type serialization
  • Loading branch information
samwelkanda authored Jan 31, 2024
2 parents 6e54404 + c7f44e9 commit 801f08b
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Fixed serialization of scalar members in union types for Python. [#2828](https://github.com/microsoft/kiota/issues/2828)
- Fixed a bug where scalar error mappings would be generated even though it's not supported by the http request adapter. [#4018](https://github.com/microsoft/kiota/issues/4018)
- Switched to proxy generation for TypeScript, leading to about ~44% bundle sizes reduction. [#3642](https://github.com/microsoft/kiota/issues/3642)
- Fixed a bug where TypeScript models factory methods would be missing return types.
Expand Down
48 changes: 22 additions & 26 deletions src/Kiota.Builder/Refiners/PythonRefiner.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -17,9 +16,15 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
return Task.Run(() =>
{
cancellationToken.ThrowIfCancellationRequested();
ConvertUnionTypesToWrapper(generatedCode,
_configuration.UsesBackingStore,
static s => s,
true,
$"{SerializationModuleName}.composed_type_wrapper",
"ComposedTypeWrapper"
);
CorrectCommonNames(generatedCode);
RemoveMethodByKind(generatedCode, CodeMethodKind.RawUrlConstructor);
AddDefaultImports(generatedCode, defaultUsingEvaluators);
DisableActionOf(generatedCode,
CodeParameterKind.RequestConfiguration);
MoveRequestBuilderPropertiesToBaseType(generatedCode,
Expand All @@ -39,6 +44,7 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
static x => x.ToSnakeCase(),
GenerationLanguage.Python);
RemoveCancellationParameter(generatedCode);
AddDefaultImports(generatedCode, defaultUsingEvaluators);
CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType, CorrectImplements);
cancellationToken.ThrowIfCancellationRequested();
CorrectCoreTypesForBackingStore(generatedCode, "field(default_factory=BackingStoreFactorySingleton(backing_store_factory=None).backing_store_factory.create_backing_store, repr=False)");
Expand Down Expand Up @@ -138,6 +144,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
}

private const string AbstractionsPackageName = "kiota_abstractions";
private const string SerializationModuleName = $"{AbstractionsPackageName}.serialization";
private const string StoreModuleName = $"{AbstractionsPackageName}.store";
private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = {
new (static x => x is CodeClass, "__future__", "annotations"),
new (static x => x is CodeClass, "typing", "Any, Callable, Dict, List, Optional, TYPE_CHECKING, Union"),
Expand All @@ -150,25 +158,25 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator),
$"{AbstractionsPackageName}.request_option", "RequestOption"),
new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Serializer),
$"{AbstractionsPackageName}.serialization", "SerializationWriter"),
SerializationModuleName, "SerializationWriter"),
new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Deserializer),
$"{AbstractionsPackageName}.serialization", "ParseNode"),
SerializationModuleName, "ParseNode"),
new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor, CodeMethodKind.IndexerBackwardCompatibility),
$"{AbstractionsPackageName}.get_path_parameters", "get_path_parameters"),
new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor),
$"{AbstractionsPackageName}.serialization", "Parsable", "ParsableFactory"),
SerializationModuleName, "Parsable", "ParsableFactory"),
new (static x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model),
$"{AbstractionsPackageName}.serialization", "Parsable"),
SerializationModuleName, "Parsable"),
new (static x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model) && @class.Properties.Any(static x => x.IsOfKind(CodePropertyKind.AdditionalData)),
$"{AbstractionsPackageName}.serialization", "AdditionalDataHolder"),
SerializationModuleName, "AdditionalDataHolder"),
new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.ClientConstructor) &&
method.Parameters.Any(y => y.IsOfKind(CodeParameterKind.BackingStore)),
$"{AbstractionsPackageName}.store", "BackingStoreFactory", "BackingStoreFactorySingleton"),
StoreModuleName, "BackingStoreFactory", "BackingStoreFactorySingleton"),
new (static x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.BackingStore),
$"{AbstractionsPackageName}.store", "BackedModel", "BackingStore", "BackingStoreFactorySingleton" ),
StoreModuleName, "BackedModel", "BackingStore", "BackingStoreFactorySingleton" ),
new (static x => x is CodeClass @class && (@class.IsOfKind(CodeClassKind.Model) || x.Parent is CodeClass), "dataclasses", "dataclass, field"),
new (static x => x is CodeClass { OriginalComposedType: CodeIntersectionType intersectionType } && intersectionType.Types.Any(static y => !y.IsExternal) && intersectionType.DiscriminatorInformation.HasBasicDiscriminatorInformation,
$"{AbstractionsPackageName}.serialization", "ParseNodeHelper"),
new (static x => x is CodeClass @class && @class.OriginalComposedType is CodeIntersectionType intersectionType && intersectionType.Types.Any(static y => !y.IsExternal),
SerializationModuleName, "ParseNodeHelper"),
new (static x => x is IDeprecableElement element && element.Deprecation is not null && element.Deprecation.IsDeprecated,
"warnings", "warn"),
};
Expand Down Expand Up @@ -258,7 +266,7 @@ private static void CorrectPropertyType(CodeProperty currentProperty)
else if (currentProperty.IsOfKind(CodePropertyKind.PathParameters))
{
currentProperty.Type.IsNullable = false;
currentProperty.Type.Name = "Dict[str, Any]";
currentProperty.Type.Name = "Union[str, Dict[str, Any]]";
if (!string.IsNullOrEmpty(currentProperty.DefaultValue))
currentProperty.DefaultValue = "{}";
}
Expand All @@ -281,20 +289,8 @@ private static void CorrectMethodType(CodeMethod currentMethod)
if (urlTplParams != null &&
urlTplParams.Type is CodeType originalType)
{
originalType.Name = "Dict[str, Any]";
urlTplParams.Documentation.Description = "The raw url or the Url template parameters for the request.";
var unionType = new CodeUnionType
{
Name = "raw_url_or_template_parameters",
IsNullable = true,
};
unionType.AddType(originalType, new()
{
Name = "str",
IsNullable = true,
IsExternal = true,
});
urlTplParams.Type = unionType;
originalType.Name = "Union[str, Dict[str, Any]]";
urlTplParams.Documentation.Description = "The raw url or the url-template parameters for the request.";
}
}
CorrectCoreTypes(currentMethod.Parent as CodeClass, DateTypesReplacements, currentMethod.Parameters
Expand Down
6 changes: 4 additions & 2 deletions src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri
WriteFactoryMethodBody(codeElement, parentClass, writer);
writer.CloseBlock(string.Empty);
break;
case CodeMethodKind.ComposedTypeMarker:
throw new InvalidOperationException("ComposedTypeMarker is not required as interface is explicitly implemented.");
case CodeMethodKind.RawUrlConstructor:
throw new InvalidOperationException("RawUrlConstructor is not supported in python");
case CodeMethodKind.RequestBuilderBackwardCompatibility:
Expand Down Expand Up @@ -722,12 +724,12 @@ private void WriteMethodPrototype(CodeMethod code, LanguageWriter writer, string
writer.WriteLine("@staticmethod");
var accessModifier = conventions.GetAccessModifier(code.Access);
var isConstructor = code.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor);
var methodName = (code.Kind switch
var methodName = code.Kind switch
{
_ when code.IsAccessor => code.AccessedProperty?.Name,
_ when isConstructor => "__init__",
_ => code.Name,
});
};
var asyncPrefix = code.IsAsync && code.Kind is CodeMethodKind.RequestExecutor ? "async " : string.Empty;
var instanceReference = code.IsOfKind(CodeMethodKind.Factory) ? string.Empty : "self,";
var parameters = string.Join(", ", code.Parameters.OrderBy(x => x, parameterOrderComparer)
Expand Down
62 changes: 61 additions & 1 deletion tests/Kiota.Builder.Tests/Refiners/PythonLanguageRefinerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,66 @@ public async Task EscapesExceptionPropertiesNames()
Assert.Contains("response_status_code_", exception.Properties.Select(x => x.Name));
}
[Fact]
public async Task ConvertsUnionTypesToWrapper()
{
var model = root.AddClass(new CodeClass
{
Name = "model",
Kind = CodeClassKind.Model
}).First();
var union = new CodeUnionType
{
Name = "union",
};
union.AddType(new()
{
Name = "type1",
}, new()
{
Name = "type2"
});
var property = model.AddProperty(new CodeProperty
{
Name = "deserialize",
Kind = CodePropertyKind.Custom,
Type = union.Clone() as CodeTypeBase,
}).First();
var method = model.AddMethod(new CodeMethod
{
Name = "method",
ReturnType = union.Clone() as CodeTypeBase
}).First();
var parameter = new CodeParameter
{
Name = "param1",
Type = union.Clone() as CodeTypeBase
};
var indexer = new CodeIndexer
{
Name = "idx",
ReturnType = union.Clone() as CodeTypeBase,
IndexParameter = new()
{
Name = "id",
Type = new CodeType
{
Name = "string"
},
}
};
model.AddIndexer(indexer);
method.AddParameter(parameter);
await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Python }, root);
Assert.True(property.Type is CodeType);
Assert.True(parameter.Type is CodeType);
Assert.True(method.ReturnType is CodeType);
var resultingWrapper = root.FindChildByName<CodeClass>("union");
Assert.NotNull(resultingWrapper);
Assert.NotNull(resultingWrapper.OriginalComposedType);
Assert.Contains("ComposedTypeWrapper", resultingWrapper.StartBlock.Implements.Select(static x => x.Name));
Assert.Null(resultingWrapper.Methods.SingleOrDefault(static x => x.IsOfKind(CodeMethodKind.ComposedTypeMarker)));
}
[Fact]
public async Task CorrectsCoreType()
{

Expand Down Expand Up @@ -369,7 +429,7 @@ public async Task CorrectsCoreType()
Assert.Empty(model.Properties.Where(x => PathParametersDefaultValue.Equals(x.DefaultValue)));
Assert.Empty(model.Methods.Where(x => DeserializeDefaultName.Equals(x.ReturnType.Name)));
Assert.Empty(model.Methods.SelectMany(x => x.Parameters).Where(x => serializerDefaultName.Equals(x.Type.Name)));
Assert.Single(constructorMethod.Parameters.Where(x => x.Type is CodeComposedTypeBase));
Assert.Single(constructorMethod.Parameters.Where(x => x.Type is CodeTypeBase));
}
[Fact]
public async Task ReplacesDateTimeOffsetByNativeType()
Expand Down

0 comments on commit 801f08b

Please sign in to comment.