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:"; + + 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.""" + ...