mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-07-08 08:30:56 +00:00
[python-fastapi] Added a base class for the actual implementation (#14470)
It is very difficult to "merge" the changes, made by code generation, and the changes, made by developers. It would be very useful to separate the generated code and the code written by developers. In addition this would remove the necessity to track the generated code. Pyhton (since 3.6) has a hook, __init_subclasses__, that could be used to solve exactly this problem. The classes from *_base.py should be implemented in an ns package that is specified by the additional parameter ("-p fastapiImplementationPackage=example_name"). Signed-off-by: Nikita Vakula <programmistov.programmist@gmail.com>
This commit is contained in:
parent
b94952b3b7
commit
3db7169959
@ -420,4 +420,6 @@ public class CodegenConstants {
|
||||
"setting this to true. You can do that by:<ul>" +
|
||||
"<li>defining the propertyName as an enum with only one value in the schemas that are in your discriminator map</li>" +
|
||||
"<li>setting additionalProperties: false in your schemas</li></ul>";
|
||||
|
||||
public static final String FASTAPI_IMPLEMENTATION_PACKAGE = "fastapiImplementationPackage";
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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}}
|
||||
|
||||
|
||||
|
31
modules/openapi-generator/src/main/resources/python-fastapi/base_api.mustache
vendored
Normal file
31
modules/openapi-generator/src/main/resources/python-fastapi/base_api.mustache
vendored
Normal file
@ -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}}
|
1
modules/openapi-generator/src/main/resources/python-fastapi/impl_argument.mustache
vendored
Normal file
1
modules/openapi-generator/src/main/resources/python-fastapi/impl_argument.mustache
vendored
Normal file
@ -0,0 +1 @@
|
||||
{{#isPathParam}}{{baseName}}{{/isPathParam}}{{^isPathParam}}{{paramName}}{{/isPathParam}}
|
@ -0,0 +1 @@
|
||||
{{#isPathParam}}{{baseName}}{{/isPathParam}}{{^isPathParam}}{{paramName}}{{/isPathParam}}: {{>param_type}}
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
""""""
|
||||
...
|
@ -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)
|
||||
|
@ -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:
|
||||
""""""
|
||||
...
|
@ -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)
|
||||
|
@ -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."""
|
||||
...
|
Loading…
x
Reference in New Issue
Block a user