Skip to content

Commit

Permalink
Use the new FastAPI lifespan hook
Browse files Browse the repository at this point in the history
Rather than using startup and shutdown events, use the new FastAPI
lifespan hook and async context manager. This avoids warnings while
running the test suite.
  • Loading branch information
rra committed Oct 25, 2023
1 parent a39271e commit a694c48
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 19 deletions.
43 changes: 28 additions & 15 deletions src/gafaelfawr/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from __future__ import annotations

import os
from collections.abc import AsyncIterator, Coroutine
from contextlib import asynccontextmanager
from importlib.metadata import version
from pathlib import Path

Expand Down Expand Up @@ -31,7 +33,11 @@
__all__ = ["create_app"]


def create_app(*, load_config: bool = True) -> FastAPI:
def create_app(
*,
load_config: bool = True,
extra_startup: Coroutine[None, None, None] | None = None,
) -> FastAPI:
"""Create the FastAPI application.
This is in a function rather than using a global variable (as is more
Expand All @@ -46,7 +52,27 @@ def create_app(*, load_config: bool = True) -> FastAPI:
set of proxy IP addresses. This is used primarily for OpenAPI
schema generation, where constructing the app is required but the
configuration won't matter.
extra_startup
If provided, an additional coroutine to run as part of the startup
section of the lifespan context manager, used by the test suite.
"""

@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
config = config_dependency.config()
await context_dependency.initialize(config)
await db_session_dependency.initialize(
config.database_url, config.database_password
)
if extra_startup:
await extra_startup

yield

await http_client_dependency.aclose()
await db_session_dependency.aclose()
await context_dependency.aclose()

app = FastAPI(
title="Gafaelfawr",
description=(
Expand Down Expand Up @@ -83,6 +109,7 @@ def create_app(*, load_config: bool = True) -> FastAPI:
openapi_url="/auth/openapi.json",
docs_url="/auth/docs",
redoc_url="/auth/redoc",
lifespan=lifespan,
)

# Add all of the routes.
Expand Down Expand Up @@ -143,20 +170,6 @@ def create_app(*, load_config: bool = True) -> FastAPI:
# Handle exceptions descended from ClientRequestError.
app.exception_handler(ClientRequestError)(client_request_error_handler)

@app.on_event("startup")
async def startup_event() -> None:
config = config_dependency.config()
await context_dependency.initialize(config)
await db_session_dependency.initialize(
config.database_url, config.database_password
)

@app.on_event("shutdown")
async def shutdown_event() -> None:
await http_client_dependency.aclose()
await db_session_dependency.aclose()
await context_dependency.aclose()

# This is a temporary workaround for a bug in FastAPI handling Pydantic
# validation errors when a list query parameter is not specified. See
# https://github.com/tiangolo/fastapi/issues/9920
Expand Down
6 changes: 2 additions & 4 deletions tests/support/selenium.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,12 @@ def selenium_create_app() -> FastAPI:
be called by uvicorn in the separate process spawned by run_app. If it is
run in the main pytest process, it will break other tests.
"""
app = create_app()
token_path = Path(os.environ["GAFAELFAWR_TEST_TOKEN_PATH"])

@app.on_event("startup")
async def selenium_startup_event() -> None:
async def selenium_startup() -> None:
await _selenium_startup(token_path)

return app
return create_app(extra_startup=selenium_startup())


@asynccontextmanager
Expand Down

0 comments on commit a694c48

Please sign in to comment.