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 841adbf3b6..43278b02e5 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
@@ -420,4 +420,6 @@ public class CodegenConstants {
"setting this to true. You can do that by:
" +
"- defining the propertyName as an enum with only one value in the schemas that are in your discriminator map
" +
"- setting additionalProperties: false in your schemas
";
+
+ public static final String FASTAPI_IMPLEMENTATION_PACKAGE = "fastapiImplementationPackage";
}
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 76c13906f0..b441fd3de1 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
@@ -65,6 +65,7 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
protected String sourceFolder;
+ private static final String BASE_CLASS_SUFFIX = "base";
private static final String SERVER_PORT = "serverPort";
private static final String NAME = "python-fastapi";
private static final int DEFAULT_SERVER_PORT = 8080;
@@ -72,6 +73,8 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
private static final String DEFAULT_SOURCE_FOLDER = "src";
private static final String DEFAULT_PACKAGE_VERSION = "1.0.0";
+ private String implPackage;
+
@Override
public CodegenType getTag() {
return CodegenType.SERVER;
@@ -99,8 +102,10 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
* are available in models, apis, and supporting files
*/
additionalProperties.put("serverPort", DEFAULT_SERVER_PORT);
+ additionalProperties.put("baseSuffix", BASE_CLASS_SUFFIX);
additionalProperties.put(CodegenConstants.SOURCE_FOLDER, DEFAULT_SOURCE_FOLDER);
additionalProperties.put(CodegenConstants.PACKAGE_NAME, DEFAULT_PACKAGE_NAME);
+ additionalProperties.put(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, DEFAULT_PACKAGE_NAME.concat(".impl"));
languageSpecificPrimitives.add("List");
languageSpecificPrimitives.add("Dict");
@@ -110,10 +115,12 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
outputFolder = "generated-code" + File.separator + NAME;
modelTemplateFiles.put("model.mustache", ".py");
apiTemplateFiles.put("api.mustache", ".py");
+ apiTemplateFiles.put("base_api.mustache", "_".concat(BASE_CLASS_SUFFIX).concat(".py"));
embeddedTemplateDir = templateDir = NAME;
apiPackage = "apis";
modelPackage = "models";
testPackage = "tests";
+ implPackage = DEFAULT_PACKAGE_NAME.concat(".impl");
apiTestTemplateFiles().put("api_test.mustache", ".py");
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "python package name (convention: snake_case).")
@@ -124,6 +131,8 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
.defaultValue(String.valueOf(DEFAULT_SERVER_PORT)));
cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, "directory for generated python source code")
.defaultValue(DEFAULT_SOURCE_FOLDER));
+ cliOptions.add(new CliOption(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, "python package name for the implementation code (convention: snake_case).")
+ .defaultValue(DEFAULT_PACKAGE_NAME.concat(".impl")));
}
@@ -139,6 +148,10 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
this.sourceFolder = ((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER));
}
+ if (additionalProperties.containsKey(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE)) {
+ this.implPackage = ((String) additionalProperties.get(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE));
+ }
+
modelPackage = packageName + "." + modelPackage;
apiPackage = packageName + "." + apiPackage;
diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/api.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/api.mustache
index 3adf3a2de3..400685f0ab 100644
--- a/modules/openapi-generator/src/main/resources/python-fastapi/api.mustache
+++ b/modules/openapi-generator/src/main/resources/python-fastapi/api.mustache
@@ -1,6 +1,11 @@
# coding: utf-8
from typing import Dict, List # noqa: F401
+import importlib
+import pkgutil
+
+from {{apiPackage}}.{{classFilename}}_{{baseSuffix}} import Base{{classname}}
+import {{fastapiImplementationPackage}}
from fastapi import ( # noqa: F401
APIRouter,
@@ -24,6 +29,10 @@ from {{modelPackage}}.extra_models import TokenModel # noqa: F401
router = APIRouter()
+ns_pkg = {{fastapiImplementationPackage}}
+for _, name, _ in pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + "."):
+ importlib.import_module(name)
+
{{#operations}}
{{#operation}}
@@ -56,7 +65,7 @@ async def {{operationId}}(
{{/hasAuthMethods}}
) -> {{returnType}}{{^returnType}}None{{/returnType}}:
{{#notes}}"""{{.}}"""
- ...{{/notes}}{{^notes}}...{{/notes}}
+ return Base{{classname}}.subclasses[0]().{{operationId}}({{#allParams}}{{>impl_argument}}{{^-last}}, {{/-last}}{{/allParams}}){{/notes}}{{^notes}}...{{/notes}}
{{^-last}}
diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/base_api.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/base_api.mustache
new file mode 100644
index 0000000000..2e80168a32
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/python-fastapi/base_api.mustache
@@ -0,0 +1,31 @@
+# coding: utf-8
+
+from typing import ClassVar, Dict, List, Tuple # noqa: F401
+
+{{#imports}}
+{{import}}
+{{/imports}}
+{{#securityImports.0}}from {{packageName}}.security_api import {{#securityImports}}get_token_{{.}}{{^-last}}, {{/-last}}{{/securityImports}}{{/securityImports.0}}
+
+class Base{{classname}}:
+ subclasses: ClassVar[Tuple] = ()
+
+ def __init_subclass__(cls, **kwargs):
+ super().__init_subclass__(**kwargs)
+ Base{{classname}}.subclasses = Base{{classname}}.subclasses + (cls,)
+{{#operations}}
+{{#operation}}
+ def {{operationId}}(
+ self,
+ {{#allParams}}
+ {{>impl_argument_definition}},
+ {{/allParams}}
+ ) -> {{returnType}}{{^returnType}}None{{/returnType}}:
+ {{#notes}}"""{{.}}"""
+ ...{{/notes}}{{^notes}}...{{/notes}}
+{{^-last}}
+
+
+{{/-last}}
+{{/operation}}
+{{/operations}}
diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/impl_argument.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/impl_argument.mustache
new file mode 100644
index 0000000000..e4da6199be
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/python-fastapi/impl_argument.mustache
@@ -0,0 +1 @@
+{{#isPathParam}}{{baseName}}{{/isPathParam}}{{^isPathParam}}{{paramName}}{{/isPathParam}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/python-fastapi/impl_argument_definition.mustache b/modules/openapi-generator/src/main/resources/python-fastapi/impl_argument_definition.mustache
new file mode 100644
index 0000000000..9bc74d63bf
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/python-fastapi/impl_argument_definition.mustache
@@ -0,0 +1 @@
+{{#isPathParam}}{{baseName}}{{/isPathParam}}{{^isPathParam}}{{paramName}}{{/isPathParam}}: {{>param_type}}
\ No newline at end of file
diff --git a/samples/server/petstore/python-fastapi/.openapi-generator/FILES b/samples/server/petstore/python-fastapi/.openapi-generator/FILES
index 1d9f0c5310..db9bf70021 100644
--- a/samples/server/petstore/python-fastapi/.openapi-generator/FILES
+++ b/samples/server/petstore/python-fastapi/.openapi-generator/FILES
@@ -9,8 +9,11 @@ requirements.txt
setup.cfg
src/openapi_server/apis/__init__.py
src/openapi_server/apis/pet_api.py
+src/openapi_server/apis/pet_api_base.py
src/openapi_server/apis/store_api.py
+src/openapi_server/apis/store_api_base.py
src/openapi_server/apis/user_api.py
+src/openapi_server/apis/user_api_base.py
src/openapi_server/main.py
src/openapi_server/models/__init__.py
src/openapi_server/models/api_response.py
diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py
index b5fdb300c7..e07db9a7cc 100644
--- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py
+++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api.py
@@ -1,6 +1,11 @@
# coding: utf-8
from typing import Dict, List # noqa: F401
+import importlib
+import pkgutil
+
+from openapi_server.apis.pet_api_base import BasePetApi
+import openapi_server.impl
from fastapi import ( # noqa: F401
APIRouter,
@@ -23,6 +28,10 @@ from openapi_server.security_api import get_token_petstore_auth, get_token_api_k
router = APIRouter()
+ns_pkg = openapi_server.impl
+for _, name, _ in pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + "."):
+ importlib.import_module(name)
+
@router.post(
"/pet",
@@ -41,7 +50,7 @@ async def add_pet(
),
) -> Pet:
""""""
- ...
+ return BasePetApi.subclasses[0]().add_pet(pet)
@router.delete(
@@ -61,7 +70,7 @@ async def delete_pet(
),
) -> None:
""""""
- ...
+ return BasePetApi.subclasses[0]().delete_pet(petId, api_key)
@router.get(
@@ -81,7 +90,7 @@ async def find_pets_by_status(
),
) -> List[Pet]:
"""Multiple status values can be provided with comma separated strings"""
- ...
+ return BasePetApi.subclasses[0]().find_pets_by_status(status)
@router.get(
@@ -101,7 +110,7 @@ async def find_pets_by_tags(
),
) -> List[Pet]:
"""Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing."""
- ...
+ return BasePetApi.subclasses[0]().find_pets_by_tags(tags)
@router.get(
@@ -122,7 +131,7 @@ async def get_pet_by_id(
),
) -> Pet:
"""Returns a single pet"""
- ...
+ return BasePetApi.subclasses[0]().get_pet_by_id(petId)
@router.put(
@@ -144,7 +153,7 @@ async def update_pet(
),
) -> Pet:
""""""
- ...
+ return BasePetApi.subclasses[0]().update_pet(pet)
@router.post(
@@ -165,7 +174,7 @@ async def update_pet_with_form(
),
) -> None:
""""""
- ...
+ return BasePetApi.subclasses[0]().update_pet_with_form(petId, name, status)
@router.post(
@@ -186,4 +195,4 @@ async def upload_file(
),
) -> ApiResponse:
""""""
- ...
+ return BasePetApi.subclasses[0]().upload_file(petId, additional_metadata, file)
diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api_base.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api_base.py
new file mode 100644
index 0000000000..e8dfbc4f5c
--- /dev/null
+++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/pet_api_base.py
@@ -0,0 +1,81 @@
+# coding: utf-8
+
+from typing import ClassVar, Dict, List, Tuple # noqa: F401
+
+from openapi_server.models.api_response import ApiResponse
+from openapi_server.models.pet import Pet
+from openapi_server.security_api import get_token_petstore_auth, get_token_api_key
+
+class BasePetApi:
+ subclasses: ClassVar[Tuple] = ()
+
+ def __init_subclass__(cls, **kwargs):
+ super().__init_subclass__(**kwargs)
+ BasePetApi.subclasses = BasePetApi.subclasses + (cls,)
+ def add_pet(
+ self,
+ pet: Pet,
+ ) -> Pet:
+ """"""
+ ...
+
+
+ def delete_pet(
+ self,
+ petId: int,
+ api_key: str,
+ ) -> None:
+ """"""
+ ...
+
+
+ def find_pets_by_status(
+ self,
+ status: List[str],
+ ) -> List[Pet]:
+ """Multiple status values can be provided with comma separated strings"""
+ ...
+
+
+ def find_pets_by_tags(
+ self,
+ tags: List[str],
+ ) -> List[Pet]:
+ """Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing."""
+ ...
+
+
+ def get_pet_by_id(
+ self,
+ petId: int,
+ ) -> Pet:
+ """Returns a single pet"""
+ ...
+
+
+ def update_pet(
+ self,
+ pet: Pet,
+ ) -> Pet:
+ """"""
+ ...
+
+
+ def update_pet_with_form(
+ self,
+ petId: int,
+ name: str,
+ status: str,
+ ) -> None:
+ """"""
+ ...
+
+
+ def upload_file(
+ self,
+ petId: int,
+ additional_metadata: str,
+ file: str,
+ ) -> ApiResponse:
+ """"""
+ ...
diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py
index 63315cfe54..585a671bba 100644
--- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py
+++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api.py
@@ -1,6 +1,11 @@
# coding: utf-8
from typing import Dict, List # noqa: F401
+import importlib
+import pkgutil
+
+from openapi_server.apis.store_api_base import BaseStoreApi
+import openapi_server.impl
from fastapi import ( # noqa: F401
APIRouter,
@@ -22,6 +27,10 @@ from openapi_server.security_api import get_token_api_key
router = APIRouter()
+ns_pkg = openapi_server.impl
+for _, name, _ in pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + "."):
+ importlib.import_module(name)
+
@router.delete(
"/store/order/{orderId}",
@@ -37,7 +46,7 @@ async def delete_order(
orderId: str = Path(None, description="ID of the order that needs to be deleted"),
) -> None:
"""For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors"""
- ...
+ return BaseStoreApi.subclasses[0]().delete_order(orderId)
@router.get(
@@ -55,7 +64,7 @@ async def get_inventory(
),
) -> Dict[str, int]:
"""Returns a map of status codes to quantities"""
- ...
+ return BaseStoreApi.subclasses[0]().get_inventory()
@router.get(
@@ -73,7 +82,7 @@ async def get_order_by_id(
orderId: int = Path(None, description="ID of pet that needs to be fetched", ge=1, le=5),
) -> Order:
"""For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions"""
- ...
+ return BaseStoreApi.subclasses[0]().get_order_by_id(orderId)
@router.post(
@@ -90,4 +99,4 @@ async def place_order(
order: Order = Body(None, description="order placed for purchasing the pet"),
) -> Order:
""""""
- ...
+ return BaseStoreApi.subclasses[0]().place_order(order)
diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api_base.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api_base.py
new file mode 100644
index 0000000000..f4436a6f07
--- /dev/null
+++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/store_api_base.py
@@ -0,0 +1,42 @@
+# coding: utf-8
+
+from typing import ClassVar, Dict, List, Tuple # noqa: F401
+
+from openapi_server.models.order import Order
+from openapi_server.security_api import get_token_api_key
+
+class BaseStoreApi:
+ subclasses: ClassVar[Tuple] = ()
+
+ def __init_subclass__(cls, **kwargs):
+ super().__init_subclass__(**kwargs)
+ BaseStoreApi.subclasses = BaseStoreApi.subclasses + (cls,)
+ def delete_order(
+ self,
+ orderId: str,
+ ) -> None:
+ """For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors"""
+ ...
+
+
+ def get_inventory(
+ self,
+ ) -> Dict[str, int]:
+ """Returns a map of status codes to quantities"""
+ ...
+
+
+ def get_order_by_id(
+ self,
+ orderId: int,
+ ) -> Order:
+ """For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions"""
+ ...
+
+
+ def place_order(
+ self,
+ order: Order,
+ ) -> Order:
+ """"""
+ ...
diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py
index b1073aa1f7..321732cc5e 100644
--- a/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py
+++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api.py
@@ -1,6 +1,11 @@
# coding: utf-8
from typing import Dict, List # noqa: F401
+import importlib
+import pkgutil
+
+from openapi_server.apis.user_api_base import BaseUserApi
+import openapi_server.impl
from fastapi import ( # noqa: F401
APIRouter,
@@ -22,6 +27,10 @@ from openapi_server.security_api import get_token_api_key
router = APIRouter()
+ns_pkg = openapi_server.impl
+for _, name, _ in pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + "."):
+ importlib.import_module(name)
+
@router.post(
"/user",
@@ -39,7 +48,7 @@ async def create_user(
),
) -> None:
"""This can only be done by the logged in user."""
- ...
+ return BaseUserApi.subclasses[0]().create_user(user)
@router.post(
@@ -58,7 +67,7 @@ async def create_users_with_array_input(
),
) -> None:
""""""
- ...
+ return BaseUserApi.subclasses[0]().create_users_with_array_input(user)
@router.post(
@@ -77,7 +86,7 @@ async def create_users_with_list_input(
),
) -> None:
""""""
- ...
+ return BaseUserApi.subclasses[0]().create_users_with_list_input(user)
@router.delete(
@@ -97,7 +106,7 @@ async def delete_user(
),
) -> None:
"""This can only be done by the logged in user."""
- ...
+ return BaseUserApi.subclasses[0]().delete_user(username)
@router.get(
@@ -115,7 +124,7 @@ async def get_user_by_name(
username: str = Path(None, description="The name that needs to be fetched. Use user1 for testing."),
) -> User:
""""""
- ...
+ return BaseUserApi.subclasses[0]().get_user_by_name(username)
@router.get(
@@ -133,7 +142,7 @@ async def login_user(
password: str = Query(None, description="The password for login in clear text"),
) -> str:
""""""
- ...
+ return BaseUserApi.subclasses[0]().login_user(username, password)
@router.get(
@@ -151,7 +160,7 @@ async def logout_user(
),
) -> None:
""""""
- ...
+ return BaseUserApi.subclasses[0]().logout_user()
@router.put(
@@ -172,4 +181,4 @@ async def update_user(
),
) -> None:
"""This can only be done by the logged in user."""
- ...
+ return BaseUserApi.subclasses[0]().update_user(username, user)
diff --git a/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api_base.py b/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api_base.py
new file mode 100644
index 0000000000..ee3314e7fa
--- /dev/null
+++ b/samples/server/petstore/python-fastapi/src/openapi_server/apis/user_api_base.py
@@ -0,0 +1,76 @@
+# coding: utf-8
+
+from typing import ClassVar, Dict, List, Tuple # noqa: F401
+
+from openapi_server.models.user import User
+from openapi_server.security_api import get_token_api_key
+
+class BaseUserApi:
+ subclasses: ClassVar[Tuple] = ()
+
+ def __init_subclass__(cls, **kwargs):
+ super().__init_subclass__(**kwargs)
+ BaseUserApi.subclasses = BaseUserApi.subclasses + (cls,)
+ def create_user(
+ self,
+ user: User,
+ ) -> None:
+ """This can only be done by the logged in user."""
+ ...
+
+
+ def create_users_with_array_input(
+ self,
+ user: List[User],
+ ) -> None:
+ """"""
+ ...
+
+
+ def create_users_with_list_input(
+ self,
+ user: List[User],
+ ) -> None:
+ """"""
+ ...
+
+
+ def delete_user(
+ self,
+ username: str,
+ ) -> None:
+ """This can only be done by the logged in user."""
+ ...
+
+
+ def get_user_by_name(
+ self,
+ username: str,
+ ) -> User:
+ """"""
+ ...
+
+
+ def login_user(
+ self,
+ username: str,
+ password: str,
+ ) -> str:
+ """"""
+ ...
+
+
+ def logout_user(
+ self,
+ ) -> None:
+ """"""
+ ...
+
+
+ def update_user(
+ self,
+ username: str,
+ user: User,
+ ) -> None:
+ """This can only be done by the logged in user."""
+ ...