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.