FastAPI 0.69 app with SQLAlchemy hangs in pytest when HTTPError is raised
See original GitHub issueFirst Check
- I added a very descriptive title to this issue.
- I used the GitHub search to find a similar issue and didn’t find it.
- I searched the FastAPI documentation, with the integrated search.
- I already searched in Google “How to X in FastAPI” and didn’t find any information.
- I already read and followed all the tutorial in the docs and didn’t find an answer.
- I already checked if it is not related to FastAPI but to Pydantic.
- I already checked if it is not related to FastAPI but to Swagger UI.
- I already checked if it is not related to FastAPI but to ReDoc.
Commit to Help
- I commit to help with one of those options 👆
Example Code
"""
test_fastapi.py
Requires:
- python3
- docker
Setup:
virtualenv -p /usr/bin/python3 .venv
source .venv/bin/activate
pip install fastapi==0.69 sqlalchemy mysqlclient pytest requests
Usage:
pytest test_fastapi.py -vvs
"""
import subprocess
import time
import pytest
from fastapi import Body, FastAPI, HTTPException, Path
from fastapi.testclient import TestClient
from pydantic import BaseModel
from sqlalchemy import (
Column,
Integer,
MetaData,
String,
Table,
create_engine,
insert,
select,
)
# Config for the docker container
CONTAINER = "mariadb-fastapi"
MYSQL_PORT = "33072"
MYSQL_DATABASE = "test"
MYSQL_ROOT_PASSWORD = "rootmysql"
DB_URL = f"mysql://root:{MYSQL_ROOT_PASSWORD}@127.0.0.1:{MYSQL_PORT}/{MYSQL_DATABASE}"
# SQLAlchemy
ENGINE = create_engine(DB_URL)
METADATA = MetaData()
T = Table(
"job",
METADATA,
Column("id", Integer(), primary_key=True),
Column("name", String(length=50)),
)
# FastAPI model and app
class Job(BaseModel):
name: str
app = FastAPI()
@app.get("/jobs/{job_id}")
def get_job(job_id: int = Path(...)):
conn = ENGINE.connect()
stmt = select([T]).with_for_update().where(T.c.id == job_id)
conn.execute(stmt).fetchone() # The result does not matter
# This exception that is raised in the first test causes the second test to
# hang during its setup.
# If we do "return None" instead, the second test will be run.
# return None
raise HTTPException(404, f'Job with ID "{job_id}" not found')
@app.post("/jobs")
def create_job(job: Job = Body(...)):
with ENGINE.begin() as conn:
ins = insert(T).values(**job.dict())
conn.execute(ins)
#
# Tests
#
@pytest.fixture(autouse=True, scope="module")
def mariadb_docker():
with subprocess.Popen(
[
"docker",
"run",
"--rm",
"--env",
f"MYSQL_DATABASE={MYSQL_DATABASE}",
"--env",
f"MYSQL_ROOT_PASSWORD={MYSQL_ROOT_PASSWORD}",
f"--name={CONTAINER}",
f"--publish={MYSQL_PORT}:3306",
"mariadb",
]
):
print("connecting ", end="")
while True:
try:
ENGINE.connect()
print("done")
break
except:
print(".", end="")
time.sleep(1)
yield
subprocess.run(["docker", "stop", CONTAINER], check=False)
@pytest.fixture
def session():
print("SESSION drop all")
METADATA.drop_all(ENGINE)
print("SESSION create all")
METADATA.create_all(ENGINE)
print("SESSION make client")
client = TestClient(app)
print("SESSION setup done")
return client
def test_function_raising_error(session):
print("TEST starting")
response = session.get(f"/jobs/2")
assert response.status_code == 404, response.text
print("TEST end")
def test_hangs_in_setup(session):
print("TEST starting")
response = session.post(f"/jobs", data={"name": "test"})
assert response.status_code == 422
print("TEST end")
import subprocess
import time
import pytest
from fastapi import Body, FastAPI, HTTPException, Path
from fastapi.testclient import TestClient
from pydantic import BaseModel
from sqlalchemy import (
Column,
Integer,
MetaData,
String,
Table,
create_engine,
insert,
select,
)
# Config for the docker container
CONTAINER = "mariadb-fastapi"
MYSQL_PORT = "33072"
MYSQL_DATABASE = "test"
MYSQL_ROOT_PASSWORD = "rootmysql"
DB_URL = f"mysql://root:{MYSQL_ROOT_PASSWORD}@127.0.0.1:{MYSQL_PORT}/{MYSQL_DATABASE}"
# SQLAlchemy
ENGINE = create_engine(DB_URL)
METADATA = MetaData()
T = Table(
"job",
METADATA,
Column("id", Integer(), primary_key=True),
Column("name", String(length=50)),
)
# FastAPI model and app
class Job(BaseModel):
name: str
app = FastAPI()
@app.get("/jobs/{job_id}")
def get_job(job_id: int = Path(...)):
conn = ENGINE.connect()
stmt = select([T]).with_for_update().where(T.c.id == job_id)
row = conn.execute(stmt).fetchone()
if row is None:
# This exception that is raised in the first test causes the second test to
# hang during its setup.
# If we do "return None" instead, the second test will be run.
# return None
raise HTTPException(404, f'Job with ID "{job_id}" not found')
return row
@app.post("/jobs")
def create_job(job: Job = Body(...)):
with ENGINE.begin() as conn:
ins = insert(T).values(**job.dict())
conn.execute(ins)
#
# Tests
#
@pytest.fixture(autouse=True, scope="module")
def mariadb_docker():
with subprocess.Popen(
[
"docker",
"run",
"--rm",
"--env",
f"MYSQL_DATABASE={MYSQL_DATABASE}",
"--env",
f"MYSQL_ROOT_PASSWORD={MYSQL_ROOT_PASSWORD}",
f"--name={CONTAINER}",
f"--publish={MYSQL_PORT}:3306",
"mariadb",
]
):
print("connecting ", end="")
while True:
try:
ENGINE.connect()
print("done")
break
except:
print(".", end="")
time.sleep(1)
yield
subprocess.run(["docker", "stop", CONTAINER], check=False)
@pytest.fixture
def session():
print("SESSION drop all")
METADATA.drop_all(ENGINE)
print("SESSION create all")
METADATA.create_all(ENGINE)
print("SESSION make client")
client = TestClient(app)
print("SESSION setup done")
return client
def test_function_raising_error(session):
print("TEST starting")
response = session.get(f"/jobs/2")
assert response.status_code == 404, response.text
print("TEST end")
def test_hangs_in_setup(session):
print("TEST starting")
response = session.post(f"/jobs", data={"name": "test"})
assert response.status_code == 422
print("TEST end")
Description
Starting with FastAPI 0.69, pytest tests started to hang under obscure conditions. The same code worked as expected with FastAPI 0.68.2.
I had issues with hanging tests in multiple projects. One was related to working with temp file handlers and the other one is shown in the example and related to working with SQLAlchemy/MariaDB.
In the example, the first test calls an endpoint that does an SQLAlchemy query and raises an HTTPException
.
Any test after that will hang during setup (when METADATA.drop_all()
is called).
This is what I get when I run the example:
$ pytest test_fastapi.py -vvs
=========================================================================================== test session starts ============================================================================================
platform linux -- Python 3.9.7, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /tmp/.venv/bin/python
cachedir: .pytest_cache
rootdir: /tmp
plugins: anyio-3.4.0
collected 2 items
test_fastapi.py::test_function_raising_error connecting done
SESSION drop all
SESSION create all
docker: Error response from daemon: Conflict. The container name "/mariadb-fastapi" is already in use by container "70717ec0e7ee7f8b721c2866a79eca73cfcd7bb3758978a9b7fe8e9b8f008786". You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.
SESSION make client
SESSION setup done
TEST starting
TEST end
PASSED
test_fastapi.py::test_hangs_in_setup SESSION drop all
Operating System
Linux
Operating System Details
Fedora 34
FastAPI Version
0.69.0
Python Version
3.9.7
Additional Context
Docker 20.10.11
Issue Analytics
- State:
- Created 2 years ago
- Comments:5
Top Results From Across the Web
fastapi (module) sqlalchemy import sqlalchemy could not be ...
py from fastapi import FastAPI from lock import FileLock app = FastAPI() lock ... 0.69 app with SQLAlchemy hangs in pytest when HTTPError...
Read more >How to test httpError in fastAPI with pytest? - Stack Overflow
Now I want to know: how can I raise HTTPException and test it on purpose? #main.py (FAST API) @app.get('/hello') def ...
Read more >Release Notes - FastAPI
If you are using response_model with some type that doesn't include None but the function is returning None , it will now raise...
Read more >[FastAPI/SQLAlchemy/Pytest] Database row not updating ...
[FastAPI/SQLAlchemy/Pytest] Database row not updating during unit testing ... lambda: db with TestClient(app) as c: yield c.
Read more >Fastapi - ormar
from typing import List, Optional import databases import sqlalchemy from fastapi import FastAPI import ormar app = FastAPI() metadata = sqlalchemy.
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
I’m having a similar issue in version
0.73.0
Yup, that solves it for me. Thx. 😃