Reputation: 65
I am making a FastAPI 0.101 server and SQLAlchemy 2.0.20, but I'm having some trouble making the tests. Essentially what I think is happening is there's a deadlock happening as both the tests (PyTest), and the code run on the route try to use the same Session that is in a yield expression. I'll show you on a route whose job is just to empty the Users table. On my test, I want to clear the table, add a User, call the endpoint and then make sure there are 0 items in the table. Aside from that, I want all this test to happen in a transaction so it theoretically doesn't affect the existing data in the database, despite the endpoint calling session.commit()
Whenever the test's execution reaches the line in test_router.py marked with "# HERE", the program hangs forever, which I'm guessing is a deadlock on the session.
Any better idea on how I can do those tests on the database? Needing to keep a transaction that is later rollbacked, despite calling commit() in the code being tested
database.py
SQLALCHEMY_DATABASE_URL = "postgresql://postgres:[email protected]:5432/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL
)
Session = sessionmaker(autocommit=False, autoflush=True, bind=engine, expire_on_commit=False)
Base = declarative_base()
# Dependency
def get_session():
"""Shares a single db session to be used throughout the service"""
session = Session()
print("Tried to get session")
try:
yield session
finally:
session.close()
router.py
@router.post("/reset")
async def reset(
session: Session = Depends(get_session),
):
"""
Clears the users table
Returns:
msg: Todos los datos fueron eliminados
"""
try:
statement = delete(User)
print(statement)
with session:
session.execute(statement)
session.commit()
except Exception as e:
print("ERROR: /users/reset")
print(e)
return JSONResponse(
status_code=500,
content={
"msg": "Un error desconocido ha ocurrido"
})
return {"msg": "Todos los datos fueron eliminados"}
conftest.py
@pytest.fixture(scope="session")
def session() -> Generator:
Base.metadata.create_all(bind=engine)
connection = engine.connect()
# begin a non-ORM transaction
transaction = connection.begin()
session = orm.Session(bind=connection)
yield session
session.close()
transaction.rollback()
connection.close()
@pytest.fixture(scope="session")
def client() -> Generator:
with TestClient(app) as c:
yield c
test_router.py
def test_reset(
client: TestClient, session: Session
):
with session as ses:
ses.execute(
delete(User)
)
mock_user = User(
id=uuid.UUID("c62147cf-2e63-4508-b1ff-98f805577f2c"),
username="user",
email="[email protected]",
phoneNumber="+57 300 500 2000",
dni="10100190",
fullName="Usuario Perez Gómez",
passwordHash="a68f7b00815178c7996ddc88208224198a584ce22faf75b19bfeb24ed6f90a59",
salt="ES25GfW7i4Pp1BqXtASUFXJFe9PMb_7o-2v73v3svWc",
token="eHBL1jbhBY6GfZ96DC03BlxM38SPF3npRBceefRgnkTpByFexOe7RPPDdLCh9gejD6Fe6Kdl_s5C3Gljqh3WM2xW1IGdlZQYg"
"V0_v55tw_NB19oMzH2t9AjKycEDdwmqPFJVR4sZuk9MFvSGoY_vQa4Y0pwCvxhBDT1VNsDnQio",
status=UserStatusEnum.NO_VERIFICADO,
expireAt=datetime.datetime.now(),
createdAt=datetime.datetime.now(),
updateAt=datetime.datetime.now()
)
session.add(mock_user)
session.commit()
assert session.scalar(select(func.count()).select_from(User)) == 1, "Table couldn't be set up successfully"
response = client.post("/users/reset") # HERE
assert response.status_code == 200, "The request failed for an unknown reason"
assert "los datos fueron eliminados" in str(response.json()['msg'])
assert session.scalar(select(func.count()).select_from(User)) == 0, "Table reset was unsuccessful"
Upvotes: 0
Views: 514
Reputation: 522
as my experience remove with
while using dependecy injection (get_session
) or make a context manager in your get_session
, while you using with it means you need to use context and in your current code you are context behavior with your session while it's not a context.
i prefer create a db_manager class and create enter and exit dunder methods when you want to use context manager or
use builtin module for it
Upvotes: 1