Yield inside with block keep open postgres connection

Viewed 621

When yielding from the below generator, is the postgres connection kept active or does the yield need to be less indented such that a new connection is made on every next()?

def getData(start, end):
    with psycopg2.connect("dbname='test' user='user' host='localhost' password='password'") as conn:
        time = start
        while time<end:
            with conn.cursor() as cur:
                cur.execute(query.format(start, time))
                data = cur.fetchall()
            time += one_week
            yield data
1 Answers

Yes, the context manager is kept active. yield pauses the function, nothing is exited.

yield doesn't alter the order of execution inside a function. The function is merely 'paused', frozen at the point where the yield expression has executed and produced a value. When the generator is later on resumed (by calling __next__ on the iterator), the function continues at that point again. A with statement __exit__ method can't be called when a generator is paused, so the context manager can't be exited until the generator is resumed, anyway.

You can see this happen if you create a simple context manager with the @contextmanager decorator (itself relying on a generator for the implementation!):

import sys
from contextlib import contextmanager

@contextmanager
def loud_contextmanager():
    print("Context entered!")
    try:
        yield "value to bind the 'as' target to"
    finally:
        exc_info = sys.exc_info()
        if exc_info:
            print("Context exited with an exception!", exc_info)
        else:
            print("Context exited!")

def generator_stages():
    yield "First yield, outside of a context manage"
    with loud_contextmanager() as cm_as_value:
        yield f"Inside of the with block, received {cm_as_value}"
    yield "Outside of with block, last yield"

And when you pull values from the generator to print, you'll see this:

>>> gen = generator_stages()
>>> print(next(gen))
First yield, outside of a context manage
>>> print(next(gen))
Context entered!
Inside of the with block, received value to bind the 'as' target to
>>> print(next(gen))
Context exited with an exception! (None, None, None)
Outside of with block, last yield
>>> next(gen, "generator was done")
'generator was done'

Note that the context is not exited until we retrieve the 3rd value! After the second next() call the code is paused at a point inside of the with block, and only when unpaused can the context be exited, and the finally suite of the loud_contextmanager() function can be run.

Related