From 45ece1eb2ffb51416726118db848085517ca247b Mon Sep 17 00:00:00 2001 From: samwelkanda Date: Fri, 1 Mar 2024 03:34:30 +0300 Subject: [PATCH] Add support for multipart form body --- CHANGELOG.md | 1 + README.md | 2 +- it/python/pyproject.toml | 1 + it/python/requirements-dev.txt | 4 +++- src/Kiota.Builder/Refiners/PythonRefiner.cs | 4 ++++ .../Refiners/PythonReservedNamesProvider.cs | 1 + .../Writers/Python/CodeMethodWriter.cs | 2 +- src/kiota/appsettings.json | 6 +++++- .../Writers/Python/CodeMethodWriterTests.cs | 18 ++++++++++++++++++ 9 files changed, 35 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4f3e6ed23..79f059e888 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the init command as part of the experience revamp of [#3356](https://github.com/microsoft/kiota/issues/3356) - Added uri-form encoded serialization for Python. [#2075](https://github.com/microsoft/kiota/issues/2075) +- Added support for multipart form data request body in Python. [#3030](https://github.com/microsoft/kiota/issues/3030) ### Changed diff --git a/README.md b/README.md index 1c7927b704..d83424d85a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The following table provides an overview of the languages supported by Kiota and | Go | ✔ | [✔](https://github.com/microsoft/kiota-abstractions-go) | [FORM](https://github.com/microsoft/kiota-serialization-form-go), [JSON](https://github.com/microsoft/kiota-serialization-json-go), [MULTIPART](https://github.com/microsoft/kiota-serialization-multipart-go), [TEXT](https://github.com/microsoft/kiota-serialization-text-go) | [Anonymous](https://github.com/microsoft/kiota-abstractions-go/blob/main/authentication/anonymous_authentication_provider.go), [API Key](https://github.com/microsoft/kiota-abstractions-go/blob/main/authentication/api_key_authentication_provider.go), [Azure](https://github.com/microsoft/kiota-authentication-azure-go/) | [✔](https://github.com/microsoft/kiota-http-go/) | [link](https://learn.microsoft.com/openapi/kiota/quickstarts/go) | | Java | ✔ | [✔](https://github.com/microsoft/kiota-java/tree/main/components/abstractions) | [FORM](https://github.com/microsoft/kiota-java/tree/main/components/serialization/form), [JSON](https://github.com/microsoft/kiota-java/tree/main/components/serialization/json), [MULTIPART](https://github.com/microsoft/kiota-java/tree/main/components/serialization/multipart), [TEXT](https://github.com/microsoft/kiota-java/tree/main/components/serialization/text) | [Anonymous](https://github.com/microsoft/kiota-java/blob/main/components/abstractions/src/main/java/com/microsoft/kiota/authentication/AnonymousAuthenticationProvider.java), [API Key](https://github.com/microsoft/kiota-java/blob/main/components/abstractions/src/main/java/com/microsoft/kiota/authentication/ApiKeyAuthenticationProvider.java), [Azure](https://github.com/microsoft/kiota-java/tree/main/components/authentication/azure) | [✔](https://github.com/microsoft/kiota-java/tree/main/components/http/okHttp) | [link](https://learn.microsoft.com/openapi/kiota/quickstarts/java) | | PHP | ✔ | [✔](https://github.com/microsoft/kiota-abstractions-php) | [JSON](https://github.com/microsoft/kiota-serialization-json-php), [❌ FORM](https://github.com/microsoft/kiota/issues/2074), [❌ MULTIPART](https://github.com/microsoft/kiota/issues/3029), [TEXT](https://github.com/microsoft/kiota-serialization-text-php) | [Anonymous](https://github.com/microsoft/kiota-abstractions-php/blob/main/src/Authentication/AnonymousAuthenticationProvider.php), [✔️ PHP League](https://github.com/microsoft/kiota-authentication-phpleague-php) | [✔](https://github.com/microsoft/kiota-http-guzzle-php) | [link](https://learn.microsoft.com/openapi/kiota/quickstarts/php) | -| Python | ✔ | [✔](https://github.com/microsoft/kiota-abstractions-python) | [FORM](https://github.com/microsoft/kiota-serialization-form-python), [JSON](https://github.com/microsoft/kiota-serialization-json-python), [❌ MULTIPART](https://github.com/microsoft/kiota/issues/3030), [TEXT](https://github.com/microsoft/kiota-serialization-text-python) | [Anonymous](https://github.com/microsoft/kiota-abstractions-python/blob/main/kiota_abstractions/authentication/anonymous_authentication_provider.py), [Azure](https://github.com/microsoft/kiota-authentication-azure-python) | [✔](https://github.com/microsoft/kiota-http-python) | [link](https://learn.microsoft.com/openapi/kiota/quickstarts/python) | +| Python | ✔ | [✔](https://github.com/microsoft/kiota-abstractions-python) | [FORM](https://github.com/microsoft/kiota-serialization-form-python), [JSON](https://github.com/microsoft/kiota-serialization-json-python), [MULTIPART](https://github.com/microsoft/kiota-serialization-multipart-python), [TEXT](https://github.com/microsoft/kiota-serialization-text-python) | [Anonymous](https://github.com/microsoft/kiota-abstractions-python/blob/main/kiota_abstractions/authentication/anonymous_authentication_provider.py), [Azure](https://github.com/microsoft/kiota-authentication-azure-python) | [✔](https://github.com/microsoft/kiota-http-python) | [link](https://learn.microsoft.com/openapi/kiota/quickstarts/python) | | Ruby | ✔ | [✔](https://github.com/microsoft/kiota-abstractions-ruby) | [❌ FORM](https://github.com/microsoft/kiota/issues/2077), [JSON](https://github.com/microsoft/kiota-serialization-json-ruby), [❌ MULTIPART](https://github.com/microsoft/kiota/issues/3032), [❌ TEXT](https://github.com/microsoft/kiota/issues/1049) | [Anonymous](https://github.com/microsoft/kiota-abstractions-ruby/blob/main/lib/microsoft_kiota_abstractions/authentication/anonymous_authentication_provider.rb), [✔️ OAuth2](https://github.com/microsoft/kiota-authentication-oauth-ruby) | [✔](https://github.com/microsoft/kiota-http-ruby)| | | CLI | ✔ | (see CSharp) + [✔](https://github.com/microsoft/kiota-cli-commons) | (see CSharp) | (see CSharp) | (see CSharp) | [link](https://learn.microsoft.com/openapi/kiota/quickstarts/cli) | | Swift | [▶](https://github.com/microsoft/kiota/issues/1449) | [✔](./abstractions/swift) | [❌ FORM](https://github.com/microsoft/kiota/issues/2076), [❌ JSON](https://github.com/microsoft/kiota/issues/1451), [❌ FORM](https://github.com/microsoft/kiota/issues/3033), [❌ TEXT](https://github.com/microsoft/kiota/issues/1452) | [Anonymous](./abstractions/swift/Source/MicrosoftKiotaAbstractions/Authentication/AnonymousAuthenticationProvider.swift), [❌ Azure](https://github.com/microsoft/kiota/issues/1453) | [❌](https://github.com/microsoft/kiota/issues/1454)| | diff --git a/it/python/pyproject.toml b/it/python/pyproject.toml index ac6eec4e36..acd33a3e4e 100644 --- a/it/python/pyproject.toml +++ b/it/python/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ "microsoft-kiota-serialization-json >= 1.0.0", "microsoft-kiota-serialization-text >= 1.0.0", "microsoft-kiota-serialization-form >= 0.1.0", + "microsoft-kiota-serialization-multipart >= 0.1.0", ] license = {file = "LICENSE"} readme = "README.md" diff --git a/it/python/requirements-dev.txt b/it/python/requirements-dev.txt index e8770a85a2..74f0c0651f 100644 --- a/it/python/requirements-dev.txt +++ b/it/python/requirements-dev.txt @@ -104,12 +104,14 @@ microsoft-kiota-authentication-azure==1.0.0 microsoft-kiota-http==1.3.0 -microsoft-kiota-serialization-json==1.0.1 +microsoft-kiota-serialization-json==1.1.0 microsoft-kiota-serialization-text==1.0.0 microsoft-kiota-serialization-form==0.1.0 +microsoft-kiota-serialization-multipart==0.1.0 + msal==1.27.0 msal-extensions==1.1.0 diff --git a/src/Kiota.Builder/Refiners/PythonRefiner.cs b/src/Kiota.Builder/Refiners/PythonRefiner.cs index 83b0533c0b..3b0bf0adb4 100644 --- a/src/Kiota.Builder/Refiners/PythonRefiner.cs +++ b/src/Kiota.Builder/Refiners/PythonRefiner.cs @@ -113,6 +113,7 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance "kiota_serialization_json.json_serialization_writer_factory.JsonSerializationWriterFactory", "kiota_serialization_text.text_serialization_writer_factory.TextSerializationWriterFactory", "kiota_serialization_form.form_serialization_writer_factory.FormSerializationWriterFactory", + "kiota_serialization_multipart.multipart_serialization_writer_factory.MultipartSerializationWriterFactory", } ); ReplaceDefaultDeserializationModules( @@ -150,6 +151,7 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance }, cancellationToken); } + private const string MultipartBodyClassName = "MultipartBody"; private const string AbstractionsPackageName = "kiota_abstractions"; private const string SerializationModuleName = $"{AbstractionsPackageName}.serialization"; private const string StoreModuleName = $"{AbstractionsPackageName}.store"; @@ -160,6 +162,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance $"{AbstractionsPackageName}.request_adapter", "RequestAdapter"), new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator), $"{AbstractionsPackageName}.method", "Method"), + new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator) && method.Parameters.Any(static y => y.IsOfKind(CodeParameterKind.RequestBody) && y.Type.Name.Equals(MultipartBodyClassName, StringComparison.OrdinalIgnoreCase)), + $"{AbstractionsPackageName}.multipart_body", MultipartBodyClassName), new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator), $"{AbstractionsPackageName}.request_information", "RequestInformation"), new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator), diff --git a/src/Kiota.Builder/Refiners/PythonReservedNamesProvider.cs b/src/Kiota.Builder/Refiners/PythonReservedNamesProvider.cs index 1e44466513..85b80f741c 100644 --- a/src/Kiota.Builder/Refiners/PythonReservedNamesProvider.cs +++ b/src/Kiota.Builder/Refiners/PythonReservedNamesProvider.cs @@ -44,6 +44,7 @@ public class PythonReservedNamesProvider : IReservedNamesProvider "yield", "property", "BaseRequestBuilder", + "MultipartBody", }); public HashSet ReservedNames => _reservedNames.Value; } diff --git a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs index f156bf9262..67eee514f2 100644 --- a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs @@ -833,7 +833,7 @@ private void UpdateRequestInformationFromRequestBody(CodeMethod codeElement, Req } else { - var setMethodName = requestParams.requestBody.Type is CodeType bodyType && bodyType.TypeDefinition is CodeClass ? "set_content_from_parsable" : "set_content_from_scalar"; + var setMethodName = requestParams.requestBody.Type is CodeType bodyType && (bodyType.TypeDefinition is CodeClass || bodyType.Name.Equals("MultipartBody", StringComparison.OrdinalIgnoreCase)) ? "set_content_from_parsable" : "set_content_from_scalar"; writer.WriteLine($"{RequestInfoVarName}.{setMethodName}(self.{requestAdapterProperty.Name}, \"{sanitizedRequestBodyContentType}\", {requestParams.requestBody.Name})"); } } diff --git a/src/kiota/appsettings.json b/src/kiota/appsettings.json index 71ac42668a..91f53fa9c0 100644 --- a/src/kiota/appsettings.json +++ b/src/kiota/appsettings.json @@ -214,6 +214,10 @@ { "Name": "microsoft-kiota-serialization-form", "Version": "0.1.0" + }, + { + "Name": "microsoft-kiota-serialization-multipart", + "Version": "0.1.0" } ], "DependencyInstallCommand": "pip install {0}=={1}" @@ -280,4 +284,4 @@ "DependencyInstallCommand": "dotnet add package {0} --version {1}" } } -} +} \ No newline at end of file diff --git a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs index b4d73a1481..fb18a5651e 100644 --- a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs @@ -694,6 +694,24 @@ public void WritesRequestGeneratorBodyForParsable() Assert.Contains("return request_info", result); } [Fact] + public void WritesRequestGeneratorBodyForMultipart() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = "MultipartBody", + IsExternal = true, + }; + method.RequestBodyContentType = "multipart/form-data"; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("set_content_from_parsable", result); + } + [Fact] public void WritesRequestGeneratorBodyWhenUrlTemplateIsOverrode() { setup();