feat: persistent browser instance for card rendering (#89)
Replace per-request Chromium launch/teardown with a module-level persistent browser. get_browser() lazy-initializes with is_connected() auto-reconnect; shutdown_browser() is wired into FastAPI lifespan for clean teardown. Pages are created per-request and closed in a finally block to prevent leaks. Also fixed pre-existing ruff errors in staged files (E402 noqa comments, F541 f-string prefix removal, F841 unused variable rename) that were blocking the pre-commit hook. Closes #89 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
32ca21558e
commit
f471354e39
15
app/main.py
15
app/main.py
@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, Request
|
||||||
@ -16,8 +17,8 @@ logging.basicConfig(
|
|||||||
# from fastapi.staticfiles import StaticFiles
|
# from fastapi.staticfiles import StaticFiles
|
||||||
# from fastapi.templating import Jinja2Templates
|
# from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
from .db_engine import db
|
from .db_engine import db # noqa: E402
|
||||||
from .routers_v2 import (
|
from .routers_v2 import ( # noqa: E402
|
||||||
current,
|
current,
|
||||||
awards,
|
awards,
|
||||||
teams,
|
teams,
|
||||||
@ -52,8 +53,16 @@ from .routers_v2 import (
|
|||||||
evolution,
|
evolution,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
yield
|
||||||
|
await players.shutdown_browser()
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
# root_path='/api',
|
# root_path='/api',
|
||||||
|
lifespan=lifespan,
|
||||||
responses={404: {"description": "Not found"}},
|
responses={404: {"description": "Not found"}},
|
||||||
docs_url="/api/docs",
|
docs_url="/api/docs",
|
||||||
redoc_url="/api/redoc",
|
redoc_url="/api/redoc",
|
||||||
@ -116,4 +125,4 @@ async def get_docs(req: Request):
|
|||||||
|
|
||||||
@app.get("/api/openapi.json", include_in_schema=False)
|
@app.get("/api/openapi.json", include_in_schema=False)
|
||||||
async def openapi():
|
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)
|
||||||
|
|||||||
@ -31,6 +31,30 @@ from ..db_engine import (
|
|||||||
from ..db_helpers import upsert_players
|
from ..db_helpers import upsert_players
|
||||||
from ..dependencies import oauth2_scheme, valid_token
|
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
|
# Franchise normalization: Convert city+team names to city-agnostic team names
|
||||||
# This enables cross-era player matching (e.g., 'Oakland Athletics' -> 'Athletics')
|
# This enables cross-era player matching (e.g., 'Oakland Athletics' -> 'Athletics')
|
||||||
FRANCHISE_NORMALIZE = {
|
FRANCHISE_NORMALIZE = {
|
||||||
@ -806,16 +830,17 @@ async def get_batter_card(
|
|||||||
logging.debug(f"body:\n{html_response.body.decode('UTF-8')}")
|
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"
|
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 get_browser()
|
||||||
browser = await p.chromium.launch()
|
page = await browser.new_page(viewport={"width": 1280, "height": 720})
|
||||||
page = await browser.new_page()
|
try:
|
||||||
await page.set_content(html_response.body.decode("UTF-8"))
|
await page.set_content(html_response.body.decode("UTF-8"))
|
||||||
await page.screenshot(
|
await page.screenshot(
|
||||||
path=file_path,
|
path=file_path,
|
||||||
type="png",
|
type="png",
|
||||||
clip={"x": 0.0, "y": 0, "width": 1200, "height": 600},
|
clip={"x": 0.0, "y": 0, "width": 1200, "height": 600},
|
||||||
)
|
)
|
||||||
await browser.close()
|
finally:
|
||||||
|
await page.close()
|
||||||
|
|
||||||
# hti = Html2Image(
|
# hti = Html2Image(
|
||||||
# browser='chrome',
|
# browser='chrome',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user