Skip to content

Commit 80c08fa

Browse files
authored
fix(python-fastapi): map binary response type file to bytes (#24285)
Fixes invalid `-> file` / `"model": file` for octet-stream responses (#20775).
1 parent 9d5fe1c commit 80c08fa

4 files changed

Lines changed: 89 additions & 0 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFastAPIServerCodegen.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ public PythonFastAPIServerCodegen() {
101101
languageSpecificPrimitives.add("Dict");
102102
typeMapping.put("array", "List");
103103
typeMapping.put("map", "Dict");
104+
// Binary response body: map OAS file/binary to built-in bytes (not the invalid Py2 type `file`).
105+
// Multipart upload fields remain UploadFile via overrideFileFormParamTyping (#23793).
106+
// See https://github.com/OpenAPITools/openapi-generator/issues/20775
107+
typeMapping.put("file", "bytes");
108+
typeMapping.put("binary", "bytes");
104109

105110
outputFolder = "generated-code" + File.separator + NAME;
106111
modelTemplateFiles.put("model.mustache", ".py");

modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastAPIServerCodegenTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,23 @@ public void testBinaryMultipartFieldUsesUploadFile() throws IOException {
136136
assertFileContains(baseApi, "csv_file: UploadFile,");
137137
assertFileContains(baseApi, "image: Optional[UploadFile],");
138138
}
139+
140+
@Test(description = "binary response body is typed as bytes, not invalid file (#20775)")
141+
public void testBinaryResponseUsesBytesNotFile() throws IOException {
142+
final DefaultCodegen codegen = new PythonFastAPIServerCodegen();
143+
final String outputPath = generateFiles(codegen, "src/test/resources/3_0/issue_20775.yaml");
144+
final Path api = Paths.get(outputPath + "src/openapi_server/apis/resource_api.py");
145+
final Path baseApi = Paths.get(outputPath + "src/openapi_server/apis/resource_api_base.py");
146+
147+
assertFileExists(api);
148+
assertFileExists(baseApi);
149+
150+
assertFileContains(api, "-> bytes");
151+
assertFileContains(api, "\"model\": bytes");
152+
assertFileNotContains(api, "-> file");
153+
assertFileNotContains(api, "\"model\": file");
154+
155+
assertFileContains(baseApi, "-> bytes");
156+
assertFileNotContains(baseApi, "-> file");
157+
}
139158
}

modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonFastapiCodegenTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,32 @@ public void testRegexPatternCheckedForArrayItems() throws IOException {
119119
TestUtils.assertFileContains(Paths.get(output + "/src/openapi_server/models/test_object.py"),
120120
"raise ValueError(r\"must validate the regular expression /^[A-Z0-9_\\- ]+$/\")");
121121
}
122+
123+
@Test
124+
public void testBinaryResponseUsesBytesNotFile() throws IOException {
125+
// Regression for https://github.com/OpenAPITools/openapi-generator/issues/20775
126+
// Repro: src/test/resources/3_0/issue_20775.yaml
127+
File output = Files.createTempDirectory("test").toFile();
128+
output.deleteOnExit();
129+
130+
final CodegenConfigurator configurator = new CodegenConfigurator()
131+
.setGeneratorName("python-fastapi")
132+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"))
133+
.setInputSpec("src/test/resources/3_0/issue_20775.yaml");
134+
135+
DefaultGenerator generator = new DefaultGenerator();
136+
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
137+
files.forEach(File::deleteOnExit);
138+
139+
final String apiFile = output + "/src/openapi_server/apis/resource_api.py";
140+
final String baseApiFile = output + "/src/openapi_server/apis/resource_api_base.py";
141+
142+
TestUtils.assertFileContains(Paths.get(apiFile), "-> bytes");
143+
TestUtils.assertFileContains(Paths.get(apiFile), "\"model\": bytes");
144+
TestUtils.assertFileNotContains(Paths.get(apiFile), "-> file");
145+
TestUtils.assertFileNotContains(Paths.get(apiFile), "\"model\": file");
146+
147+
TestUtils.assertFileContains(Paths.get(baseApiFile), "-> bytes");
148+
TestUtils.assertFileNotContains(Paths.get(baseApiFile), "-> file");
149+
}
122150
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
openapi: 3.0.1
2+
info:
3+
title: binary response typing
4+
version: 1.0.0
5+
paths:
6+
/resource:
7+
get:
8+
tags:
9+
- resource
10+
description: Binary response body (full or partial)
11+
operationId: resourceInResponse
12+
parameters:
13+
- name: Range
14+
in: header
15+
required: false
16+
schema:
17+
type: string
18+
responses:
19+
"200":
20+
description: Full binary body
21+
content:
22+
application/octet-stream:
23+
schema:
24+
type: string
25+
format: binary
26+
"206":
27+
description: Partial binary body
28+
headers:
29+
Content-Range:
30+
required: true
31+
schema:
32+
type: string
33+
content:
34+
application/octet-stream:
35+
schema:
36+
type: string
37+
format: binary

0 commit comments

Comments
 (0)