How to deal with regeneration?
sefasenturk95 opened this issue · 6 comments
Hi! First of all thank you for this generator, seems a lot more easier to use than the openapi-generator. I was wondering how you deal with regeneration when your spec changes? Do you have a working example of how this generator is used in the real world? Thanks!
I use custom template and visitor to implement delegate pattern.
So the generated code looks as below:
@app.post(
'/upload',
response_model=bytes,
responses={'400': {'model': ProblemDetails}, '500': {'model': ProblemDetails}},
)
def upload(
file: UploadFile,
) -> Union[bytes, ProblemDetails]:
return upload_service.upload(file)
Actual implementation is foo_service.upload(). It is never overwritten.
Hope it might help.
@sefasenturk95
Thank you for sponsoring me!!
I agree with @yyamano .
I show you an example to separate abstract methods and implementations.
The example will output Abstract class in abstract.py
we should implement concrete method in concreate.py
abstract.py
...
class Service(ABC):
@staticmethod
@abstractmethod
def post_bar(request: Request) -> None:
...
main.py
from concreate import Service
...
service = Service()
...
@app.post('/bar', response_model=None, tags=['bar'])
def post_bar(request: Request) -> None:
"""
Create a bar
"""
return service.post_bar(request)
...
↓example
fastapi-codegen --input openapi.yaml --output app --template-dir templates --custom-visitor generate_call_expression.py
templates/main.jinja2
from __future__ import annotations
from fastapi import FastAPI
from concreate import Service
{{imports}}
service = Service()
app = FastAPI(
{% if info %}
{% for key,value in info.items() %}
{% set info_value= value.__repr__() %}
{{ key }} = {{info_value}},
{% endfor %}
{% endif %}
)
{% for operation in operations %}
@app.{{operation.type}}('{{operation.path}}', response_model={{operation.response}}
{% if operation.additional_responses %}
, responses={
{% for status_code, models in operation.additional_responses.items() %}
'{{ status_code }}': {
{% for key, model in models.items() %}
'{{ key }}': {{ model }}{% if not loop.last %},{% endif %}
{% endfor %}
}{% if not loop.last %},{% endif %}
{% endfor %}
}
{% endif %}
{% if operation.tags %}
, tags={{operation.tags}}
{% endif %})
def {{operation.function_name}}({{operation.snake_case_arguments}}) -> {{operation.return_type}}:
{%- if operation.summary %}
"""
{{ operation.summary }}
"""
{%- endif %}
return service.{{operation.function_name}}({{call_expressions[operation.function_name]}})
{% endfor %}
call_expressin_visitor.py
import re
from pathlib import Path
from typing import Dict
from fastapi_code_generator.parser import OpenAPIParser
from fastapi_code_generator.visitor import Visitor
def custom_visitor(parser: OpenAPIParser, model_path: Path) -> Dict[str, object]:
call_expressions: Dict[str, str] = {}
for operation in parser.operations.values():
result = re.findall(r'(\w+):', operation.snake_case_arguments)
call_expressions[operation.function_name] = ', '.join(result)
return {'call_expressions': call_expressions}
visit: Visitor = custom_visitor
templates/abstract.jinja2
from abc import ABC, abstractmethod
from models import *
class Service(ABC):
{% for operation in operations %}
@staticmethod
@abstractmethod
def {{operation.function_name}}({{operation.arguments}}) -> {{operation.response}}:
...
{% endfor %}
@koxudaxi Thank you very much for your response! This really helps! The thing is now that I have the generate-router
flag set to true, so I would expect multiple abstract classes in the abstract.py
, but I don't see a forl oop in the templates/abstract.jinja2
file so in the meanwhile I try to add it and see if I can work it out.
I always generate the abstraction class in this way to separate the generated code from the actual file to be edited.
This is a lovely personal use of the code generator, and it would be nice to have this template available from the CLI options as a preset.
This would have been an excellent option to have existed since the initial release.
How do people manage their auto-generated code? 🤔
@sefasenturk95
You can apply the same method to routers.
Creating a new custom template is advisable instead of using templates/main.jinja2
.
You can reference and copy it from modular_template/routers.jinja2.