Skip to content

Commit c83886d

Browse files
[python-fastapi] add useExternalImplementationPackage to reference an implementation package outside the generated one (#24955)
1 parent 9e7c44a commit c83886d

4 files changed

Lines changed: 54 additions & 5 deletions

File tree

‎docs/generators/python-fastapi.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
3232
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
3333
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
3434
|sourceFolder|directory for generated python source code| |src|
35+
|useExternalImplementationPackage|If true, fastapiImplementationPackage is a fully qualified python package that already exists outside the generated package: it is imported as-is (not prefixed with packageName) and its __init__.py is not generated.| |false|
3536

3637
## IMPORT MAPPING
3738

‎modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,9 @@ public static enum ENUM_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case,
472472

473473
public static final String FASTAPI_IMPLEMENTATION_PACKAGE = "fastapiImplementationPackage";
474474

475+
public static final String USE_EXTERNAL_IMPLEMENTATION_PACKAGE = "useExternalImplementationPackage";
476+
public static final String USE_EXTERNAL_IMPLEMENTATION_PACKAGE_DESC = "If true, fastapiImplementationPackage is a fully qualified python package that already exists outside the generated package: it is imported as-is (not prefixed with packageName) and its __init__.py is not generated.";
477+
475478
public static final String WITH_XML = "withXml";
476479

477480
public static final String WITH_GO_MOD = "withGoMod";

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
6363
private static final String X_FASTAPI_REQUEST_BODY_EXAMPLE = "x-python-fastapi-request-body-example";
6464

6565
private String implPackage;
66+
private boolean useExternalImplementationPackage = false;
6667

6768
@Override
6869
public CodegenType getTag() {
@@ -128,6 +129,8 @@ public PythonFastAPIServerCodegen() {
128129
.defaultValue(DEFAULT_SOURCE_FOLDER));
129130
cliOptions.add(new CliOption(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, "python package name for the implementation code (convention: snake_case).")
130131
.defaultValue(implPackage));
132+
cliOptions.add(CliOption.newBoolean(CodegenConstants.USE_EXTERNAL_IMPLEMENTATION_PACKAGE, CodegenConstants.USE_EXTERNAL_IMPLEMENTATION_PACKAGE_DESC)
133+
.defaultValue(Boolean.FALSE.toString()));
131134
}
132135

133136
@Override
@@ -172,16 +175,22 @@ public void processOpts() {
172175
this.sourceFolder = ((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER));
173176
}
174177

178+
if (additionalProperties.containsKey(CodegenConstants.USE_EXTERNAL_IMPLEMENTATION_PACKAGE)) {
179+
this.useExternalImplementationPackage = convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_EXTERNAL_IMPLEMENTATION_PACKAGE);
180+
}
181+
175182
if (additionalProperties.containsKey(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE)) {
176183
this.implPackage = ((String) additionalProperties.get(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE));
177-
// Prefix templating value with the package name
178-
additionalProperties.put(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE,
179-
this.packageName + "." + this.implPackage);
180184
}
181185

182186
modelPackage = packageName + "." + modelPackage;
183187
apiPackage = packageName + "." + apiPackage;
184-
implPackage = packageName + "." + implPackage;
188+
if (!useExternalImplementationPackage) {
189+
// The implementation package is a sub-package of the generated one
190+
implPackage = packageName + "." + implPackage;
191+
}
192+
// Templating value: the fully qualified implementation package
193+
additionalProperties.put(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, implPackage);
185194

186195
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
187196
supportingFiles.add(new SupportingFile("openapi.mustache", "", "openapi.yaml"));
@@ -200,7 +209,10 @@ public void processOpts() {
200209
}
201210
supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(modelFileFolder(), outputFolder), "__init__.py"));
202211
supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(apiFileFolder(), outputFolder), "__init__.py"));
203-
supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(apiImplFileFolder(), outputFolder), "__init__.py"));
212+
if (!useExternalImplementationPackage) {
213+
// An external implementation package already exists and is owned by the user: nothing to generate in it
214+
supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(apiImplFileFolder(), outputFolder), "__init__.py"));
215+
}
204216

205217
supportingFiles.add(new SupportingFile("conftest.mustache", testPackage.replace('.', File.separatorChar), "conftest.py"));
206218

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,39 @@ public void testAdditionalPropertiesPutForConfigValues() throws Exception {
3131
files.forEach(File::deleteOnExit);
3232

3333
TestUtils.assertFileExists(Paths.get(output.getAbsolutePath(), "/src", "/nodesc", IMPL_PKG, "__init__.py"));
34+
TestUtils.assertFileContains(Paths.get(output + "/src/nodesc/apis/nodesc_api.py"),
35+
"import nodesc." + IMPL_PKG + "\n");
36+
}
37+
38+
@Test
39+
public void testExternalImplementationPackage() throws Exception {
40+
File output = Files.createTempDirectory("test").toFile();
41+
output.deleteOnExit();
42+
43+
// A fully qualified package living outside the generated one, owned by the user
44+
final String EXTERNAL_IMPL_PKG = "my_company.handlers";
45+
final CodegenConfigurator configurator = new CodegenConfigurator()
46+
.setGeneratorName("python-fastapi")
47+
.setPackageName("nodesc")
48+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"))
49+
.setInputSpec("src/test/resources/3_1/nodesc.yaml")
50+
.addAdditionalProperty(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, EXTERNAL_IMPL_PKG)
51+
.addAdditionalProperty(CodegenConstants.USE_EXTERNAL_IMPLEMENTATION_PACKAGE, true);
52+
53+
DefaultGenerator generator = new DefaultGenerator();
54+
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
55+
files.forEach(File::deleteOnExit);
56+
57+
// Imported as-is, not prefixed with the package name
58+
final String apiFile = output + "/src/nodesc/apis/nodesc_api.py";
59+
TestUtils.assertFileContains(Paths.get(apiFile), "import " + EXTERNAL_IMPL_PKG + "\n");
60+
TestUtils.assertFileContains(Paths.get(apiFile), "ns_pkg = " + EXTERNAL_IMPL_PKG + "\n");
61+
TestUtils.assertFileNotContains(Paths.get(apiFile), "nodesc." + EXTERNAL_IMPL_PKG);
62+
63+
// Nothing is generated in a package the generator does not own
64+
TestUtils.assertFileNotExists(Paths.get(output.getAbsolutePath(), "/src", "/nodesc", "impl", "__init__.py"));
65+
TestUtils.assertFileNotExists(Paths.get(output.getAbsolutePath(), "/src", "/my_company", "/handlers", "__init__.py"));
66+
TestUtils.assertFileNotExists(Paths.get(output.getAbsolutePath(), "/src", "/nodesc", "/my_company", "/handlers", "__init__.py"));
3467
}
3568

3669
@Test

0 commit comments

Comments
 (0)