From 8a6fbfaa8a0baf282e1e548b7b1d0a96b98cf2a7 Mon Sep 17 00:00:00 2001 From: KoalaTrapLord Date: Sat, 14 Mar 2026 19:08:21 +0000 Subject: [PATCH] fix: 3 post-refactor bugs (COMMANDER_META, PRECON_INDEX, startup) Task 1: Move BUILTIN_COMMANDERS + load_commander_meta() to shared.py Fix import_routes.py to use _shared.COMMANDER_META (module ref) Task 2: Rewrite precon.py to use _shared.PRECON_INDEX (module ref) Task 3: Add @app.on_event('startup') safety net for uvicorn --reload --- lab_api.py | 53 ++++++++++++----------------------------- routes/import_routes.py | 6 ++--- routes/precon.py | 11 +++------ routes/shared.py | 34 ++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 49 deletions(-) diff --git a/lab_api.py b/lab_api.py index c069235..e4d8dce 100644 --- a/lab_api.py +++ b/lab_api.py @@ -99,6 +99,8 @@ from routes.shared import ( # EDHREC cache _edhrec_cache, _EDHREC_CACHE_TTL, _edhrec_cache_get, _edhrec_cache_set, + # Commander meta + load_commander_meta, BUILTIN_COMMANDERS, ) # Route modules @@ -151,6 +153,18 @@ app.include_router(import_router) app.include_router(lab_router) +@app.on_event("startup") +async def _on_startup(): + """Safety net: ensure DB and precon index are initialized even when + main() is bypassed (e.g. uvicorn lab_api:app --reload).""" + import routes.shared as _shared + _shared.init_collection_db() + if not _shared.PRECON_INDEX: + _shared.download_precon_database() + if not _shared.COMMANDER_META: + _shared.load_commander_meta() + + # ══════════════════════════════════════════════════════════════ # In-Memory State (used by remaining endpoints) # ══════════════════════════════════════════════════════════════ @@ -619,29 +633,6 @@ async def get_card_edhrec(cardId: int): return result -# ══════════════════════════════════════════════════════════════ -# Commander Meta Mapping (loaded at startup) -# ══════════════════════════════════════════════════════════════ - -BUILTIN_COMMANDERS = { - "Edgar Markov": [{"source": "edhrec", "archetype": "aggro", "colorIdentity": ["W","B","R"]}], - "Atraxa, Praetors' Voice": [{"source": "edhrec", "archetype": "midrange", "colorIdentity": ["W","U","B","G"]}], - "Korvold, Fae-Cursed King": [{"source": "edhrec", "archetype": "combo", "colorIdentity": ["B","R","G"]}], - "Muldrotha, the Gravetide": [{"source": "edhrec", "archetype": "midrange", "colorIdentity": ["U","B","G"]}], - "The Ur-Dragon": [{"source": "edhrec", "archetype": "midrange", "colorIdentity": ["W","U","B","R","G"]}], - "Yuriko, the Tiger's Shadow": [{"source": "edhrec", "archetype": "aggro", "colorIdentity": ["U","B"]}], - "Krenko, Mob Boss": [{"source": "edhrec", "archetype": "aggro", "colorIdentity": ["R"]}], - "Meren of Clan Nel Toth": [{"source": "edhrec", "archetype": "midrange", "colorIdentity": ["B","G"]}], - "Prossh, Skyraider of Kher": [{"source": "edhrec", "archetype": "combo", "colorIdentity": ["B","R","G"]}], - "Kaalia of the Vast": [{"source": "edhrec", "archetype": "aggro", "colorIdentity": ["W","B","R"]}], - "Talrand, Sky Summoner": [{"source": "edhrec", "archetype": "control", "colorIdentity": ["U"]}], - "Omnath, Locus of Creation": [{"source": "edhrec", "archetype": "combo", "colorIdentity": ["W","U","R","G"]}], - "Teysa Karlov": [{"source": "edhrec", "archetype": "combo", "colorIdentity": ["W","B"]}], - "Lathril, Blade of the Elves": [{"source": "edhrec", "archetype": "aggro", "colorIdentity": ["B","G"]}], - "Breya, Etherium Shaper": [{"source": "edhrec", "archetype": "combo", "colorIdentity": ["W","U","B","R"]}], -} - - # ══════════════════════════════════════════════════════════════ # Python Simulator API # ══════════════════════════════════════════════════════════════ @@ -4405,21 +4396,7 @@ def resolve_forge_decks_dir() -> str: return "" -def load_commander_meta(): - """Load commander meta mapping from file or use builtins.""" - global COMMANDER_META - meta_path = Path(__file__).parent / "commander-meta.json" - if meta_path.exists(): - try: - with open(meta_path, "r", encoding="utf-8") as f: - COMMANDER_META = json.load(f) - log.info(f" Meta: Loaded {len(COMMANDER_META)} commanders from {meta_path}") - return - except Exception as e: - log.warning(f" WARNING: Failed to load commander-meta.json: {e}") - - COMMANDER_META = BUILTIN_COMMANDERS - log.info(f" Meta: {len(COMMANDER_META)} built-in commanders") +# load_commander_meta() is now in routes/shared.py def main(): diff --git a/routes/import_routes.py b/routes/import_routes.py index 8479868..e1681a4 100644 --- a/routes/import_routes.py +++ b/routes/import_routes.py @@ -15,8 +15,8 @@ from typing import Optional from fastapi import APIRouter, HTTPException, BackgroundTasks from fastapi.responses import JSONResponse +import routes.shared as _shared from routes.shared import ( - COMMANDER_META, ImportUrlRequest, ImportTextRequest, MetaFetchRequest, @@ -74,7 +74,7 @@ async def import_from_text(req: ImportTextRequest): async def list_meta_commanders(): """List available commanders in the meta mapping.""" commanders = [] - for name, entries in COMMANDER_META.items(): + for name, entries in _shared.COMMANDER_META.items(): entry = entries[0] if entries else {} commanders.append({ "name": name, @@ -90,7 +90,7 @@ async def search_meta_commanders(q: str = ""): """Search commanders by partial name match.""" query = q.lower() matches = [] - for name, entries in COMMANDER_META.items(): + for name, entries in _shared.COMMANDER_META.items(): if query in name.lower(): entry = entries[0] if entries else {} matches.append({ diff --git a/routes/precon.py b/routes/precon.py index 86cedf3..ab6ec58 100644 --- a/routes/precon.py +++ b/routes/precon.py @@ -15,13 +15,8 @@ from pathlib import Path from fastapi import APIRouter, HTTPException -from routes.shared import ( - CFG, - PRECON_INDEX, - PRECON_DIR, - download_precon_database, - log, -) +import routes.shared as _shared # module reference, not value import +from routes.shared import CFG, PRECON_DIR, download_precon_database, log router = APIRouter(tags=["precon"]) @@ -29,7 +24,7 @@ router = APIRouter(tags=["precon"]) @router.get("/api/lab/precons") async def list_precons(): """List all available precon decks.""" - return {"precons": PRECON_INDEX} + return {"precons": _shared.PRECON_INDEX} # reads current value every call @router.post("/api/lab/precons/install") diff --git a/routes/shared.py b/routes/shared.py index 832177e..bf037a9 100644 --- a/routes/shared.py +++ b/routes/shared.py @@ -148,6 +148,40 @@ class BatchState: active_batches: dict[str, BatchState] = {} COMMANDER_META: dict = {} +BUILTIN_COMMANDERS: dict = { + "Edgar Markov": [{"source": "edhrec", "archetype": "aggro", "colorIdentity": ["W","B","R"]}], + "Atraxa, Praetors' Voice": [{"source": "edhrec", "archetype": "midrange", "colorIdentity": ["W","U","B","G"]}], + "Korvold, Fae-Cursed King": [{"source": "edhrec", "archetype": "combo", "colorIdentity": ["B","R","G"]}], + "Muldrotha, the Gravetide": [{"source": "edhrec", "archetype": "midrange", "colorIdentity": ["U","B","G"]}], + "The Ur-Dragon": [{"source": "edhrec", "archetype": "midrange", "colorIdentity": ["W","U","B","R","G"]}], + "Yuriko, the Tiger's Shadow": [{"source": "edhrec", "archetype": "aggro", "colorIdentity": ["U","B"]}], + "Krenko, Mob Boss": [{"source": "edhrec", "archetype": "aggro", "colorIdentity": ["R"]}], + "Meren of Clan Nel Toth": [{"source": "edhrec", "archetype": "midrange", "colorIdentity": ["B","G"]}], + "Prossh, Skyraider of Kher": [{"source": "edhrec", "archetype": "combo", "colorIdentity": ["B","R","G"]}], + "Kaalia of the Vast": [{"source": "edhrec", "archetype": "aggro", "colorIdentity": ["W","B","R"]}], + "Talrand, Sky Summoner": [{"source": "edhrec", "archetype": "control", "colorIdentity": ["U"]}], + "Omnath, Locus of Creation": [{"source": "edhrec", "archetype": "combo", "colorIdentity": ["W","U","R","G"]}], + "Teysa Karlov": [{"source": "edhrec", "archetype": "combo", "colorIdentity": ["W","B"]}], + "Lathril, Blade of the Elves": [{"source": "edhrec", "archetype": "aggro", "colorIdentity": ["B","G"]}], + "Breya, Etherium Shaper": [{"source": "edhrec", "archetype": "combo", "colorIdentity": ["W","U","B","R"]}], +} + + +def load_commander_meta() -> None: + """Load commander meta mapping from file or fall back to builtins.""" + global COMMANDER_META + meta_path = Path(__file__).parent.parent / "commander-meta.json" + if meta_path.exists(): + try: + with open(meta_path, "r", encoding="utf-8") as f: + COMMANDER_META = json.load(f) + log.info(f" Meta: Loaded {len(COMMANDER_META)} commanders from {meta_path}") + return + except Exception as e: + log.warning(f" WARNING: Failed to load commander-meta.json: {e}") + COMMANDER_META = BUILTIN_COMMANDERS + log.info(f" Meta: {len(COMMANDER_META)} built-in commanders") + # ══════════════════════════════════════════════════════════════ # Pydantic Models