Replies: 6 comments
-
Hi @ShoorDay, The problem not with An example regarding this problem: from asyncio import run, create_task
from contextvars import ContextVar
from typing import Optional
VAR: ContextVar[Optional[str]] = ContextVar('VAR', default=None)
async def child():
print(f'before setting VAR: {VAR.get()}')
VAR.set('value')
print(f'after setting VAR: {VAR.get()}')
async def main():
print(f'before call child: {VAR.get()}')
await create_task(child())
print(f'after call child: {VAR.get()}')
if __name__ == '__main__':
run(main()) The result will be:
In order to make your code working using from contextvars import ContextVar
from dataclasses import dataclass
from typing import Optional
from fastapi import Request
from sqlalchemy.orm import Session
from sqlalchemy import create_engine
from starlette.middleware.base import BaseHTTPMiddleware
from src.config import settings
engine = create_engine(
settings.SQLALCHEMY_DATABASE_URI,
)
session_var: ContextVar[Optional['SessionHolder']] = ContextVar("session", default=None)
@dataclass
class SessionHolder:
session: Optional[Session] = None
@property
def is_session_inited(self) -> bool:
return self.session is not None
def get_session(self) -> Session:
if self.session is None:
# self.session = Session(engine, future=True)
self.session = Session(engine)
print("create a session: ", self.session)
print("return a session: ", self.session)
return self.session
def get_session() -> Session:
holder = session_var.get()
return holder.get_session()
class DBSessionMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
session_var.set(SessionHolder())
res = await call_next(request)
print("session in context: ", session_var.get())
if (holder := session_var.get()).is_session_inited:
holder.get_session().close()
print("close the session: ", holder)
return res If you have any questions or maybe I missed smth, feel free to ask) |
Beta Was this translation helpful? Give feedback.
-
@uriyyo thx! |
Beta Was this translation helpful? Give feedback.
-
Hello @uriyyo ! Thanks for the code you provided. How does it compare to using |
Beta Was this translation helpful? Give feedback.
-
Hi @cglacet, The main advantage of using If take into account example above, you can access to async def some_route():
session = get_session()
# do some staff with session For more information please follow PEP-567 |
Beta Was this translation helpful? Give feedback.
-
@uriyyo oh ok, I see, never heard of this construct in python, that looks interesting. If I get this correctly each asynchronous task will get its own context. I have no idea how Starlette dispatches requests, but I guess each request runs on a single task so it means we get a request based context for free? Here is a small example that tests contextvars in case someone is interested: import asyncio
import random
from contextvars import ContextVar
index_context = ContextVar("index", default=None)
async def test(index):
index_context.set(index)
await asyncio.sleep(random.randint(1, 10)/10)
return index == index_context.get()
success = await asyncio.gather(*[test(i) for i in range(10)])
assert all(success) |
Beta Was this translation helpful? Give feedback.
-
@cglacet As far as I know, for instance, |
Beta Was this translation helpful? Give feedback.
-
Checklist
master
.Describe the bug
I try to manage database session with
ContextVar
and want to close it in the middleware, but I always getnone
unless the session was set in the middleware (This is valid, but not all handlers require sqlalchemy session).To reproduce
Expected behavior
Get the session if it is used in the handler, close it
Actual behavior
Always get none
Debugging material
Environment
Additional context
Beta Was this translation helpful? Give feedback.
All reactions