diff --git a/CHANGELOG.md b/CHANGELOG.md index c75b714c5a..507307e492 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Python client: Populate `APIError.message` while deserializing a property marked with `x-ms-primary-error-message`. [#7542](https://github.com/microsoft/kiota/issues/7542) - C#, Java, Go, PHP, Dart, TypeScript, Python and Ruby client: default value initialization in model classes for DateTime/Date/Time/UUID properties did not compile [#7404](https://github.com/microsoft/kiota/issues/7404) - All languages: default value initialization in model classes for numeric/boolean properties was missing [#7404](https://github.com/microsoft/kiota/issues/7404) - Fixed a bug where required query parameters from one HTTP operation were leaking into the path-item-level URL template, making them appear required for sibling operations on the same path. [#7292](https://github.com/microsoft/kiota/issues/7292) diff --git a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs index 394a0384a0..93722e07ba 100644 --- a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs @@ -568,13 +568,30 @@ private void WriteDeserializerBodyForIntersectionModel(CodeClass parentClass, La private void WriteDeserializerBodyForInheritedModel(bool inherits, CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer) { _codeUsingWriter.WriteInternalImports(parentClass, writer); + var customProperties = parentClass + .GetPropertiesOfKind(CodePropertyKind.Custom) + .Where(static x => !x.ExistsInBaseType) + .OrderBy(static x => x.Name) + .ToArray(); + if (parentClass.IsErrorDefinition) + { + foreach (var primaryErrorMessageProperty in customProperties.Where(static x => x.IsPrimaryErrorMessage)) + { + var deserializerName = $"deserialize_{primaryErrorMessageProperty.Name.CleanupSymbolName()}"; + writer.StartBlock($"def {deserializerName}(n: ParseNode) -> None:"); + writer.WriteLine($"value = n.{GetDeserializationMethodName(primaryErrorMessageProperty.Type, codeElement, parentClass)}"); + writer.WriteLine($"setattr(self, '{primaryErrorMessageProperty.Name.SanitizeSingleQuote()}', value)"); + writer.WriteLine("self.message = '' if value is None else str(value)"); + writer.CloseBlock(string.Empty); + } + } writer.StartBlock($"fields: dict[str, Callable[[Any], {NoneKeyword}]] = {{"); - foreach (var otherProp in parentClass - .GetPropertiesOfKind(CodePropertyKind.Custom) - .Where(static x => !x.ExistsInBaseType) - .OrderBy(static x => x.Name)) + foreach (var otherProp in customProperties) { - writer.WriteLine($"\"{otherProp.WireName.SanitizeDoubleQuote()}\": lambda n : setattr(self, '{otherProp.Name}', n.{GetDeserializationMethodName(otherProp.Type, codeElement, parentClass)}),"); + var deserializer = parentClass.IsErrorDefinition && otherProp.IsPrimaryErrorMessage ? + $"deserialize_{otherProp.Name.CleanupSymbolName()}" : + $"lambda n : setattr(self, '{otherProp.Name.SanitizeSingleQuote()}', n.{GetDeserializationMethodName(otherProp.Type, codeElement, parentClass)})"; + writer.WriteLine($"\"{otherProp.WireName.SanitizeDoubleQuote()}\": {deserializer},"); } writer.CloseBlock(); if (inherits) diff --git a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs index c05f3f317b..ecd4668b27 100644 --- a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs @@ -908,6 +908,61 @@ public void WritesDeSerializerBody() Assert.Contains("defined_in_parent", result, StringComparison.OrdinalIgnoreCase); } [Fact] + public void WritesPrimaryErrorMessageDeserializer() + { + setup(); + parentClass.IsErrorDefinition = true; + parentClass.AddProperty(new CodeProperty + { + Name = "detail", + Kind = CodePropertyKind.Custom, + IsPrimaryErrorMessage = true, + Type = new CodeType + { + Name = "string" + } + }); + method.Kind = CodeMethodKind.Deserializer; + method.IsAsync = false; + + writer.Write(method); + var result = tw.ToString(); + + Assert.Contains("def deserialize_detail(n: ParseNode) -> None:", result); + Assert.Contains("value = n.get_str_value()", result); + Assert.Contains("setattr(self, 'detail', value)", result); + Assert.Contains("self.message = '' if value is None else str(value)", result); + Assert.Contains("\"detail\": deserialize_detail,", result); + } + [Fact] + public void EscapesPrimaryErrorMessagePropertyName() + { + setup(); + parentClass.IsErrorDefinition = true; + parentClass.AddProperty(new CodeProperty + { + Name = "line1'\nline2", + SerializationName = "detail", + Kind = CodePropertyKind.Custom, + IsPrimaryErrorMessage = true, + Type = new CodeType + { + Name = "string" + } + }); + method.Kind = CodeMethodKind.Deserializer; + method.IsAsync = false; + + writer.Write(method); + var result = tw.ToString(); + + Assert.Contains("def deserialize_line1Line2(n: ParseNode) -> None:", result); + Assert.Contains("value = n.get_str_value()", result); + Assert.Contains("setattr(self, 'line1\\'\\nline2', value)", result); + Assert.Contains("self.message = '' if value is None else str(value)", result); + Assert.Contains("\"detail\": deserialize_line1Line2,", result); + } + [Fact] public void WritesInheritedSerializerBody() { setup(true); @@ -1031,6 +1086,28 @@ public void EscapesWireNamesInSerializerAndDeserializerBody() Assert.Contains("\"line1\\\"\\nline2\": lambda n : setattr(self, 'dummy_string', n.get_str_value())", deserializerResult); } [Fact] + public void EscapesPropertyNamesInDeserializerBody() + { + setup(); + parentClass.AddProperty(new CodeProperty + { + Name = "line1'\nline2", + SerializationName = "value", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = "string" + } + }); + method.Kind = CodeMethodKind.Deserializer; + method.IsAsync = false; + + writer.Write(method); + var result = tw.ToString(); + + Assert.Contains("setattr(self, 'line1\\'\\nline2', n.get_str_value())", result); + } + [Fact] public void WritesMethodAsyncDescription() { setup();