Home > Software engineering >  How is passing a generator to Depends make the generator act like a contextmanager?
How is passing a generator to Depends make the generator act like a contextmanager?

Time:02-04

I was going through a tutorial on fast api and I came across something like below

def get_db():
    try:
        db = SessionLocal()
        yield db
    finally:
        print("from finally block")
        db.close()
@app.get("/")
async def read_all(db: Session = Depends(get_db)):
    res = db.query(models.Todos).all()
    print("from endpoint")
    return res

result

INFO:     127.0.0.1:39088 - "GET /openapi.json HTTP/1.1" 200 OK
from endpoint
INFO:     127.0.0.1:39088 - "GET / HTTP/1.1" 200 OK
from finally block

why does Depends(get_db) seem to act like somekind of contextmanager?. the "from finally block" print statement does not get executed until the end of the read_all method

doing something like


class SomeDependency:
    def __enter__(self):
        print("entering")
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exited")

def hello():
    try:
        yield SomeDependency()
    finally:
        print("yolo")

if __name__ == "__main__":
   next(hello())

the finally block gets executed immediately after the call to next.

what why does the finally block of the get_db not execute immediately when passed to Depends?

CodePudding user response:

You don't seem to clearly understand the concept of the context manager and the generator. Basically they are totally unrelated different concepts. I'll briefly explain them.

The context manager is a part of the with statement feature, which is a syntax sugar for boiler plate code of try/finally block ensuring to finalize things. See the reference for detail. In your second example, no context manager is involved because there's no with statement.

And the generator is a syntax sugar for boiler plate code of defining an iterator.(But it's a quite complex feature called coroutine from the implementation point of view.) See the reference for detail. In your second example, the finally clause will be executed when an iterator returned by the hello() is finalized(when the reference count reaches zero).

Although I said the two are totally unrelated before, there exists a little relation actually - a syntax sugar for boiler plate code of defining a context manager class, defining a context manager via a generator, like the following.(It's scrapped from the reference and edited).

from contextlib import contextmanager

@contextmanager
def managed_resource(...):
    resource = acquire_resource(...)
    try:
        yield resource
    finally:
        release_resource(resource)

with managed_resource(...) as resource:
    ...

And returning the focus to the FastAPI Depends, if you give a generator as an argument, it will internally create a context manager using the above feature. You can see the implementation.

  • Related