Usage of ContextVar in Fastapi #8628
Replies: 15 comments 11 replies
-
I guess it is secure but i dont recommand to do this. The problem should be in |
Beta Was this translation helpful? Give feedback.
-
Thanks a lot for the answer. Yeah i through so aswell, that the problem - if there is one - lies in fastapi, unfortunately i couldn't find anything, which would indicate that it would(n't) cause a problem. Is there a reason why you would not suggest doing doing this? |
Beta Was this translation helpful? Give feedback.
-
contextvars is low level method. It is better to make a higher level method in fastapi framework. I’ve found one project called starlette_context. Maybe it works |
Beta Was this translation helpful? Give feedback.
-
In my project the instance of db session is stored in a request.state so no need to worry about sharing it across users. Got middleware to set it and handy dependency for endpoints. Here is short summary:
Got also two other middlewares (authentication, logging) that share some data using request.state scope. However I am not an expert of SQL Alchemy so would appreciate comments about disadvantages of this solution if any. |
Beta Was this translation helpful? Give feedback.
-
Mhm i mean your solution is better than the default, where you handover each object individually, but you still have to hand over the request for every function... This is what i want to avoid with the ContextVar, where i can access the objects for each request through simple objects without any handover |
Beta Was this translation helpful? Give feedback.
-
@Sharleedah I'm wondering what was your final solution. Have you used ContextVar? Any issues? |
Beta Was this translation helpful? Give feedback.
-
Hey @Elfpkck |
Beta Was this translation helpful? Give feedback.
-
If others are looking for an answer here, it worked for me to set a ContextVar (which is available to the path operation) as an async dependency to the path operation since FastAPI calls async deps in the same execution context as the path operation. A similar example for setting database state can be found in the FastAPI docs here |
Beta Was this translation helpful? Give feedback.
-
Thanks a lot @Sharleedah. Seems your code works with none async session as well @app.middleware("http")
async def add_middleware(request: Request, call_next):
from backend.contexts import db
from backend.contexts import request as ctx_request
from backend.database import session
token = ctx_request.set(request)
with session() as db_session:
# with db_session.begin(): # this raises sqlalchemy.exc.InvalidRequestError in sync mode
db_token = db.set(db_session)
response = await call_next(request)
db.reset(db_token)
ctx_request.reset(token)
return response I do need the current db session in my modell.py for recursive cte and did not find any other good solution to include this property in my pedantic schema. Your solution just saved my day. class Item(Model):
...
@property
def descandants(self):
top_query = select(Item).where(Item.id == self.id).cte("cte", recursive=True)
bottom_query = select(Item).join(top_query, Item.ancestor_id == top_query.c.id)
recursive_query = top_query.union_all(bottom_query)
query = select(aliased(Item, recursive_query))
return db.session.execute(query).scalars().all()
... |
Beta Was this translation helpful? Give feedback.
-
@Sharleedah this looks good. I wonder where in code of FastAPI / uvcorn / asyncio the new context gets created. Did anyone dive to the bottom of this? |
Beta Was this translation helpful? Give feedback.
-
@Sharleedah could you explain why you need to create two contextvars (one for the request and the other for the db session)? Wouldn't just one ( |
Beta Was this translation helpful? Give feedback.
-
I am currently experimenting this Dependency to set the Context, and until know I am not facing issues, but any feedback is very welcome Database Session Geenerator: @asynccontextmanager
async def get_async_db_session():
async with SessionLocal() as session:
try:
yield session
await session.commit()
except: # noqa
await session.rollback()
raise
finally:
await session.close() Dependency async def context_var_db():
async with get_async_db_session() as session:
token = db_session.set(session)
yield
db_session.reset(token) route: @app.get("/contextvar", dependencies=[Depends(context_var_db)])
async def print_context_var_session_id():
print("Enjoying my session id", id(db_session.get()) |
Beta Was this translation helpful? Give feedback.
-
@Sharleedah how do you deal with commits and rollbacks? For example, suppose that you call two functions in your code: @app.get("/some-endpoint")
async def some_endpoint():
function_a()
function_b() Each of these functions needs to store a new item on the database, but if |
Beta Was this translation helpful? Give feedback.
-
@Sharleedah is it possible to use |
Beta Was this translation helpful? Give feedback.
-
We have been using FastAPI in production for 2 years now. There are a few use cases which are similar to this and I haven't found a great solution within the framework. Some of our requests use HTTP clients built using async def get_email_client():
client = EmailClient()
try:
yield client
finally:
await client.aclose() It would be amazing to have access to the cached version of this dependency outside of the call chain. The main option right now is to put it into a contextvar, and then depending on that contextvar through some function. The function has to handle the contextvar parts and the conditional cleanup behavior if you're outside of the request context. Building our own context management conflicts with FastAPIs ability to cache dependencies and clean them up at the end of the endpoint function. It would be nice to have some semantics like: from fastapi.dependencies.cache import get_container
async def do_something_with_stripe_that_may_or_may_not_happen_in_a_request():
async with get_container() as container:
email_client: EmailClient = container.get(Depends(get_email_client))
await email_client.send()
# end of with block could clean up dependencies if outside of request context This would also help a lot with testing, as the tests could create their own dependencies and pass into endpoint functions, and still cleanup afterwards. This is a really common sort of problem for things like emails, api clients, database access, etc. It would be awesome to get access to the dependency cache. If there is any interest from @tiangolo - I can look into contributing it. I remember reading some other similar threads but I can't remember if this was deemed possible or if the maintainers think it conflicts with their vision. |
Beta Was this translation helpful? Give feedback.
-
First Check
Commit to Help
Example Code
Description
I am building an application with FastAPI for 200.000 users, where at some point at 10.000 are accessing the application at the same time. Fastapi will be of course be deployed with gunicorn/uvicorn with auto scaling and a loadbalancer in the aws. But there is one question, which i couldn't solve. I do not get any errors but i cannot rule out that there can be a problem, when people use the application at the same time.
Question:
Is the usage of ContextVar (see defintion in init.py and usage in main.py) secure, when many people access the api at the same time (thread etc. wise)? So that the database session or the request are set correctly in the http middleware, that i can access those contexts from any point in the application (when working on a request) without having to fear that may a function get a request context from another user (authentication etc. wise)
Or do you have a better solution in mind?
Operating System
Linux, Windows, macOS, Other
Operating System Details
No response
FastAPI Version
0.75.0
Python Version
3.10.0
Additional Context
No response
Beta Was this translation helpful? Give feedback.
All reactions