diff --git a/docs/generators/python-fastapi.md b/docs/generators/python-fastapi.md index 6f96b6ba8960..b1a2d987450a 100644 --- a/docs/generators/python-fastapi.md +++ b/docs/generators/python-fastapi.md @@ -32,6 +32,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| |sourceFolder|directory for generated python source code| |src| +|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| ## IMPORT MAPPING diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java index f79bec58a662..8e62ad60896f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java @@ -472,6 +472,9 @@ public static enum ENUM_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case, public static final String FASTAPI_IMPLEMENTATION_PACKAGE = "fastapiImplementationPackage"; + public static final String USE_EXTERNAL_IMPLEMENTATION_PACKAGE = "useExternalImplementationPackage"; + 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."; + public static final String WITH_XML = "withXml"; public static final String WITH_GO_MOD = "withGoMod"; 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 144e22ac4b8e..ecaf15cdf630 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 @@ -63,6 +63,7 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen { private static final String X_FASTAPI_REQUEST_BODY_EXAMPLE = "x-python-fastapi-request-body-example"; private String implPackage; + private boolean useExternalImplementationPackage = false; @Override public CodegenType getTag() { @@ -128,6 +129,8 @@ public PythonFastAPIServerCodegen() { .defaultValue(DEFAULT_SOURCE_FOLDER)); cliOptions.add(new CliOption(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, "python package name for the implementation code (convention: snake_case).") .defaultValue(implPackage)); + cliOptions.add(CliOption.newBoolean(CodegenConstants.USE_EXTERNAL_IMPLEMENTATION_PACKAGE, CodegenConstants.USE_EXTERNAL_IMPLEMENTATION_PACKAGE_DESC) + .defaultValue(Boolean.FALSE.toString())); } @Override @@ -172,16 +175,22 @@ public void processOpts() { this.sourceFolder = ((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER)); } + if (additionalProperties.containsKey(CodegenConstants.USE_EXTERNAL_IMPLEMENTATION_PACKAGE)) { + this.useExternalImplementationPackage = convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_EXTERNAL_IMPLEMENTATION_PACKAGE); + } + if (additionalProperties.containsKey(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE)) { this.implPackage = ((String) additionalProperties.get(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE)); - // Prefix templating value with the package name - additionalProperties.put(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, - this.packageName + "." + this.implPackage); } modelPackage = packageName + "." + modelPackage; apiPackage = packageName + "." + apiPackage; - implPackage = packageName + "." + implPackage; + if (!useExternalImplementationPackage) { + // The implementation package is a sub-package of the generated one + implPackage = packageName + "." + implPackage; + } + // Templating value: the fully qualified implementation package + additionalProperties.put(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, implPackage); supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); supportingFiles.add(new SupportingFile("openapi.mustache", "", "openapi.yaml")); @@ -200,7 +209,10 @@ public void processOpts() { } supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(modelFileFolder(), outputFolder), "__init__.py")); supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(apiFileFolder(), outputFolder), "__init__.py")); - supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(apiImplFileFolder(), outputFolder), "__init__.py")); + if (!useExternalImplementationPackage) { + // An external implementation package already exists and is owned by the user: nothing to generate in it + supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(apiImplFileFolder(), outputFolder), "__init__.py")); + } supportingFiles.add(new SupportingFile("conftest.mustache", testPackage.replace('.', File.separatorChar), "conftest.py")); 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 52040cc3e85d..a5ba8c6bc7df 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 @@ -31,6 +31,39 @@ public void testAdditionalPropertiesPutForConfigValues() throws Exception { files.forEach(File::deleteOnExit); TestUtils.assertFileExists(Paths.get(output.getAbsolutePath(), "/src", "/nodesc", IMPL_PKG, "__init__.py")); + TestUtils.assertFileContains(Paths.get(output + "/src/nodesc/apis/nodesc_api.py"), + "import nodesc." + IMPL_PKG + "\n"); + } + + @Test + public void testExternalImplementationPackage() throws Exception { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + // A fully qualified package living outside the generated one, owned by the user + final String EXTERNAL_IMPL_PKG = "my_company.handlers"; + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("python-fastapi") + .setPackageName("nodesc") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")) + .setInputSpec("src/test/resources/3_1/nodesc.yaml") + .addAdditionalProperty(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, EXTERNAL_IMPL_PKG) + .addAdditionalProperty(CodegenConstants.USE_EXTERNAL_IMPLEMENTATION_PACKAGE, true); + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + // Imported as-is, not prefixed with the package name + final String apiFile = output + "/src/nodesc/apis/nodesc_api.py"; + TestUtils.assertFileContains(Paths.get(apiFile), "import " + EXTERNAL_IMPL_PKG + "\n"); + TestUtils.assertFileContains(Paths.get(apiFile), "ns_pkg = " + EXTERNAL_IMPL_PKG + "\n"); + TestUtils.assertFileNotContains(Paths.get(apiFile), "nodesc." + EXTERNAL_IMPL_PKG); + + // Nothing is generated in a package the generator does not own + TestUtils.assertFileNotExists(Paths.get(output.getAbsolutePath(), "/src", "/nodesc", "impl", "__init__.py")); + TestUtils.assertFileNotExists(Paths.get(output.getAbsolutePath(), "/src", "/my_company", "/handlers", "__init__.py")); + TestUtils.assertFileNotExists(Paths.get(output.getAbsolutePath(), "/src", "/nodesc", "/my_company", "/handlers", "__init__.py")); } @Test