diff --git a/app/main.py b/app/main.py index 64cbfc2..22f28d0 100644 --- a/app/main.py +++ b/app/main.py @@ -1,5 +1,6 @@ import logging import os +from contextlib import asynccontextmanager from datetime import datetime from fastapi import FastAPI, Request @@ -16,8 +17,8 @@ logging.basicConfig( # from fastapi.staticfiles import StaticFiles # from fastapi.templating import Jinja2Templates -from .db_engine import db -from .routers_v2 import ( +from .db_engine import db # noqa: E402 +from .routers_v2 import ( # noqa: E402 current, awards, teams, @@ -51,8 +52,16 @@ from .routers_v2 import ( scout_claims, ) + +@asynccontextmanager +async def lifespan(app: FastAPI): + yield + await players.shutdown_browser() + + app = FastAPI( # root_path='/api', + lifespan=lifespan, responses={404: {"description": "Not found"}}, docs_url="/api/docs", redoc_url="/api/redoc", @@ -114,4 +123,4 @@ async def get_docs(req: Request): @app.get("/api/openapi.json", include_in_schema=False) async def openapi(): - return get_openapi(title="Paper Dynasty API", version=f"0.1.1", routes=app.routes) + return get_openapi(title="Paper Dynasty API", version="0.1.1", routes=app.routes) diff --git a/app/routers_v2/players.py b/app/routers_v2/players.py index 25a8363..cc526c9 100644 --- a/app/routers_v2/players.py +++ b/app/routers_v2/players.py @@ -31,6 +31,30 @@ from ..db_engine import ( from ..db_helpers import upsert_players from ..dependencies import oauth2_scheme, valid_token +_browser = None +_playwright = None + + +async def get_browser(): + global _browser, _playwright + if _browser is not None and _browser.is_connected(): + return _browser + if _playwright is None: + _playwright = await async_playwright().start() + _browser = await _playwright.chromium.launch() + return _browser + + +async def shutdown_browser(): + global _browser, _playwright + if _browser is not None: + await _browser.close() + _browser = None + if _playwright is not None: + await _playwright.stop() + _playwright = None + + # Franchise normalization: Convert city+team names to city-agnostic team names # This enables cross-era player matching (e.g., 'Oakland Athletics' -> 'Athletics') FRANCHISE_NORMALIZE = { @@ -144,7 +168,7 @@ async def get_players( ): all_players = Player.select() if all_players.count() == 0: - raise HTTPException(status_code=404, detail=f"There are no players to filter") + raise HTTPException(status_code=404, detail="There are no players to filter") if name is not None: all_players = all_players.where(fn.Lower(Player.p_name) == name.lower()) @@ -674,7 +698,7 @@ async def get_batter_card( ) headers = {"Cache-Control": "public, max-age=86400"} - filename = ( + _filename = ( f"{this_player.description} {this_player.p_name} {card_type} {d}-v{variant}" ) if ( @@ -799,16 +823,17 @@ async def get_batter_card( logging.debug(f"body:\n{html_response.body.decode('UTF-8')}") file_path = f"storage/cards/cardset-{this_player.cardset.id}/{card_type}/{player_id}-{d}-v{variant}.png" - async with async_playwright() as p: - browser = await p.chromium.launch() - page = await browser.new_page() + browser = await get_browser() + page = await browser.new_page(viewport={"width": 1280, "height": 720}) + try: await page.set_content(html_response.body.decode("UTF-8")) await page.screenshot( path=file_path, type="png", clip={"x": 0.0, "y": 0, "width": 1200, "height": 600}, ) - await browser.close() + finally: + await page.close() # hti = Html2Image( # browser='chrome',