I'm working with a FastAPI project generated by OpenAPI Generator. I want to implement custom logic for my API endpoints, but I'm unsure how to ensure my changes are picked up when running the server.
media_api_python/
└── openapi_server/
├── apis/
│ ├── __init__.py
│ ├── media_files_api.py
│ └── media_files_api_base.py
├── impl/
│ ├── __init__.py
│ └── media_files_api.py
├── main.py
└── models/
├── __init__.py
├── appearance.py
├── bounding_box.py
...
How can I implement custom logic in impl/media_files_api.py
and ensure it's used when running the server with uvicorn openapi_server.main:app --host 0.0.0.0 --port 8080
?
Can you provide a simple "Hello World" implementation of the media_files_mediafile_post
function to demonstrate this approach?
Here's the content of media_files_api_base.py
:
class BaseMediaFilesApi:
subclasses: ClassVar[Tuple] = ()
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
BaseMediaFilesApi.subclasses = BaseMediaFilesApi.subclasses + (cls,)
def media_files_mediafile_post(self, file: str) -> object:
...
And here's the relevant part of media_files_api.py
:
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("/MediaFiles/mediafile", ...)
async def media_files_mediafile_post(file: str = Form(None, description="")) -> object:
...
I'd appreciate any guidance on how to properly implement and integrate custom logic in this OpenAPI-generated FastAPI project. Thanks in advance!
In my attempt at solving it, I created this file impl/media_files_api.py
with the following code, with the expectation that it would somehow be picked up and would override the corresponding endpoints in apis/media_files_api.py
, but when I run curl -X POST "http://localhost:8080/MediaFiles/mediafile" -H "Content-Type: application/x-www-form-urlencoded" --data-urlencode "file=$(< setup.cfg)"
, it returns null
, instead of {"Hello": "World: setup.cfg"}
from openapi_server.apis.media_files_api_base import *
class MediaFilesApiImpl(BaseMediaFilesApi):
def media_files_mediafile_post(self, file: str):
print({"Hello": f"World: {file}"}, file)
return {"Hello": f"World: {file}"}
I figured out that the default OpenAPI-provided mustache template used to generate the API file, media_files_api.py
, has a bug as it fails to generate the return statements for all the endpoints. For the endpoint in the question, the return statement looks like this:
return BaseMediaFilesApi.subclasses[0]().media_files_mediafile_post(file)
To fix this, I had to modify the api.mustache file by substituting L67-L68,
{{#notes}}"""{{.}}"""
return Base{{classname}}.subclasses[0]().{{operationId}}({{#allParams}}{{>impl_argument}}{{^-last}}, {{/-last}}{{/allParams}}){{/notes}}{{^notes}}...{{/notes}}
with
return Base{{classname}}.subclasses[0]().{{operationId}}({{#allParams}}{{>impl_argument}}{{^-last}}, {{/-last}}{{/allParams}})
Bonus: I needed to fix many issues relating to non-primitive model types and a few other customizations without having to directly modify any of the generated files. I found modifying the mustache template the easiest way to achieve that. Additionally, do watch out for template composition –– thus, what you need to change might, for example, be a subfile that is inserted into the api.mustache
file.