@@ -231,7 +231,6 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
231
231
async with AsyncClient(app = app) as client:
232
232
app.state.client = client
233
233
yield
234
- await client.aclose()
235
234
236
235
237
236
app = FastAPI(lifespan = lifespan)
@@ -331,6 +330,123 @@ To avoid the performance penalty, you can implement a [Pure ASGI middleware]. Th
331
330
332
331
Check the Starlette's documentation to learn how to implement a [ Pure ASGI middleware] .
333
332
333
+ ## 9. Your dependencies may be running on threads
334
+
335
+ If the function is non-async and you use it as a dependency, it will run in a thread.
336
+
337
+ In the following example, the ` http_client ` function will run in a thread:
338
+
339
+ ``` py
340
+ from collections.abc import AsyncIterator
341
+ from contextlib import asynccontextmanager
342
+
343
+ from httpx import AsyncClient
344
+ from fastapi import FastAPI, Request, Depends
345
+
346
+
347
+ @asynccontextmanager
348
+ async def lifespan (app : FastAPI) -> AsyncIterator[dict[str , AsyncClient]]:
349
+ async with AsyncClient() as client:
350
+ yield {" client" : client}
351
+
352
+
353
+ app = FastAPI(lifespan = lifespan)
354
+
355
+
356
+ def http_client (request : Request) -> AsyncClient:
357
+ return request.state.client
358
+
359
+
360
+ @app.get (" /" )
361
+ async def read_root (client : AsyncClient = Depends(http_client)):
362
+ return await client.get(" /" )
363
+ ```
364
+
365
+ To run in the event loop, you need to make the function async:
366
+ ``` py
367
+ # ...
368
+
369
+ async def http_client (request : Request) -> AsyncClient:
370
+ return request.state.client
371
+
372
+ # ...
373
+ ```
374
+
375
+ As an exercise for the reader, let's learn a bit more about how to check the running threads.
376
+
377
+ You can run the following with ` python main.py ` :
378
+
379
+ ``` py
380
+ from collections.abc import AsyncIterator
381
+ from contextlib import asynccontextmanager
382
+
383
+ import anyio
384
+ from anyio.to_thread import current_default_thread_limiter
385
+ from httpx import AsyncClient
386
+ from fastapi import FastAPI, Request, Depends
387
+
388
+
389
+ @asynccontextmanager
390
+ async def lifespan (app : FastAPI) -> AsyncIterator[dict[str , AsyncClient]]:
391
+ async with AsyncClient() as client:
392
+ yield {" client" : client}
393
+
394
+
395
+ app = FastAPI(lifespan = lifespan)
396
+
397
+
398
+ # Change this function to be async, and rerun this application.
399
+ def http_client (request : Request) -> AsyncClient:
400
+ return request.state.client
401
+
402
+
403
+ @app.get (" /" )
404
+ async def read_root (client : AsyncClient = Depends(http_client)): ...
405
+
406
+
407
+ async def monitor_thread_limiter ():
408
+ limiter = current_default_thread_limiter()
409
+ threads_in_use = limiter.borrowed_tokens
410
+ while True :
411
+ if threads_in_use != limiter.borrowed_tokens:
412
+ print (f " Threads in use: { limiter.borrowed_tokens} " )
413
+ threads_in_use = limiter.borrowed_tokens
414
+ await anyio.sleep(0 )
415
+
416
+
417
+ if __name__ == " __main__" :
418
+ import uvicorn
419
+
420
+ config = uvicorn.Config(app = " main:app" )
421
+ server = uvicorn.Server(config)
422
+
423
+ async def main ():
424
+ async with anyio.create_task_group() as tg:
425
+ tg.start_soon(monitor_thread_limiter)
426
+ await server.serve()
427
+
428
+ anyio.run(main)
429
+ ```
430
+
431
+ If you call the endpoint, you will see the following message:
432
+
433
+ ``` bash
434
+ ❯ python main.py
435
+ INFO: Started server process [23966]
436
+ INFO: Waiting for application startup.
437
+ INFO: Application startup complete.
438
+ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
439
+ Threads in use: 1
440
+ INFO: 127.0.0.1:57848 - " GET / HTTP/1.1" 200 OK
441
+ Threads in use: 0
442
+ ```
443
+
444
+ Replace the ` def http_client ` with ` async def http_client ` and rerun the application.
445
+ You will not see the message ` Threads in use: 1 ` , because the function is running in the event loop.
446
+
447
+ > [ !TIP]
448
+ > You can use the [ FastAPI Dependency] package that I've built to make it explicit when a dependency should run in a thread.
449
+
334
450
[ uvicorn ] : https://www.uvicorn.org/
335
451
[ run_sync ] : https://anyio.readthedocs.io/en/stable/threads.html#running-a-function-in-a-worker-thread
336
452
[ run_in_threadpool ] : https://github.com/encode/starlette/blob/9f16bf5c25e126200701f6e04330864f4a91a898/starlette/concurrency.py#L36-L42
@@ -342,3 +458,4 @@ Check the Starlette's documentation to learn how to implement a [Pure ASGI middl
342
458
[ The FastAPI Expert ] : https://github.com/Kludex
343
459
[ base-http-middleware ] : https://www.starlette.io/middleware/#basehttpmiddleware
344
460
[ pure ASGI middleware ] : https://www.starlette.io/middleware/#pure-asgi-middleware
461
+ [ FastAPI Dependency ] : https://github.com/kludex/fastapi-dependency
0 commit comments