From 24536bbd46423a94d3dae29702d32af4435f3ed7 Mon Sep 17 00:00:00 2001 From: Mathias Goncalves Date: Fri, 2 Feb 2024 16:38:11 -0500 Subject: [PATCH] CHORE: Stay current with latest FastAPI / Starlette (#86) * RF: Move to lifespan context manager for startup/shutdown * RF: Reflect parameter order change * RF: Stop using deprecated `close` * FIX: Catch potential `None` client For reasons I don't quite understand, starlette.TestClient will now produce a Nonetype object for `request.client`, leading to a problem for the sliding window rate limiter. This should not be present in production. * PIN: Update stable dependencies --- migas/server/app.py | 43 +++++++++++++-------------- migas/server/schema.py | 3 +- stable-requirements.txt | 65 +++++++++++++++++++---------------------- 3 files changed, 52 insertions(+), 59 deletions(-) diff --git a/migas/server/app.py b/migas/server/app.py index 8f221e0..5493d13 100644 --- a/migas/server/app.py +++ b/migas/server/app.py @@ -1,4 +1,5 @@ import os +from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -18,8 +19,24 @@ from .schema import SCHEMA +@asynccontextmanager +async def lifespan(app: FastAPI): + """Handle startup and shutdown logic""" + # Connect to Redis + app.cache = await get_redis_connection() + # Connect to PostgreSQL and initialize tables + app.db = await get_db_engine() + await init_db(app.db) + # Establish aiohttp session + app.requests = await get_requests_session() + yield + await app.cache.aclose() + await app.db.dispose() + await app.requests.close() + + def _create_app() -> FastAPI: - app = FastAPI(title="migas", version=__version__) + app = FastAPI(title="migas", version=__version__, lifespan=lifespan) graphql_app = GraphQLRouter(SCHEMA) app.include_router(graphql_app, prefix="/graphql") @@ -45,26 +62,6 @@ def _create_app() -> FastAPI: app.mount("/static", StaticFiles(directory=static), name="static") templates = Jinja2Templates(directory=static) - -@app.on_event("startup") -async def startup(): - # Connect to Redis - app.cache = await get_redis_connection() - # Connect to PostgreSQL and initialize tables - app.db = await get_db_engine() - await init_db(app.db) - # Establish aiohttp session - app.requests = await get_requests_session() - - -@app.on_event("shutdown") -async def shutdown(): - # close connections - await app.cache.close() - await app.db.dispose() - await app.requests.close() - - @app.get("/info") async def info(): return { @@ -76,9 +73,9 @@ async def info(): @app.get("/", response_class=HTMLResponse) async def home(request: Request): - return templates.TemplateResponse("home.html", {"request": request}) + return templates.TemplateResponse(request, "home.html") @app.get("/viz", response_class=HTMLResponse) async def viz(request: Request): - return templates.TemplateResponse("viz.html", {"request": request}) + return templates.TemplateResponse(request, "viz.html") diff --git a/migas/server/schema.py b/migas/server/schema.py index 7d36c51..6c59229 100644 --- a/migas/server/schema.py +++ b/migas/server/schema.py @@ -282,7 +282,8 @@ async def sliding_window_rate_limit(self, request: Request, response: Response): cache = await get_redis_connection() # the sliding window key - key = f'rate-limit-{request.client.host}' + host = request.client.host if request.client else 'no-client' + key = f'rate-limit-{host}' time_ = time.time() async with cache.pipeline(transaction=True) as pipe: diff --git a/stable-requirements.txt b/stable-requirements.txt index 61195a5..fb5ed72 100644 --- a/stable-requirements.txt +++ b/stable-requirements.txt @@ -4,61 +4,56 @@ # # pip-compile --extra=test --output-file=stable-requirements.txt pyproject.toml # -aiohttp==3.8.6 +aiohttp==3.9.3 # via migas-server (pyproject.toml) aiosignal==1.3.1 # via aiohttp annotated-types==0.6.0 # via pydantic -anyio==3.7.1 +anyio==4.2.0 # via - # fastapi # httpx # starlette # watchfiles async-timeout==4.0.3 - # via - # aiohttp - # asyncpg + # via asyncpg asyncpg==0.29.0 # via migas-server (pyproject.toml) -attrs==23.1.0 +attrs==23.2.0 # via aiohttp -certifi==2023.7.22 +certifi==2024.2.2 # via # httpcore # httpx -charset-normalizer==3.3.2 - # via aiohttp click==8.1.7 # via uvicorn -dnspython==2.4.2 +dnspython==2.5.0 # via email-validator email-validator==2.1.0.post1 # via fastapi -fastapi[all]==0.104.1 +fastapi[all]==0.109.0 # via # migas-server (pyproject.toml) # strawberry-graphql -frozenlist==1.4.0 +frozenlist==1.4.1 # via # aiohttp # aiosignal graphql-core==3.2.3 # via strawberry-graphql -greenlet==3.0.1 +greenlet==3.0.3 # via sqlalchemy h11==0.14.0 # via # httpcore # uvicorn -httpcore==1.0.1 +httpcore==1.0.2 # via httpx httptools==0.6.1 # via uvicorn -httpx==0.25.1 +httpx==0.26.0 # via fastapi -idna==3.4 +idna==3.6 # via # anyio # email-validator @@ -68,38 +63,38 @@ iniconfig==2.0.0 # via pytest itsdangerous==2.1.2 # via fastapi -jinja2==3.1.2 +jinja2==3.1.3 # via fastapi -markupsafe==2.1.3 +markupsafe==2.1.5 # via jinja2 -multidict==6.0.4 +multidict==6.0.5 # via # aiohttp # yarl -orjson==3.9.10 +orjson==3.9.12 # via fastapi packaging==23.2 # via # migas-server (pyproject.toml) # pytest -pluggy==1.3.0 +pluggy==1.4.0 # via pytest -pydantic==2.4.2 +pydantic==2.6.0 # via # fastapi # pydantic-extra-types # pydantic-settings -pydantic-core==2.10.1 +pydantic-core==2.16.1 # via pydantic -pydantic-extra-types==2.1.0 +pydantic-extra-types==2.5.0 # via fastapi -pydantic-settings==2.0.3 +pydantic-settings==2.1.0 # via fastapi -pytest==7.4.3 +pytest==8.0.0 # via migas-server (pyproject.toml) python-dateutil==2.8.2 # via strawberry-graphql -python-dotenv==1.0.0 +python-dotenv==1.0.1 # via # pydantic-settings # uvicorn @@ -119,22 +114,22 @@ sniffio==1.3.0 # via # anyio # httpx -sqlalchemy[asyncio]==2.0.23 +sqlalchemy[asyncio]==2.0.25 # via migas-server (pyproject.toml) -starlette==0.27.0 +starlette==0.35.1 # via fastapi -strawberry-graphql[fastapi]==0.211.2 +strawberry-graphql[fastapi]==0.219.1 # via migas-server (pyproject.toml) -typing-extensions==4.8.0 +typing-extensions==4.9.0 # via # fastapi # pydantic # pydantic-core # sqlalchemy # strawberry-graphql -ujson==5.8.0 +ujson==5.9.0 # via fastapi -uvicorn[standard]==0.24.0.post1 +uvicorn[standard]==0.27.0.post1 # via fastapi uvloop==0.19.0 # via uvicorn @@ -142,5 +137,5 @@ watchfiles==0.21.0 # via uvicorn websockets==12.0 # via uvicorn -yarl==1.9.2 +yarl==1.9.4 # via aiohttp