From 3c30b97b22279bbb34dcb5d60810e4ad5e795ca9 Mon Sep 17 00:00:00 2001 From: Andrey Markelov Date: Wed, 30 Sep 2026 12:05:56 -0700 Subject: [PATCH 1/2] fix(python): serialize JSON header parameter content --- .../languages/PythonClientCodegen.java | 5 +++ .../src/main/resources/python/api.mustache | 8 ++++ .../python/PythonClientCodegenTest.java | 33 ++++++++++++++++ .../3_1/python/json-header-content.yaml | 38 +++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 modules/openapi-generator/src/test/resources/3_1/python/json-header-content.yaml 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..48816ddb28ce 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 @@ -1021,6 +1021,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..26a098950e45 100644 --- a/modules/openapi-generator/src/main/resources/python/api.mustache +++ b/modules/openapi-generator/src/main/resources/python/api.mustache @@ -1,5 +1,6 @@ {{>partial_header}} +import json import warnings from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt from typing import Any, Dict, List, Optional, Tuple, Union @@ -383,7 +384,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..91615fcf4843 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,39 @@ public void testHttpx2Generation(boolean sync, Object poetry1, String backend) t Assert.assertFalse(Files.readString(Paths.get(output, "requirements.txt")).contains("httpx >=")); } + @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 From 2d53ce8e9eecafab3ff87a26e8c1c2d76fa228e8 Mon Sep 17 00:00:00 2001 From: Andrey Markelov Date: Wed, 30 Sep 2026 14:07:28 -0700 Subject: [PATCH 2/2] fix(python): conditionally import json for header serialization --- .../languages/PythonClientCodegen.java | 15 ++++++++++-- .../src/main/resources/python/api.mustache | 2 ++ .../python/PythonClientCodegenTest.java | 23 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) 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 48816ddb28ce..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); } diff --git a/modules/openapi-generator/src/main/resources/python/api.mustache b/modules/openapi-generator/src/main/resources/python/api.mustache index 26a098950e45..4319b31edfdc 100644 --- a/modules/openapi-generator/src/main/resources/python/api.mustache +++ b/modules/openapi-generator/src/main/resources/python/api.mustache @@ -1,6 +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 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 91615fcf4843..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,29 @@ 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();