|
10 | 10 | import contextlib |
11 | 11 | import gc |
12 | 12 | import hmac |
| 13 | +import importlib.metadata |
13 | 14 | import logging |
14 | 15 | import mimetypes |
15 | 16 | import os |
|
41 | 42 | from fastapi.openapi.utils import get_openapi |
42 | 43 | from fastapi.responses import JSONResponse, RedirectResponse |
43 | 44 | from fastapi.staticfiles import StaticFiles |
| 45 | +from packaging.version import Version |
44 | 46 | from starlette.exceptions import HTTPException |
45 | 47 | from typing_extensions import Self |
46 | 48 |
|
|
73 | 75 |
|
74 | 76 | logfire: Any | None = configure_logfire() |
75 | 77 |
|
| 78 | +# FastAPI < 0.137 copies routes when including a router; 0.137+ keeps a |
| 79 | +# reference to the original via _IncludedRouter. |
| 80 | +_FASTAPI_COPIES_ROUTES_ON_INCLUDE: bool = Version( |
| 81 | + importlib.metadata.version("fastapi") |
| 82 | +) < Version("0.137.0") |
| 83 | + |
76 | 84 | TITLE = "Prefect Server" |
77 | 85 | API_TITLE = "Prefect REST API" |
78 | 86 | UI_TITLE = "Prefect REST API UI" |
@@ -491,21 +499,16 @@ async def server_version() -> str: # type: ignore[reportUnusedFunction] |
491 | 499 |
|
492 | 500 | for router in API_ROUTERS: |
493 | 501 | api_app.include_router(router, dependencies=dependencies) |
494 | | - if final: |
495 | | - # Important note about how FastAPI works: |
496 | | - # |
497 | | - # When including a router, FastAPI copies the routes and builds entirely new |
498 | | - # Pydantic models to represent the request bodies of the routes in the |
499 | | - # router. This is because the dependencies may change if the same router is |
500 | | - # included multiple times, but it also means that we are holding onto an |
501 | | - # entire set of Pydantic models on the original routers for the duration of |
502 | | - # the server process that will never be used. |
| 502 | + if final and _FASTAPI_COPIES_ROUTES_ON_INCLUDE: |
| 503 | + # When including a router, older versions of FastAPI (< 0.137) copy |
| 504 | + # the routes and build entirely new Pydantic models. Since Prefect |
| 505 | + # does not reuse routers, we can delete the originals to reclaim |
| 506 | + # ~50-55 MB of memory. |
503 | 507 | # |
504 | | - # Because Prefect does not reuse routers, we are free to clean up the routes |
505 | | - # because we know they won't be used again. Thus, if we have the hint that |
506 | | - # this is the final instance we will create in this process, we can clean up |
507 | | - # the routes on the original source routers to conserve memory (~50-55MB as |
508 | | - # of introducing this change). |
| 508 | + # FastAPI 0.137+ wraps included routers in an _IncludedRouter that |
| 509 | + # references the original router for request matching, so the |
| 510 | + # originals must be kept. The duplication no longer occurs in this |
| 511 | + # case either, so the optimisation is unnecessary. |
509 | 512 | del router.routes |
510 | 513 |
|
511 | 514 | if final: |
|
0 commit comments