If you throw breakpoint()
in your dependency before try
and look at the call tree using w
option you can see that for generator type function next()
is called in contextlib
module code:
[0] c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\threading.py(890)_bootstrap()
-> self._bootstrap_inner()
[1] c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\threading.py(926)_bootstrap_inner()
-> self.run()
[2] c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\threading.py(870)run()
-> self._target(*self._args, **self._kwargs)
[3] c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\concurrent\futures\thread.py(80)_worker()
-> work_item.run()
[4] c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\concurrent\futures\thread.py(57)run()
-> result = self.fn(*self.args, **self.kwargs)
[5] c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\contextlib.py(112)__enter__()
-> return next(self.gen)
Notice that this next()
call is being done inside __enter__
special function that is used by context managers when using with
keyword to open it. Now if step forward through yield
statement and wait until your breakpoint will step in the finally
block where you can look at call tree using w
once again:
[0] c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\threading.py(890)_bootstrap()
-> self._bootstrap_inner()
[1] c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\threading.py(926)_bootstrap_inner()
-> self.run()
[2] c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\threading.py(870)run()
-> self._target(*self._args, **self._kwargs)
[3] c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\concurrent\futures\thread.py(80)_worker()
-> work_item.run()
[4] c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\concurrent\futures\thread.py(57)run()
-> result = self.fn(*self.args, **self.kwargs)
[5] c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\contextlib.py(119)__exit__()
-> next(self.gen)
Now you will notice that next()
is called on your generator function when exiting from context manager object.
You're right about pausing, but state of function is also remembered. So when first next()
in __enter__
is called it yields your session object, then second next()
call resumes function execution thus making it possible to enter inside finally
block.
I think @contextmanager
decorator is used somewhere in FastAPI implementation that takes care of dependencies thus making it possible to use generators in Depends
.
Take a look at this article that takes pretty good explanation how Python generators work.