From a51a0c8ad80c67934ce3fe4c086250b0957b0f7a Mon Sep 17 00:00:00 2001 From: Aayush Pandey <108435719+AayushP123@users.noreply.github.com> Date: Sat, 1 Aug 2026 02:10:36 -0700 Subject: [PATCH 1/2] Populate Python API error messages --- CHANGELOG.md | 1 + .../Writers/Python/CodeMethodWriter.cs | 25 ++++++++-- .../Writers/Python/CodeMethodWriterTests.cs | 48 +++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) 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..5ec7dde4bf 100644 --- a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs @@ -568,13 +568,28 @@ 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)) + { + writer.StartBlock($"def deserialize_{primaryErrorMessageProperty.Name}(n: ParseNode) -> None:"); + writer.WriteLine($"self.{primaryErrorMessageProperty.Name} = n.{GetDeserializationMethodName(primaryErrorMessageProperty.Type, codeElement, parentClass)}"); + writer.WriteLine($"self.message = '' if self.{primaryErrorMessageProperty.Name} is None else str(self.{primaryErrorMessageProperty.Name})"); + 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}" : + $"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..a17c2a840d 100644 --- a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs @@ -908,6 +908,32 @@ 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("self.detail = n.get_str_value()", result); + Assert.Contains("self.message = '' if self.detail is None else str(self.detail)", result); + Assert.Contains("\"detail\": deserialize_detail,", result); + } + [Fact] public void WritesInheritedSerializerBody() { setup(true); @@ -1031,6 +1057,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(); From 47a1d1d785855ab5a8f463ad59af087a31bd2fb5 Mon Sep 17 00:00:00 2001 From: Aayush Pandey <108435719+AayushP123@users.noreply.github.com> Date: Sat, 8 Aug 2026 14:16:25 -0700 Subject: [PATCH 2/2] Harden Python error deserializer names --- .../Writers/Python/CodeMethodWriter.cs | 10 +++--- .../Writers/Python/CodeMethodWriterTests.cs | 33 +++++++++++++++++-- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs index 5ec7dde4bf..93722e07ba 100644 --- a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs @@ -577,9 +577,11 @@ private void WriteDeserializerBodyForInheritedModel(bool inherits, CodeMethod co { foreach (var primaryErrorMessageProperty in customProperties.Where(static x => x.IsPrimaryErrorMessage)) { - writer.StartBlock($"def deserialize_{primaryErrorMessageProperty.Name}(n: ParseNode) -> None:"); - writer.WriteLine($"self.{primaryErrorMessageProperty.Name} = n.{GetDeserializationMethodName(primaryErrorMessageProperty.Type, codeElement, parentClass)}"); - writer.WriteLine($"self.message = '' if self.{primaryErrorMessageProperty.Name} is None else str(self.{primaryErrorMessageProperty.Name})"); + 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); } } @@ -587,7 +589,7 @@ private void WriteDeserializerBodyForInheritedModel(bool inherits, CodeMethod co foreach (var otherProp in customProperties) { var deserializer = parentClass.IsErrorDefinition && otherProp.IsPrimaryErrorMessage ? - $"deserialize_{otherProp.Name}" : + $"deserialize_{otherProp.Name.CleanupSymbolName()}" : $"lambda n : setattr(self, '{otherProp.Name.SanitizeSingleQuote()}', n.{GetDeserializationMethodName(otherProp.Type, codeElement, parentClass)})"; writer.WriteLine($"\"{otherProp.WireName.SanitizeDoubleQuote()}\": {deserializer},"); } diff --git a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs index a17c2a840d..ecd4668b27 100644 --- a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs @@ -929,11 +929,40 @@ public void WritesPrimaryErrorMessageDeserializer() var result = tw.ToString(); Assert.Contains("def deserialize_detail(n: ParseNode) -> None:", result); - Assert.Contains("self.detail = n.get_str_value()", result); - Assert.Contains("self.message = '' if self.detail is None else str(self.detail)", 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);