diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java index 61936225db59..a2001a0633de 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java @@ -619,10 +619,21 @@ protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Sc @Override public OperationsMap postProcessOperationsWithModels( OperationsMap objs, List allModels) { + List operations = objs.getOperations().getOperation(); + if (useIndependentImplicitClients) { - renameIndependentClientOperationMembers( - objs.getOperations().getOperation()); + renameIndependentClientOperationMembers(operations); + } + + boolean hasJsonHeader = operations.stream() + .flatMap(operation -> operation.headerParams.stream()) + .anyMatch(parameter -> + parameter.vendorExtensions.containsKey("x-python-json-header")); + + if (hasJsonHeader) { + objs.put("x-python-has-json-header", true); } + return super.postProcessOperationsWithModels(objs, allModels); } @@ -1021,6 +1032,11 @@ public String toVarName(String name) { @Override public void postProcessParameter(CodegenParameter parameter) { super.postProcessParameter(parameter); + + if (parameter.isHeaderParam && isJsonMimeType(parameter.contentType)) { + parameter.vendorExtensions.put("x-python-json-header", true); + } + // Only operation signatures gain this control argument. Keep model // field naming independent of the selected library. if (usesLegacyApiCompatibility() diff --git a/modules/openapi-generator/src/main/resources/python/api.mustache b/modules/openapi-generator/src/main/resources/python/api.mustache index 3cee5c88c1b8..4319b31edfdc 100644 --- a/modules/openapi-generator/src/main/resources/python/api.mustache +++ b/modules/openapi-generator/src/main/resources/python/api.mustache @@ -1,5 +1,8 @@ {{>partial_header}} +{{#x-python-has-json-header}} +import json +{{/x-python-has-json-header}} import warnings from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt from typing import Any, Dict, List, Optional, Tuple, Union @@ -383,7 +386,14 @@ https://github.com/OpenAPITools/openapi-generator/blob/c84b949df1a9ec04ba75989cb # process the header parameters {{#headerParams}} if {{paramName}} is not None: + {{#vendorExtensions.x-python-json-header}} + _header_params['{{baseName}}'] = json.dumps( + self.api_client.sanitize_for_serialization({{paramName}}) + ) + {{/vendorExtensions.x-python-json-header}} + {{^vendorExtensions.x-python-json-header}} _header_params['{{baseName}}'] = {{paramName}} + {{/vendorExtensions.x-python-json-header}} {{/headerParams}} # process the form parameters {{#formParams}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientCodegenTest.java index 7ae88fae560b..282daa871d4d 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientCodegenTest.java @@ -128,6 +128,62 @@ public void testHttpx2Generation(boolean sync, Object poetry1, String backend) t Assert.assertFalse(Files.readString(Paths.get(output, "requirements.txt")).contains("httpx >=")); } + @Test + public void testJsonImportOnlyGeneratedForJsonHeaderContent() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("python") + .setInputSpec("src/test/resources/3_0/petstore.yaml") + .setOutputDir(output.getAbsolutePath()); + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + Path api = Paths.get( + output.getAbsolutePath(), + "openapi_client", + "api", + "pet_api.py"); + + TestUtils.assertFileNotContains(api, "import json"); + } + + @Test + public void testJsonContentHeaderUsesJsonSerialization() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("python") + .setInputSpec("src/test/resources/3_1/python/json-header-content.yaml") + .setOutputDir(output.getAbsolutePath()); + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + Path api = Paths.get( + output.getAbsolutePath(), + "openapi_client", + "api", + "default_api.py"); + + TestUtils.assertFileContains( + api, + "import json", + "_header_params['X-Json-Arg'] = json.dumps(", + "self.api_client.sanitize_for_serialization(x_json_arg)", + "_header_params['X-Plain-Arg'] = x_plain_arg"); + + TestUtils.assertFileNotContains( + api, + "_header_params['X-Json-Arg'] = x_json_arg", + "_header_params['X-Plain-Arg'] = json.dumps("); + } + @Test public void testInitialConfigValues() throws Exception { final PythonClientCodegen codegen = new PythonClientCodegen(); diff --git a/modules/openapi-generator/src/test/resources/3_1/python/json-header-content.yaml b/modules/openapi-generator/src/test/resources/3_1/python/json-header-content.yaml new file mode 100644 index 000000000000..9bbaa5c7d408 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/python/json-header-content.yaml @@ -0,0 +1,38 @@ +openapi: 3.1.0 + +info: + title: JSON header content test + version: 1.0.0 + +paths: + /test: + post: + operationId: testJsonHeader + parameters: + - name: X-Json-Arg + in: header + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/HeaderArg' + + - name: X-Plain-Arg + in: header + required: false + schema: + type: string + + responses: + '204': + description: No content + +components: + schemas: + HeaderArg: + type: object + properties: + path: + type: string + recursive: + type: boolean