diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFastAPIServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFastAPIServerCodegen.java index ab6a95fcc3a1..91e37991b8c0 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFastAPIServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFastAPIServerCodegen.java @@ -101,6 +101,11 @@ public PythonFastAPIServerCodegen() { languageSpecificPrimitives.add("Dict"); typeMapping.put("array", "List"); typeMapping.put("map", "Dict"); + // Binary response body: map OAS file/binary to built-in bytes (not the invalid Py2 type `file`). + // Multipart upload fields remain UploadFile via overrideFileFormParamTyping (#23793). + // See https://github.com/OpenAPITools/openapi-generator/issues/20775 + typeMapping.put("file", "bytes"); + typeMapping.put("binary", "bytes"); outputFolder = "generated-code" + File.separator + NAME; modelTemplateFiles.put("model.mustache", ".py"); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastAPIServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastAPIServerCodegenTest.java index ff381bacecbd..ea70acdb67da 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastAPIServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastAPIServerCodegenTest.java @@ -136,4 +136,23 @@ public void testBinaryMultipartFieldUsesUploadFile() throws IOException { assertFileContains(baseApi, "csv_file: UploadFile,"); assertFileContains(baseApi, "image: Optional[UploadFile],"); } + + @Test(description = "binary response body is typed as bytes, not invalid file (#20775)") + public void testBinaryResponseUsesBytesNotFile() throws IOException { + final DefaultCodegen codegen = new PythonFastAPIServerCodegen(); + final String outputPath = generateFiles(codegen, "src/test/resources/3_0/issue_20775.yaml"); + final Path api = Paths.get(outputPath + "src/openapi_server/apis/resource_api.py"); + final Path baseApi = Paths.get(outputPath + "src/openapi_server/apis/resource_api_base.py"); + + assertFileExists(api); + assertFileExists(baseApi); + + assertFileContains(api, "-> bytes"); + assertFileContains(api, "\"model\": bytes"); + assertFileNotContains(api, "-> file"); + assertFileNotContains(api, "\"model\": file"); + + assertFileContains(baseApi, "-> bytes"); + assertFileNotContains(baseApi, "-> file"); + } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastapiCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastapiCodegenTest.java index 931d6cd41ce2..1594f8d139c0 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastapiCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastapiCodegenTest.java @@ -89,4 +89,32 @@ public void testRegexPatternCheckedForArrayItems() throws IOException { TestUtils.assertFileContains(Paths.get(output + "/src/openapi_server/models/test_object.py"), "raise ValueError(r\"must validate the regular expression /^[A-Z0-9_\\- ]+$/\")"); } + + @Test + public void testBinaryResponseUsesBytesNotFile() throws IOException { + // Regression for https://github.com/OpenAPITools/openapi-generator/issues/20775 + // Repro: src/test/resources/3_0/issue_20775.yaml + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("python-fastapi") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")) + .setInputSpec("src/test/resources/3_0/issue_20775.yaml"); + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + final String apiFile = output + "/src/openapi_server/apis/resource_api.py"; + final String baseApiFile = output + "/src/openapi_server/apis/resource_api_base.py"; + + TestUtils.assertFileContains(Paths.get(apiFile), "-> bytes"); + TestUtils.assertFileContains(Paths.get(apiFile), "\"model\": bytes"); + TestUtils.assertFileNotContains(Paths.get(apiFile), "-> file"); + TestUtils.assertFileNotContains(Paths.get(apiFile), "\"model\": file"); + + TestUtils.assertFileContains(Paths.get(baseApiFile), "-> bytes"); + TestUtils.assertFileNotContains(Paths.get(baseApiFile), "-> file"); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/issue_20775.yaml b/modules/openapi-generator/src/test/resources/3_0/issue_20775.yaml new file mode 100644 index 000000000000..092d6efb16ca --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/issue_20775.yaml @@ -0,0 +1,37 @@ +openapi: 3.0.1 +info: + title: binary response typing + version: 1.0.0 +paths: + /resource: + get: + tags: + - resource + description: Binary response body (full or partial) + operationId: resourceInResponse + parameters: + - name: Range + in: header + required: false + schema: + type: string + responses: + "200": + description: Full binary body + content: + application/octet-stream: + schema: + type: string + format: binary + "206": + description: Partial binary body + headers: + Content-Range: + required: true + schema: + type: string + content: + application/octet-stream: + schema: + type: string + format: binary