AsyncClient ignores the startup and shutdown events
See original GitHub issueThis is very close to #1072, but for AsyncClient.
The problem is that AsyncClient ignores the startup and shutdown events.
To Reproduce
Steps to reproduce the behavior with a minimum self-contained file.
Replace each part with your own scenario:
- Create a main.py file with:
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
async def add_test_header(request: Request, call_next):
response = await call_next(request)
response.headers["X-Test-Header"] = 'Hello'
return response
@app.on_event("startup")
def setup():
# example code that runs on startup
global add_test_header
print('executing startup!!')
add_test_header = app.middleware("http")(add_test_header)
- Create a test_startup.py file with:
import pytest
from httpx import AsyncClient
from main import app
@pytest.fixture()
async def client():
async with AsyncClient(app=app, base_url="http://test") as
yield client
@pytest.mark.asyncio
async def test_read_main(client):
response = await client.get("/")
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}
assert 'X-Test-Header' in response.headers
Run pytest -s -v test_startup.py You will see an AssertionError for the X-Test-Header not being there The test should pass
So far I am using a hack to make it work:
@pytest.fixture()
async def client():
"""Test client pytest fixture.
Example:
>>> from httpx import Response
>>>
>>>
>>> @pytest.mark.asyncio
>>> async def test_health_check(client):
>>> resp: Response = await client.get("/health_check")
>>> assert resp.status_code == 200
"""
app = build_app()
async with AsyncClient(app=app, base_url="http://test") as client:
await connect_to_db(app)
yield client
await db_teardown(app)
but probably some solution or a note in the docs would compliment the project
Issue Analytics
- State:
- Created 3 years ago
- Reactions:5
- Comments:20 (5 by maintainers)
Top Results From Across the Web
Startup and Shutdown — Quart 0.17.0 documentation
... ability for awaiting coroutines before the first byte is received and after the final byte is sent, through the startup and shutdown...
Read more >Alienvault HIDS does not collect Windows Reboot or ...
Default configuration prevents the event from being delivered, requiring special considerations if reboot/shutdown events are required data.
Read more >Async Support - HTTPX
Startup /shutdown of ASGI apps. It is not in the scope of HTTPX to trigger lifespan events of your app. However it is...
Read more >How to schedule a task to run when shutting down windows
In all other cases (start menu shutdown), the computer kernel hibernated, and revieved on boot, and GPO startup and shutdown scipts are ignored....
Read more >ASP.NET Core WebSockets and Application Lifetime ...
ASP.NET Core WebSockets and Application Lifetime Shutdown Events ... None); } catch { // this may throw on shutdown and can be ignored...
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
It’s all discussed in https://github.com/encode/httpx/issues/350 and related issues, but I’m gonna leave here a short summary.
As described in https://github.com/encode/httpx/issues/1441, app lifecycle managemenent won’t be added to HTTPX’s
AsyncClient
because its considered out of its scope.Suggested solution for original problem is to use
LifespanManager
from asgi-lifespan in pair withAsyncClient
.So
test_startup.py
could be changed to:I think it should be at least mentioned in “Testing Events: startup - shutdown” section of FastAPI docs.
Now that my tests are running, I will suggest that the so-called hack suggested by the original poster may in fact be desired best practice. This is because, once I got the first test running under
pytest-asyncio
I hit another round of problems trying to get multiple tests to run until I realized something else was going on besides just startup/shutdown confusion:Like all of
pytest
,pytest-asyncio
is built around the concept of running individually isolated unit tests. Thus, it creates a new event loop for each unit test. This can be a good thing, you’ll be able to isolatexyz was never awaited
errors to one unit test. But it can also lead to confusing results if your test tries to reuse theapp
variable as infrom main import app
: I was trying to run a test that was marked withparametrize
and no matter what I did, only one iteration would run and then I’d started getting weird errors about event loops. To be fully isolated unit tests, you have to create a new FastAPI instance each time as the OP did with theirbuild_app
function.Here is how I refactored my code:
Then, in my tests, I have a fixture that creates a brand new FastAPI instance for each test function so that nothing gets confused by all the new event loops being created and destroyed under the covers by pytest-asyncio.
And here’s a stub for a test:
Edit to add: If you don’t mind having your event_loop stick around between tests, this issue shows how to prevent pytest-asyncio from creating new loops during the test: https://github.com/tiangolo/fastapi/issues/2006
Edited to patch up the pseudo-code a bit more.