Skip to content
\n

The problem is, when I use multiple repositories in an endpoint, a new database session is created for every repository. So when there is an error in one of the repositories, there is no way to rollback all changes. Is there a way to create a single session in a request context and share it across repositories?

\n

I have thought about overriding and resetting the provider in middlewares. But I think it won't work because the container instance is shared between requests.

","upvoteCount":2,"answerCount":7,"acceptedAnswer":{"@type":"Answer","text":"

Sorry for the super late answer @SarloAkrobata, I had some really busy workdays lately. Here is the solution I found:

\n

First, I tried to use ContextLocalSingleton as suggested by @rmk135 , but I couldn't manage to use the same session for the endpoint, dependencies and middlewares. I don't remember exactly, but I think FastAPI was executing middlewares in a separate context. My alternate solution was to create the context manually.

\n
# database.py looks roughly like this\n\n_request_id_ctx_var: contextvars.ContextVar[str] = contextvars.ContextVar('request_id_ctx')\n\ndef get_request_id() -> str:\n    return _request_id_ctx_var.get()\n    \n@contextmanager\ndef db_context(identifier: str):\n    ctx_token = _request_id_ctx_var.set(identifier)\n    yield\n    _request_id_ctx_var.reset(ctx_token)\n\nclass Database:\n    def __init__(self, db_url: str, pool_size: int) -> None:\n        self.db_url = db_url\n        self._engine = create_async_engine(\n            self.db_url,\n            echo=False,\n            pool_size=pool_size,\n        )\n        self._create_factory()\n\n    def _create_factory(self, bind: Union[Connection, Engine, None] = None) -> None:\n        bind = bind or self._engine\n\n        self.async_session_factory = sessionmaker(\n            bind=bind,\n            class_=_AsyncSession,\n            expire_on_commit=False,\n            autoflush=False,\n        )\n        self.AsyncSession = async_scoped_session(self.async_session_factory, scopefunc=get_request_id)\n\n    def create_session(self):\n        return self.AsyncSession()\n\n# In application.py I set up middlewares to set the context\n\n@app.middleware('http')\nasync def remove_db_session(request: Request, call_next):\n    response = await call_next(request)\n\n    db = container.db()\n    await db.AsyncSession.remove()\n\n    return response\n\n@app.middleware('http')\nasync def set_db_context(request: Request, call_next):\n    request_id = str(uuid.uuid4())\n\n    with db_context(identifier=request_id):\n        response = await call_next(request)\n\n    return response\n\n# And in containers.py\nclass Container(containers.DeclarativeContainer):\n    db = providers.Singleton(Database, db_url=config.database.url, pool_size=config.database.pool_size)\n    db_session = providers.Callable(db.provided.create_session.call())\n\n    user_repository = providers.Factory(UserRepository, session=db_session)
\n

Disclaimers:

\n","upvoteCount":1,"url":"https://github.com/ets-labs/python-dependency-injector/discussions/493#discussioncomment-3032798"}}}
Discussion options

You must be logged in to vote

Sorry for the super late answer @SarloAkrobata, I had some really busy workdays lately. Here is the solution I found:

First, I tried to use ContextLocalSingleton as suggested by @rmk135 , but I couldn't manage to use the same session for the endpoint, dependencies and middlewares. I don't remember exactly, but I think FastAPI was executing middlewares in a separate context. My alternate solution was to create the context manually.

# database.py looks roughly like this

_request_id_ctx_var: contextvars.ContextVar[str] = contextvars.ContextVar('request_id_ctx')

def get_request_id() -> str:
    return _request_id_ctx_var.get()
    
@contextmanager
def db_context(identifier: str):
    ctx_token

Replies: 7 comments 2 replies

Comment options

You must be logged in to vote
0 replies
Comment options

You must be logged in to vote
0 replies
Comment options

You must be logged in to vote
0 replies
Comment options

You must be logged in to vote
0 replies
Answer selected by hakanutku
Comment options

You must be logged in to vote
0 replies
Comment options

You must be logged in to vote
2 replies
@JobaDiniz
Comment options

@hakanutku
Comment options

Comment options

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet
6 participants