fastapi-code-generator icon indicating copy to clipboard operation
fastapi-code-generator copied to clipboard

Request/Inquiry: Dependency Injection

Open ridwan-salau opened this issue 1 year ago • 2 comments

Hi Koudai, I want to take the time to appreciate what you have created here. It's incredibly easy to use and amenable to customization.

I am using this awesome tool in a way I think is interesting (maybe to me alone). I modified the templates such that generated codes are never modified. Instead, I create another directory, which I call services, with modules corresponding to each router module. Each service module implements the corresponding endpoints defined in the router module. My templates already ensure that the right service module is imported into the router module and called with the same parameters.

Now the question: I want to implement dependency injection while keeping to the promise of not modifying the generated files but I'm not sure how best to achieve this. So, I would like to know if the way I'm approaching the problem is the right way or there are better ways.

Below, I provide the structure of my API.

api
├── data_models.py
├── database.py
├── dependencies.py
├── main.py
├── models.py
├── routers
│   ├── authentication.py
│   ├── dashboard.py
│   ├── model_execution.py
│   ├── settings.py
│   └── user_management.py
├── services
│   ├── authentication.py
│   ├── dashboard.py
│   ├── model_execution.py
│   ├── settings.py
│   └── user_management.py
└── utils
    └── auth.py

Also, below is routers.jinja2 template

from __future__ import annotations

from fastapi import APIRouter

from ..dependencies import *
{% for operation in operations %}
{% if operation.tags[0] == tag %}
from ..services import {{ operation.tags[0].lower().replace(" ","_") }}
{% endif %}
{% endfor %}


router = APIRouter(
    tags=["{{tag}}"]
    )

{% for operation in operations %}
{% if operation.tags[0] == tag %}
@router.{{operation.type}}("{{operation.snake_case_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 %})
async def {{operation.function_name}}({{operation.snake_case_arguments}}) -> {{operation.return_type}}:
    {%- if operation.summary %}
    """
    {{ operation.summary }}
    """
    {%- endif %}

    {{ operation.tags[0].lower().replace(" ","_") }}.{{ operation.function_name}}({{ operation.snake_case_arguments.split(":")[0] }})

    pass
{% endif %}
{% endfor %}

ridwan-salau avatar Oct 07 '24 22:10 ridwan-salau

Hello!

I did this with a generated Protocol and overriding the DI, relevant code below:

# Generated this code (in different files)

app = FastAPI(...)

router = APIRouter(tags=['Hello'])

# DI a procotol instead of a concrete impl.

class GreetingHandler(Protocol):
    async def create_greeting(self, body: GreetingPostRequest) -> Greeting: ...


ApiHandler = Annotated[GreetingHandler, Depends()]


@router.post(
    '/hello',
    response_model=None,
    responses={'201': {'model': Greeting}},
    tags=['Hello'],
)
async def create_greeting(
        body: GreetingPostRequest,
        handler: ApiHandler
) -> Optional[Greeting]:
    """
    Create Greeting
    """
    return await handler.create_greeting(body)

implement with something like


class GreetingServiceHandler(GreetingHandler):

    async def create_greeting(self, body: GreetingPostRequest) -> Greeting:
        return Greeting(f"hello {body.name} from concrete implementation")

app.dependency_overrides = {GreetingHandler: GreetingServiceHandler} # or a factory function.
app.include_router(router)

krs23 avatar Feb 04 '25 15:02 krs23

@krs23 would you mind sharing how you achieved that? I'm trying to achieve the same!

marcellodesales avatar Mar 11 '25 22:03 marcellodesales