From 97e07803fd67d34b38cffc4d8f18b1fb2f78b8fb Mon Sep 17 00:00:00 2001 From: Troy Patterson Date: Sun, 23 May 2021 01:18:52 -0700 Subject: [PATCH] Adding usePythonSrcRootInImports logic to AbstractPythonConnexionServerCodegen. (#9558) --- docs/generators/python-aiohttp.md | 1 + docs/generators/python-blueplanet.md | 1 + docs/generators/python-flask.md | 1 + .../languages/AbstractPythonCodegen.java | 1 + .../AbstractPythonConnexionServerCodegen.java | 26 +++- ...tractPythonConnexionServerCodegenTest.java | 114 ++++++++++++++++++ 6 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/codegen/python/AbstractPythonConnexionServerCodegenTest.java diff --git a/docs/generators/python-aiohttp.md b/docs/generators/python-aiohttp.md index 7c46da0b5b44..9adef96ba51f 100644 --- a/docs/generators/python-aiohttp.md +++ b/docs/generators/python-aiohttp.md @@ -23,6 +23,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| |supportPython2|support python2. This option has been deprecated and will be removed in the 5.x release.| |false| |useNose|use the nose test framework| |false| +|usePythonSrcRootInImports|include pythonSrcRoot in import namespaces.| |false| ## IMPORT MAPPING diff --git a/docs/generators/python-blueplanet.md b/docs/generators/python-blueplanet.md index 81c9b89cbf3d..ba461d1822fe 100644 --- a/docs/generators/python-blueplanet.md +++ b/docs/generators/python-blueplanet.md @@ -23,6 +23,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| |supportPython2|support python2. This option has been deprecated and will be removed in the 5.x release.| |false| |useNose|use the nose test framework| |false| +|usePythonSrcRootInImports|include pythonSrcRoot in import namespaces.| |false| ## IMPORT MAPPING diff --git a/docs/generators/python-flask.md b/docs/generators/python-flask.md index 92bf4fdb97fa..9e69fa443023 100644 --- a/docs/generators/python-flask.md +++ b/docs/generators/python-flask.md @@ -23,6 +23,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| |supportPython2|support python2. This option has been deprecated and will be removed in the 5.x release.| |false| |useNose|use the nose test framework| |false| +|usePythonSrcRootInImports|include pythonSrcRoot in import namespaces.| |false| ## IMPORT MAPPING diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java index 2765920fab4f..eced1ac0e782 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java @@ -600,6 +600,7 @@ public String patternCorrection(String pattern) { public void setPackageName(String packageName) { this.packageName = packageName; + additionalProperties.put(CodegenConstants.PACKAGE_NAME, this.packageName); } public void setProjectName(String projectName) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonConnexionServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonConnexionServerCodegen.java index 3898b7c09daf..df60913e61e4 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonConnexionServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonConnexionServerCodegen.java @@ -52,6 +52,7 @@ public abstract class AbstractPythonConnexionServerCodegen extends AbstractPytho // nose is a python testing framework, we use pytest if USE_NOSE is unset public static final String USE_NOSE = "useNose"; public static final String PYTHON_SRC_ROOT = "pythonSrcRoot"; + public static final String USE_PYTHON_SRC_ROOT_IN_IMPORTS = "usePythonSrcRootInImports"; static final String MEDIA_TYPE = "mediaType"; protected int serverPort = 8080; @@ -62,6 +63,7 @@ public abstract class AbstractPythonConnexionServerCodegen extends AbstractPytho protected boolean featureCORS = Boolean.FALSE; protected boolean useNose = Boolean.FALSE; protected String pythonSrcRoot; + protected boolean usePythonSrcRootInImports = Boolean.FALSE; public AbstractPythonConnexionServerCodegen(String templateDirectory, boolean fixBodyNameValue) { super(); @@ -79,7 +81,7 @@ public AbstractPythonConnexionServerCodegen(String templateDirectory, boolean fi typeMapping.put("map", "Dict"); // set the output folder here - outputFolder = "generated-code/connexion"; + outputFolder = "generated-code" + File.separatorChar + "connexion"; apiTemplateFiles.put("controller.mustache", ".py"); modelTemplateFiles.put("model.mustache", ".py"); @@ -132,6 +134,8 @@ public AbstractPythonConnexionServerCodegen(String templateDirectory, boolean fi defaultValue(Boolean.FALSE.toString())); cliOptions.add(new CliOption(PYTHON_SRC_ROOT, "put python sources in this subdirectory of output folder (defaults to \"\" for). Use this for src/ layout."). defaultValue("")); + cliOptions.add(new CliOption(USE_PYTHON_SRC_ROOT_IN_IMPORTS, "include pythonSrcRoot in import namespaces."). + defaultValue("false")); } protected void addSupportingFiles() { @@ -147,7 +151,6 @@ public void processOpts() { setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); } else { setPackageName("openapi_server"); - additionalProperties.put(CodegenConstants.PACKAGE_NAME, this.packageName); } if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) { setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION)); @@ -177,9 +180,19 @@ public void processOpts() { if (additionalProperties.containsKey(USE_NOSE)) { setUseNose((String) additionalProperties.get(USE_NOSE)); } + if (additionalProperties.containsKey(USE_PYTHON_SRC_ROOT_IN_IMPORTS)) { + setUsePythonSrcRootInImports((String) additionalProperties.get(USE_PYTHON_SRC_ROOT_IN_IMPORTS)); + } if (additionalProperties.containsKey(PYTHON_SRC_ROOT)) { - setPythonSrcRoot((String) additionalProperties.get(PYTHON_SRC_ROOT)); - additionalProperties.put(PYTHON_SRC_ROOT, pythonSrcRoot); + String pythonSrcRoot = (String) additionalProperties.get(PYTHON_SRC_ROOT); + if (usePythonSrcRootInImports) { + // if we prepend the package name if the pythonSrcRoot we get the desired effect. + // but we also need to set pythonSrcRoot itself to "" to ensure all the paths are + // what we expect. + setPackageName(pythonSrcRoot + "." + packageName); + pythonSrcRoot = ""; + } + setPythonSrcRoot(pythonSrcRoot); } else { setPythonSrcRoot(""); } @@ -218,6 +231,11 @@ public void setPythonSrcRoot(String val) { } else { this.pythonSrcRoot = pySrcRoot + File.separator; } + additionalProperties.put(PYTHON_SRC_ROOT, StringUtils.defaultIfBlank(this.pythonSrcRoot, null)); + } + + public void setUsePythonSrcRootInImports(String val) { + this.usePythonSrcRootInImports = Boolean.parseBoolean(val); } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/AbstractPythonConnexionServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/AbstractPythonConnexionServerCodegenTest.java new file mode 100644 index 000000000000..0e1e46c347f8 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/AbstractPythonConnexionServerCodegenTest.java @@ -0,0 +1,114 @@ +package org.openapitools.codegen.python; + +import static org.openapitools.codegen.languages.AbstractPythonConnexionServerCodegen.PYTHON_SRC_ROOT; +import static org.openapitools.codegen.languages.AbstractPythonConnexionServerCodegen.USE_PYTHON_SRC_ROOT_IN_IMPORTS; + +import java.io.File; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.languages.AbstractPythonConnexionServerCodegen; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class AbstractPythonConnexionServerCodegenTest { + + /** + * Passing "description" as first param to succinctly identify the significance of test parameters. + */ + @Test(dataProvider = "data") + public void test(String description, Map additionalProperties, String modelName, + ExpectedValues expectedValues) { + AbstractPythonConnexionServerCodegen codegen = new MockAbstractPythonConnexionServerCodegen("", false); + codegen.additionalProperties().putAll(additionalProperties); + codegen.processOpts(); + String pythonSrcRoot = Objects.toString(codegen.additionalProperties().get(PYTHON_SRC_ROOT), null); + Assert.assertEquals(pythonSrcRoot, expectedValues.pythonSrcRoot); + Assert.assertEquals(codegen.apiPackage(), expectedValues.expectedApiPackage); + Assert.assertEquals(codegen.modelFileFolder(), expectedValues.expectedModelFileFolder); + Assert.assertEquals(codegen.apiFileFolder(), expectedValues.expectedApiFileFolder); + Assert.assertEquals(codegen.toModelImport(modelName), expectedValues.expectedImport); + } + + @DataProvider + public Object[][] data() { + return new Object[][]{ + new Object[]{ + "Default setup", + Collections.emptyMap(), + "TestModel", + new ExpectedValues("from openapi_server.models.test_model import TestModel", + "openapi_server.controllers", + platformAgnosticPath("generated-code", "connexion", "openapi_server", "models"), + platformAgnosticPath("generated-code", "connexion", "openapi_server", "controllers"), + null) + }, + new Object[]{ + "Default setup with Python src root", + ImmutableMap.of(PYTHON_SRC_ROOT, "test_root"), + "TestModel", + new ExpectedValues("from openapi_server.models.test_model import TestModel", + "openapi_server.controllers", + platformAgnosticPath("generated-code", "connexion", "test_root", "openapi_server", "models"), + platformAgnosticPath("generated-code", "connexion", "test_root", "openapi_server", "controllers"), + "test_root") + }, + new Object[]{ + "Python src in import", + ImmutableMap.of(PYTHON_SRC_ROOT, "test_root", USE_PYTHON_SRC_ROOT_IN_IMPORTS, "true"), + "TestModel", + new ExpectedValues("from test_root.openapi_server.models.test_model import TestModel", + "test_root.openapi_server.controllers", + platformAgnosticPath("generated-code", "connexion", "test_root", "openapi_server", "models"), + platformAgnosticPath("generated-code", "connexion", "test_root", "openapi_server", "controllers"), + null) + }, + new Object[]{ + "Python src in import with specified package", + ImmutableMap.of(PYTHON_SRC_ROOT, "test_root", + USE_PYTHON_SRC_ROOT_IN_IMPORTS, "true", + CodegenConstants.PACKAGE_NAME, "test_package"), + "TestModel", + new ExpectedValues("from test_root.test_package.models.test_model import TestModel", + "test_root.test_package.controllers", + platformAgnosticPath("generated-code", "connexion", "test_root", "test_package", "models"), + platformAgnosticPath("generated-code", "connexion", "test_root", "test_package", "controllers"), + null) + } + }; + } + + private static String platformAgnosticPath(String... nodes) { + return StringUtils.join(nodes, File.separatorChar); + } + + private static class MockAbstractPythonConnexionServerCodegen extends AbstractPythonConnexionServerCodegen { + public MockAbstractPythonConnexionServerCodegen(String templateDirectory, boolean fixBodyNameValue) { + super(templateDirectory, fixBodyNameValue); + } + } + + private static class ExpectedValues { + public final String expectedImport; + public final String expectedApiPackage; + public final String expectedModelFileFolder; + public final String expectedApiFileFolder; + public final String pythonSrcRoot; + + public ExpectedValues(String expectedImport, String expectedApiPackage, String expectedModelFileFolder, + String expectedApiFileFolder, String pythonSrcRoot) { + this.expectedImport = expectedImport; + this.expectedApiPackage = expectedApiPackage; + this.expectedModelFileFolder = expectedModelFileFolder; + this.expectedApiFileFolder = expectedApiFileFolder; + this.pythonSrcRoot = pythonSrcRoot != null ? pythonSrcRoot + File.separatorChar : null; + } + } + +}