koxudaxi/fastapi-code-generator

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.