I'm trying to create a class method that can run some code after its execution.
In pytest
we have this functionality with fixtures
:
@pytest.fixture
def db_connection(conn_str: str):
connection = psycopg2.connect(conn_str)
yield connection
connection.close() # this code will be executed after the test is done
Using this fixture in some test guarantees that connection will be closed soon after the test finishes. This behavior is described here, in the Teardown section.
When I try to do it in my own class methods, I didn't get the same result.
class Database:
def __call__(self, conn_str: str):
conn = psycopg2.connect(conn_str)
yield conn
print("Got here")
conn.close()
database = Database()
conn = next(database())
cur = conn.cursor()
cur.execute("select * from users")
result = cur.fetchall()
conn.commit()
result
The output is the data in users table, but I never see the "Got here" string, so I'm guessing this code after the yield
keyword never runs.
Is there a way to achieve this?
CodePudding user response:
You need another next
call to have it run the code after the yield:
database = Database()
gen = database() # Saved the generator to a variable
conn = next(gen)
cur = conn.cursor()
cur.execute("select * from users")
result = cur.fetchall()
conn.commit()
next(gen) # Triggers the latter part of the function
Also note, when you exhaust a generator, it raises a StopIteration
exception as you'll see. You'll need to catch that as well.
CodePudding user response:
What you are trying to do is implement a context manager; the similarly to a Pytext fixture is incidental.
You can do this with contextmanager.contextlib
from contextlib import contextmanager
@contextmanager
def db_connection(conn_str):
connection = psycopg2.connect(conn_str)
yield connection
connection.close()
with db_connection(...) as db:
...
or define Database.__enter__
and Database.__exit__
explicitly:
class Database:
def __init__(self, conn_str: str):
self.conn_str = conn_str
def __enter__(self):
self.conn = psycopg2.connect(self.conn_str)
return self.conn
def __exit__(self, *args):
print("Got here")
self.conn.close()
with Database(...) as db:
...
(You can use the connection returned by psycopg2.connect
as a context manager itself.)