diff --git a/CHANGELOG.md b/CHANGELOG.md index b93ed1d703..2b0c7265d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Deprecated Visual Studio OpenAPI reference packages. - Fixes a bug where a stackoverflow exception occurs when inlined schemas have self-references [2656](https://github.com/microsoft/kiota/issues/2656) - Fixes nil safety while type casting values in collections in Go +- Moved common RequestBuilder ((request_adapter, url_template and path_parameters)) and RequestConfiguration(headers, options) properties to respective base classes in Python.[2440](https://github.com/microsoft/kiota/issues/2440) ## [1.3.0] - 2023-06-09 diff --git a/it/exec-cmd.ps1 b/it/exec-cmd.ps1 index b231650b52..a8093edec2 100755 --- a/it/exec-cmd.ps1 +++ b/it/exec-cmd.ps1 @@ -179,10 +179,9 @@ elseif ($language -eq "php") { elseif ($language -eq "python") { Invoke-Call -ScriptBlock { python -m pip install --upgrade pip - pip install pipenv - pipenv install --dev --skip-lock - pipenv run pylint integration_test --disable=W --rcfile=.pylintrc - pipenv run mypy integration_test + pip install -r requirements-dev.txt + pylint integration_test --disable=W --rcfile=.pylintrc + mypy integration_test } -ErrorAction Stop } Pop-Location diff --git a/it/python/Pipfile b/it/python/Pipfile deleted file mode 100644 index a75d07f685..0000000000 --- a/it/python/Pipfile +++ /dev/null @@ -1,23 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -azure-identity = "1.12.0" -httpx = {version = "==0.23.3", extras = ["http2"]} -microsoft-kiota-abstractions = "==0.5.0" -microsoft-kiota-authentication-azure = "==0.2.0" -microsoft-kiota-http = "==0.4.0" -microsoft-kiota-serialization-json = "==0.2.2" -microsoft-kiota-serialization-text = "==0.2.0" - -[dev-packages] -flit = "==3.8.0" -pylint = "==2.16.4" -mypy = "==1.0.1" -yapf = "==0.32.0" -isort = "==5.12.0" -toml = "==0.10.2" -pytest = "==7.2.2" -pytest-asyncio = "==0.20.3" \ No newline at end of file diff --git a/it/python/requirements-dev.txt b/it/python/requirements-dev.txt new file mode 100644 index 0000000000..7f9cafb720 --- /dev/null +++ b/it/python/requirements-dev.txt @@ -0,0 +1,136 @@ +-i https://pypi.org/simple + +astroid==2.15.5 ; python_full_version >= '3.7.2' + +certifi==2023.5.7 ; python_version >= '3.6' + +charset-normalizer==3.1.0 ; python_full_version >= '3.7.0' + +colorama==0.4.6 ; sys_platform == 'win32' + +dill==0.3.6 ; python_version < '3.11' + +docutils==0.20.1 ; python_version >= '3.7' + +exceptiongroup==1.1.1 ; python_version < '3.11' + +flit==3.9.0 + +flit-core==3.9.0 ; python_version >= '3.6' + +idna==3.4 + +importlib-metadata==6.7.0 ; python_version >= '3.7' + +iniconfig==2.0.0 ; python_version >= '3.7' + +isort==5.12.0 + +lazy-object-proxy==1.9.0 ; python_version >= '3.7' + +mccabe==0.7.0 ; python_version >= '3.6' + +mypy==1.4.1 + +mypy-extensions==1.0.0 ; python_version >= '3.5' + +packaging==23.1 ; python_version >= '3.7' + +platformdirs==3.8.0 ; python_version >= '3.7' + +pluggy==1.2.0 ; python_version >= '3.7' + +pylint==2.17.4 + +pytest==7.4.0 + +pytest-asyncio==0.21.0 + +requests==2.31.0 ; python_version >= '3.7' + +toml==0.10.2 + +tomli==2.0.1 ; python_version < '3.11' + +tomli-w==1.0.0 ; python_version >= '3.7' + +tomlkit==0.11.8 ; python_version >= '3.7' + +typing-extensions==4.6.3 ; python_version >= '3.7' + +urllib3==2.0.3 ; python_version >= '3.7' + +wrapt==1.15.0 ; python_version < '3.11' + +yapf==0.40.1 + +zipp==3.15.0 ; python_version >= '3.7' + +aiohttp==3.8.4 ; python_version >= '3.6' + +aiosignal==1.3.1 ; python_version >= '3.7' + +anyio==3.7.0 ; python_version >= '3.7' + +async-timeout==4.0.2 ; python_version >= '3.6' + +attrs==23.1.0 ; python_version >= '3.7' + +azure-core==1.27.1 ; python_version >= '3.7' + +azure-identity==1.13.0 + +cffi==1.15.1 + +cryptography==41.0.1 ; python_version >= '3.7' + +frozenlist==1.3.3 ; python_version >= '3.7' + +h11==0.14.0 ; python_version >= '3.7' + +h2==4.1.0 + +hpack==4.0.0 ; python_full_version >= '3.6.1' + +httpcore==0.16.3 ; python_version >= '3.7' + +httpx[http2]==0.23.3 + +hyperframe==6.0.1 ; python_full_version >= '3.6.1' + +microsoft-kiota-abstractions==0.5.5 + +microsoft-kiota-authentication-azure==0.2.0 + +microsoft-kiota-http==0.4.4 + +microsoft-kiota-serialization-json==0.3.6 + +microsoft-kiota-serialization-text==0.2.1 + +msal==1.22.0 + +msal-extensions==1.0.0 + +multidict==6.0.4 ; python_version >= '3.7' + +portalocker==2.7.0 ; python_version >= '3.5' and platform_system == 'Windows' + +pycparser==2.21 + +pyjwt[crypto]==2.7.0 ; python_version >= '3.7' + +python-dateutil==2.8.2 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' + +pywin32==306 ; platform_system == 'Windows' + +rfc3986[idna2008]==1.5.0 + +six==1.16.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' + +sniffio==1.3.0 ; python_version >= '3.7' + +uritemplate==4.1.1 ; python_version >= '3.6' + +yarl==1.9.2 ; python_version >= '3.7' + diff --git a/src/Kiota.Builder/Refiners/PythonRefiner.cs b/src/Kiota.Builder/Refiners/PythonRefiner.cs index c037dcd3cf..9051ecda5f 100644 --- a/src/Kiota.Builder/Refiners/PythonRefiner.cs +++ b/src/Kiota.Builder/Refiners/PythonRefiner.cs @@ -19,6 +19,16 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance AddDefaultImports(generatedCode, defaultUsingEvaluators); DisableActionOf(generatedCode, CodeParameterKind.RequestConfiguration); + MoveRequestBuilderPropertiesToBaseType(generatedCode, + new CodeUsing + { + Name = "BaseRequestBuilder", + Declaration = new CodeType + { + Name = $"{AbstractionsPackageName}.base_request_builder", + IsExternal = true + } + }, AccessModifier.Public); cancellationToken.ThrowIfCancellationRequested(); ReplaceIndexersByMethodsWithParameter(generatedCode, false, @@ -42,6 +52,16 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance new PythonExceptionsReservedNamesProvider(), static x => $"{x}_" ); + RemoveRequestConfigurationClassesCommonProperties(generatedCode, + new CodeUsing + { + Name = "BaseRequestConfiguration", + Declaration = new CodeType + { + Name = $"{AbstractionsPackageName}.base_request_configuration", + IsExternal = true + } + }); cancellationToken.ThrowIfCancellationRequested(); MoveClassesWithNamespaceNamesUnderNamespace(generatedCode); ReplacePropertyNames(generatedCode, diff --git a/src/Kiota.Builder/Refiners/PythonReservedNamesProvider.cs b/src/Kiota.Builder/Refiners/PythonReservedNamesProvider.cs index befac81608..1e44466513 100644 --- a/src/Kiota.Builder/Refiners/PythonReservedNamesProvider.cs +++ b/src/Kiota.Builder/Refiners/PythonReservedNamesProvider.cs @@ -43,6 +43,7 @@ public class PythonReservedNamesProvider : IReservedNamesProvider "while", "yield", "property", + "BaseRequestBuilder", }); public HashSet ReservedNames => _reservedNames.Value; } diff --git a/src/Kiota.Builder/Writers/Python/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/Python/CodeClassDeclarationWriter.cs index 6f2be8cfe1..65fdf6ec37 100644 --- a/src/Kiota.Builder/Writers/Python/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/Python/CodeClassDeclarationWriter.cs @@ -16,9 +16,9 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); var parentNamespace = codeElement.GetImmediateParentOfType(); - if (codeElement.Parent?.Parent is not CodeClass) //Imports for inner classes will be written locally + _codeUsingWriter.WriteExternalImports(codeElement, writer); // external imports before internal imports + if (codeElement.Parent?.Parent is not CodeClass) //Internal imports for inner classes will be written locally { - _codeUsingWriter.WriteExternalImports(codeElement, writer); // external imports before internal imports _codeUsingWriter.WriteConditionalInternalImports(codeElement, writer, parentNamespace); } diff --git a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs index c4261f8045..4804d4ca7f 100644 --- a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs @@ -38,7 +38,8 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); var requestConfigParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestConfiguration); var requestParams = new RequestParams(requestBodyParam, requestConfigParam); - if (!codeElement.IsOfKind(CodeMethodKind.Setter)) + if (!codeElement.IsOfKind(CodeMethodKind.Setter) && + !(codeElement.IsOfKind(CodeMethodKind.Constructor) && parentClass.IsOfKind(CodeClassKind.RequestBuilder))) foreach (var parameter in codeElement.Parameters.Where(static x => !x.Optional).OrderBy(static x => x.Name)) { var parameterName = parameter.Name.ToSnakeCase(); @@ -314,31 +315,40 @@ private CodePropertyKind[] SetterAccessProperties private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMethod, LanguageWriter writer, bool inherits) { if (inherits && !parentClass.IsOfKind(CodeClassKind.Model)) - writer.WriteLine("super().__init__()"); + { + if (parentClass.IsOfKind(CodeClassKind.RequestBuilder) && + currentMethod.Parameters.OfKind(CodeParameterKind.RequestAdapter) is CodeParameter requestAdapterParameter && + parentClass.Properties.OfKind(CodePropertyKind.UrlTemplate) is CodeProperty urlTemplateProperty) + { + if (currentMethod.Parameters.OfKind(CodeParameterKind.PathParameters) is CodeParameter pathParametersParameter) + writer.WriteLine($"super().__init__({requestAdapterParameter.Name.ToSnakeCase()}, {urlTemplateProperty.DefaultValue ?? ""}, {pathParametersParameter.Name.ToSnakeCase()})"); + else + writer.WriteLine($"super().__init__({requestAdapterParameter.Name.ToSnakeCase()}, {urlTemplateProperty.DefaultValue ?? ""}, None)"); + } + else + writer.WriteLine("super().__init__()"); + } if (parentClass.IsOfKind(CodeClassKind.Model)) { writer.DecreaseIndent(); } - WriteDirectAccessProperties(parentClass, writer); - WriteSetterAccessProperties(parentClass, writer); - WriteSetterAccessPropertiesWithoutDefaults(parentClass, writer); - if (parentClass.IsOfKind(CodeClassKind.RequestBuilder)) - { - if (currentMethod.IsOfKind(CodeMethodKind.Constructor)) - { - if (currentMethod.Parameters.OfKind(CodeParameterKind.PathParameters) is CodeParameter pathParametersParam) - conventions.AddParametersAssignment(writer, - pathParametersParam.Type.AllTypes.OfType().FirstOrDefault(), - pathParametersParam.Name.ToFirstCharacterLowerCase(), - currentMethod.Parameters - .Where(x => x.IsOfKind(CodeParameterKind.Path)) - .Select(x => (x.Type, x.SerializationName, x.Name.ToFirstCharacterLowerCase())) - .ToArray()); - AssignPropertyFromParameter(parentClass, currentMethod, CodeParameterKind.PathParameters, CodePropertyKind.PathParameters, writer, conventions.TempDictionaryVarName); - } - AssignPropertyFromParameter(parentClass, currentMethod, CodeParameterKind.RequestAdapter, CodePropertyKind.RequestAdapter, writer); + if (!(parentClass.IsOfKind(CodeClassKind.RequestBuilder) && currentMethod.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor))) + { + WriteDirectAccessProperties(parentClass, writer); + WriteSetterAccessProperties(parentClass, writer); + WriteSetterAccessPropertiesWithoutDefaults(parentClass, writer); + if (currentMethod.Parameters.OfKind(CodeParameterKind.PathParameters) is CodeParameter pathParametersParam) + conventions.AddParametersAssignment(writer, + pathParametersParam.Type.AllTypes.OfType().FirstOrDefault(), + pathParametersParam.Name.ToFirstCharacterLowerCase(), + currentMethod.Parameters + .Where(x => x.IsOfKind(CodeParameterKind.Path)) + .Select(x => (x.Type, x.SerializationName, x.Name.ToFirstCharacterLowerCase())) + .ToArray()); + AssignPropertyFromParameter(parentClass, currentMethod, CodeParameterKind.PathParameters, CodePropertyKind.PathParameters, writer, conventions.TempDictionaryVarName); } + if (parentClass.IsOfKind(CodeClassKind.Model)) { writer.IncreaseIndent(); @@ -686,8 +696,8 @@ private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer, st { writer.StartBlock("Args:"); - foreach (var paramWithDescription in parametersWithDescription.OrderBy(x => x.Name)) - writer.WriteLine($"{conventions.DocCommentPrefix}{paramWithDescription.Name}: {PythonConventionService.RemoveInvalidDescriptionCharacters(paramWithDescription.Documentation.Description)}"); + foreach (var paramWithDescription in parametersWithDescription.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)) + writer.WriteLine($"{conventions.DocCommentPrefix}{paramWithDescription.Name.ToSnakeCase()}: {PythonConventionService.RemoveInvalidDescriptionCharacters(paramWithDescription.Documentation.Description)}"); writer.DecreaseIndent(); } if (!isVoid) @@ -804,15 +814,13 @@ private static void UpdateRequestInformationFromRequestConfiguration(RequestPara if (requestParams.requestConfiguration != null) { writer.StartBlock($"if {requestParams.requestConfiguration.Name.ToSnakeCase()}:"); - var headers = requestParams.Headers; - if (headers != null) - writer.WriteLine($"{RequestInfoVarName}.add_request_headers({requestParams.requestConfiguration.Name.ToSnakeCase()}.{headers.Name.ToSnakeCase()})"); + var headers = requestParams.Headers?.Name.ToSnakeCase() ?? "headers"; + writer.WriteLine($"{RequestInfoVarName}.add_request_headers({requestParams.requestConfiguration.Name.ToSnakeCase()}.{headers})"); var queryString = requestParams.QueryParameters; if (queryString != null) writer.WriteLines($"{RequestInfoVarName}.set_query_string_parameters_from_raw_object({requestParams.requestConfiguration.Name.ToSnakeCase()}.{queryString.Name.ToSnakeCase()})"); - var options = requestParams.Options; - if (options != null) - writer.WriteLine($"{RequestInfoVarName}.add_request_options({requestParams.requestConfiguration.Name.ToSnakeCase()}.{options.Name.ToSnakeCase()})"); + var options = requestParams.Options?.Name.ToSnakeCase() ?? "options"; + writer.WriteLine($"{RequestInfoVarName}.add_request_options({requestParams.requestConfiguration.Name.ToSnakeCase()}.{options})"); writer.DecreaseIndent(); } } diff --git a/src/Kiota.Builder/Writers/Python/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/Python/CodePropertyWriter.cs index 23153ef2ad..c3ee176c5e 100644 --- a/src/Kiota.Builder/Writers/Python/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/Python/CodePropertyWriter.cs @@ -14,6 +14,7 @@ public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter w { ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); + if (codeElement.ExistsInExternalBaseType) return; var returnType = conventions.GetTypeString(codeElement.Type, codeElement, true, writer); if (codeElement.Parent is not CodeClass parentClass) throw new InvalidOperationException("The parent of a property should be a class"); /* Only write specific properties as class attributes diff --git a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs index b846adcf60..0c6a26a2d7 100644 --- a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs @@ -899,7 +899,7 @@ public void WritesMethodAsyncDescription() Assert.Contains("\"\"\"", result); Assert.Contains(MethodDescription, result); Assert.Contains("Args:", result); - Assert.Contains(ParamName, result); + Assert.Contains("param_name", result); Assert.Contains(ParamDescription, result); Assert.Contains("Returns:", result); Assert.Contains("await", result); @@ -927,7 +927,7 @@ public void WritesMethodSyncDescription() Assert.Contains("\"\"\"", result); Assert.Contains(MethodDescription, result); Assert.Contains("Args:", result); - Assert.Contains(ParamName, result); + Assert.Contains("param_name", result); Assert.Contains(ParamDescription, result); Assert.DoesNotContain("await", result); } @@ -1431,10 +1431,10 @@ public void WritesConstructor() }); writer.Write(method); var result = tw.ToString(); - Assert.DoesNotContain("super().__init__()", result); - Assert.Contains("This property has a description", result); - Assert.Contains($"self.{propName}: Optional[str] = {defaultValue}", result); - Assert.Contains("get_path_parameters(", result); + Assert.DoesNotContain("super().__init__(self)", result); + Assert.DoesNotContain("This property has a description", result); + Assert.DoesNotContain($"self.{propName}: Optional[str] = {defaultValue}", result); + Assert.DoesNotContain("get_path_parameters(", result); } [Fact] public void DoesntWriteConstructorForModelClasses() @@ -1560,9 +1560,9 @@ public void WritesConstructorWithInheritance() writer.Write(method); var result = tw.ToString(); Assert.Contains("super().__init__()", result); - Assert.Contains("has a description", result); - Assert.Contains($"self.{prop2Name}: Optional[str] = {defaultValue}", result); - Assert.Contains($"self.{propName}: Optional[str] = None", result); + Assert.DoesNotContain("has a description", result); + Assert.DoesNotContain($"self.{prop2Name}: Optional[str] = {defaultValue}", result); + Assert.DoesNotContain($"self.{propName}: Optional[str] = None", result); } [Fact] public void WritesApiConstructor()