Description
Help, pls! I really don't understand how I can integrate python-dependency-injector
in my project 😔😭
When using a container in my application, cyclical dependencies that look like: Container -> core -> services -> Container
. And I really don't understand how I should change the structure of my application to avoid this.
For example, I have the following Flask application structure (usually core
, extensions
and service
are packages):
app
│ __init__.py # create_app
│ containers.py # Container
│ core.py # reused components (BaseModel, ...) that I want to use in any part of the App (services, api, ...)
│ extensions.py # SQLAlchemy, HTTPTokenAuth, Keycloak, ...
│
├───api # here, wiring works great
│ ...
│
└───services
service.py # Models, Repositories, UseCases for this service
...
__init__.py
__init__.py
contains just the App factory:
from apiflask import APIFlask
from app import containers
def create_app() -> APIFlask:
app = APIFlask(__name__)
...
containers.init_app(app)
...
return app
In the container in containers.py
I want to store both extensions and services:
from apiflask import APIFlask, HTTPTokenAuth
from dependency_injector import containers, providers
from _issue.extensions import SQLAlchemy, Keycloak
from _issue.services.service import ModelRepository, ModelUseCase
CONTAINER_CONFIG = {}
class Container(containers.DeclarativeContainer):
wiring_config = containers.WiringConfiguration(packages=['app.api'], modules=['app.core'])
config = providers.Configuration(strict=True)
db = providers.Singleton( # extension
SQLAlchemy,
...
)
auth = providers.Singleton( # extension
HTTPTokenAuth,
...
)
keycloak = providers.Singleton( # extension
Keycloak,
...
)
model_repo = providers.Factory( # service
ModelRepository,
session=db.provided.session
)
model_use_case = providers.Factory( # service
ModelUseCase,
repository=model_repo
)
def init_app(app: APIFlask) -> None:
container = Container()
container.config.from_dict(CONTAINER_CONFIG)
app.container = container
db = container.db()
db.init_app(app)
...
In core.py
, I keep a template code that I want to reuse from anywhere in the application. This code often requires the use of extension objects (auth
, db
, ...):
from dependency_injector.wiring import Provide
from flask_sqlalchemy import SQLAlchemy
from _issue.containers import Container
from _issue.extensions import Keycloak, HTTPTokenAuth
db: SQLAlchemy = Provide[Container.db] # Provide['db'] -> Provide object
auth: HTTPTokenAuth = Provide[Container.auth] # Provide['auth'] -> Provide object
class BaseModel(db.Model):
__abstract__ = True
...
@auth.verify_token
def verify_token(token: str, keycloak: Keycloak = Provide[Container.keycloak]): # Provide['keycloak'] -> Provide object
userinfo = keycloak.userinfo(token)
...
Using string literals doesn't help!
extensions.py
import typing as t
from apiflask import HTTPTokenAuth
from flask_sqlalchemy import SQLAlchemy
__all__ = ['HTTPTokenAuth', 'SQLAlchemy', 'Keycloak']
class Keycloak:
def userinfo(self, token: str) -> dict[str, t.Any]: ...
services/service.py
import typing as t
from abc import ABC
from sqlalchemy.orm import Session
from _issue.core import BaseModel
class BaseRepository(ABC):
def __init__(self, session: Session, model: t.Type[BaseModel]):
self._session = session
self._model = model
class Model(BaseModel):
...
class ModelRepository(BaseRepository):
def __init__(self, session: Session):
super(ModelRepository, self).__init__(session, Model)
class ModelUseCase:
def __init__(self, repository: ModelRepository):
self._repository = repository
...
With this approach, there is an obvious cyclical dependence: Container -> BaseModel -> ModelRepository -> Container
from app import containers
...
from app import containers
...
from app.services.service_1 import ModelRepository, ModelUseCase
...
from app.core import BaseModel
...
from app.containers import Container
...
ImportError: cannot import name 'Container' from partially initialized module 'app.containers' (most likely due to a circular import) (...\app\containers.py)
What is the best application structure that can save me from this error? Help please 😔😭