mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-12-29 05:38:49 +01:00
fix(lint): improved type checking and code quality (77% error reduction)
This commit is contained in:
parent
3c45a31aa3
commit
83fe527979
37 changed files with 423 additions and 303 deletions
|
|
@ -19,9 +19,9 @@ from contextlib import asynccontextmanager
|
|||
from code.deck_builder.summary_telemetry import get_mdfc_metrics, get_partner_metrics, get_theme_metrics
|
||||
from tagging.multi_face_merger import load_merge_summary
|
||||
from .services.combo_utils import detect_all as _detect_all
|
||||
from .services.theme_catalog_loader import prewarm_common_filters, load_index # type: ignore
|
||||
from .services.commander_catalog_loader import load_commander_catalog # type: ignore
|
||||
from .services.tasks import get_session, new_sid, set_session_value # type: ignore
|
||||
from .services.theme_catalog_loader import prewarm_common_filters, load_index
|
||||
from .services.commander_catalog_loader import load_commander_catalog
|
||||
from .services.tasks import get_session, new_sid, set_session_value
|
||||
|
||||
# Logger for app-level logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -56,18 +56,18 @@ async def _lifespan(app: FastAPI): # pragma: no cover - simple infra glue
|
|||
except Exception:
|
||||
pass
|
||||
try:
|
||||
commanders_routes.prewarm_default_page() # type: ignore[attr-defined]
|
||||
commanders_routes.prewarm_default_page()
|
||||
except Exception:
|
||||
pass
|
||||
# Warm preview card index once (updated Phase A: moved to card_index module)
|
||||
try: # local import to avoid cost if preview unused
|
||||
from .services.card_index import maybe_build_index # type: ignore
|
||||
from .services.card_index import maybe_build_index
|
||||
maybe_build_index()
|
||||
except Exception:
|
||||
pass
|
||||
# Warm card browser theme catalog (fast CSV read) and theme index (slower card parsing)
|
||||
try:
|
||||
from .routes.card_browser import get_theme_catalog, get_theme_index # type: ignore
|
||||
from .routes.card_browser import get_theme_catalog, get_theme_index
|
||||
get_theme_catalog() # Fast: just reads CSV
|
||||
get_theme_index() # Slower: parses cards for theme-to-card mapping
|
||||
except Exception:
|
||||
|
|
@ -76,7 +76,7 @@ async def _lifespan(app: FastAPI): # pragma: no cover - simple infra glue
|
|||
try:
|
||||
from code.settings import ENABLE_CARD_DETAILS
|
||||
if ENABLE_CARD_DETAILS:
|
||||
from .routes.card_browser import get_similarity # type: ignore
|
||||
from .routes.card_browser import get_similarity
|
||||
get_similarity() # Pre-initialize singleton (one-time cost: ~2-3s)
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -89,7 +89,7 @@ app.add_middleware(GZipMiddleware, minimum_size=500)
|
|||
# Mount static if present
|
||||
if _STATIC_DIR.exists():
|
||||
class CacheStatic(StaticFiles):
|
||||
async def get_response(self, path, scope): # type: ignore[override]
|
||||
async def get_response(self, path, scope):
|
||||
resp = await super().get_response(path, scope)
|
||||
try:
|
||||
# Add basic cache headers for static assets
|
||||
|
|
@ -133,7 +133,7 @@ templates.env.filters["card_image"] = card_image_url
|
|||
# Prevents DeprecationWarning noise in tests without touching all call sites.
|
||||
_orig_template_response = templates.TemplateResponse
|
||||
|
||||
def _compat_template_response(*args, **kwargs): # type: ignore[override]
|
||||
def _compat_template_response(*args, **kwargs):
|
||||
try:
|
||||
if args and isinstance(args[0], str):
|
||||
name = args[0]
|
||||
|
|
@ -151,7 +151,7 @@ def _compat_template_response(*args, **kwargs): # type: ignore[override]
|
|||
pass
|
||||
return _orig_template_response(*args, **kwargs)
|
||||
|
||||
templates.TemplateResponse = _compat_template_response # type: ignore[assignment]
|
||||
templates.TemplateResponse = _compat_template_response
|
||||
|
||||
# (Startup prewarm moved to lifespan handler _lifespan)
|
||||
|
||||
|
|
@ -327,7 +327,7 @@ templates.env.globals.update({
|
|||
# Expose catalog hash (for cache versioning / service worker) – best-effort, fallback to 'dev'
|
||||
def _load_catalog_hash() -> str:
|
||||
try: # local import to avoid circular on early load
|
||||
from .services.theme_catalog_loader import CATALOG_JSON # type: ignore
|
||||
from .services.theme_catalog_loader import CATALOG_JSON
|
||||
if CATALOG_JSON.exists():
|
||||
raw = _json.loads(CATALOG_JSON.read_text(encoding="utf-8") or "{}")
|
||||
meta = raw.get("metadata_info") or {}
|
||||
|
|
@ -951,7 +951,7 @@ async def status_random_theme_stats():
|
|||
if not SHOW_DIAGNOSTICS:
|
||||
raise HTTPException(status_code=404, detail="Not Found")
|
||||
try:
|
||||
from deck_builder.random_entrypoint import get_theme_tag_stats # type: ignore
|
||||
from deck_builder.random_entrypoint import get_theme_tag_stats
|
||||
|
||||
stats = get_theme_tag_stats()
|
||||
return JSONResponse({"ok": True, "stats": stats})
|
||||
|
|
@ -1038,8 +1038,8 @@ async def api_random_build(request: Request):
|
|||
except Exception:
|
||||
timeout_s = max(0.1, float(RANDOM_TIMEOUT_MS) / 1000.0)
|
||||
# Import on-demand to avoid heavy costs at module import time
|
||||
from deck_builder.random_entrypoint import build_random_deck, RandomConstraintsImpossibleError # type: ignore
|
||||
from deck_builder.random_entrypoint import RandomThemeNoMatchError # type: ignore
|
||||
from deck_builder.random_entrypoint import build_random_deck, RandomConstraintsImpossibleError
|
||||
from deck_builder.random_entrypoint import RandomThemeNoMatchError
|
||||
|
||||
res = build_random_deck(
|
||||
theme=theme,
|
||||
|
|
@ -1170,7 +1170,7 @@ async def api_random_full_build(request: Request):
|
|||
timeout_s = max(0.1, float(RANDOM_TIMEOUT_MS) / 1000.0)
|
||||
|
||||
# Build a full deck deterministically
|
||||
from deck_builder.random_entrypoint import build_random_full_deck, RandomConstraintsImpossibleError # type: ignore
|
||||
from deck_builder.random_entrypoint import build_random_full_deck, RandomConstraintsImpossibleError
|
||||
res = build_random_full_deck(
|
||||
theme=theme,
|
||||
constraints=constraints,
|
||||
|
|
@ -1394,7 +1394,7 @@ async def api_random_reroll(request: Request):
|
|||
except Exception:
|
||||
new_seed = None
|
||||
if new_seed is None:
|
||||
from random_util import generate_seed # type: ignore
|
||||
from random_util import generate_seed
|
||||
new_seed = int(generate_seed())
|
||||
|
||||
# Build with the new seed
|
||||
|
|
@ -1405,7 +1405,7 @@ async def api_random_reroll(request: Request):
|
|||
timeout_s = max(0.1, float(RANDOM_TIMEOUT_MS) / 1000.0)
|
||||
attempts = body.get("attempts", int(RANDOM_MAX_ATTEMPTS))
|
||||
|
||||
from deck_builder.random_entrypoint import build_random_full_deck # type: ignore
|
||||
from deck_builder.random_entrypoint import build_random_full_deck
|
||||
res = build_random_full_deck(
|
||||
theme=theme,
|
||||
constraints=constraints,
|
||||
|
|
@ -1786,10 +1786,10 @@ async def hx_random_reroll(request: Request):
|
|||
except Exception:
|
||||
new_seed = None
|
||||
if new_seed is None:
|
||||
from random_util import generate_seed # type: ignore
|
||||
from random_util import generate_seed
|
||||
new_seed = int(generate_seed())
|
||||
# Import outside conditional to avoid UnboundLocalError when branch not taken
|
||||
from deck_builder.random_entrypoint import build_random_full_deck # type: ignore
|
||||
from deck_builder.random_entrypoint import build_random_full_deck
|
||||
try:
|
||||
t0 = time.time()
|
||||
_attempts = int(attempts_override) if attempts_override is not None else int(RANDOM_MAX_ATTEMPTS)
|
||||
|
|
@ -1800,7 +1800,7 @@ async def hx_random_reroll(request: Request):
|
|||
_timeout_s = max(0.1, float(_timeout_ms) / 1000.0)
|
||||
if is_reroll_same:
|
||||
build_t0 = time.time()
|
||||
from headless_runner import run as _run # type: ignore
|
||||
from headless_runner import run as _run
|
||||
# Suppress builder's internal initial export to control artifact generation (matches full random path logic)
|
||||
try:
|
||||
import os as _os
|
||||
|
|
@ -1813,18 +1813,18 @@ async def hx_random_reroll(request: Request):
|
|||
summary = None
|
||||
try:
|
||||
if hasattr(builder, 'build_deck_summary'):
|
||||
summary = builder.build_deck_summary() # type: ignore[attr-defined]
|
||||
summary = builder.build_deck_summary()
|
||||
except Exception:
|
||||
summary = None
|
||||
decklist = []
|
||||
try:
|
||||
if hasattr(builder, 'deck_list_final'):
|
||||
decklist = getattr(builder, 'deck_list_final') # type: ignore[attr-defined]
|
||||
decklist = getattr(builder, 'deck_list_final')
|
||||
except Exception:
|
||||
decklist = []
|
||||
# Controlled artifact export (single pass)
|
||||
csv_path = getattr(builder, 'last_csv_path', None) # type: ignore[attr-defined]
|
||||
txt_path = getattr(builder, 'last_txt_path', None) # type: ignore[attr-defined]
|
||||
csv_path = getattr(builder, 'last_csv_path', None)
|
||||
txt_path = getattr(builder, 'last_txt_path', None)
|
||||
compliance = None
|
||||
try:
|
||||
import os as _os
|
||||
|
|
@ -1832,7 +1832,7 @@ async def hx_random_reroll(request: Request):
|
|||
# Perform exactly one export sequence now
|
||||
if not csv_path and hasattr(builder, 'export_decklist_csv'):
|
||||
try:
|
||||
csv_path = builder.export_decklist_csv() # type: ignore[attr-defined]
|
||||
csv_path = builder.export_decklist_csv()
|
||||
except Exception:
|
||||
csv_path = None
|
||||
if csv_path and isinstance(csv_path, str):
|
||||
|
|
@ -1842,7 +1842,7 @@ async def hx_random_reroll(request: Request):
|
|||
try:
|
||||
base_name = _os.path.basename(base_path) + '.txt'
|
||||
if hasattr(builder, 'export_decklist_text'):
|
||||
txt_path = builder.export_decklist_text(filename=base_name) # type: ignore[attr-defined]
|
||||
txt_path = builder.export_decklist_text(filename=base_name)
|
||||
except Exception:
|
||||
# Fallback: if a txt already exists from a prior build reuse it
|
||||
if _os.path.isfile(base_path + '.txt'):
|
||||
|
|
@ -1857,7 +1857,7 @@ async def hx_random_reroll(request: Request):
|
|||
else:
|
||||
try:
|
||||
if hasattr(builder, 'compute_and_print_compliance'):
|
||||
compliance = builder.compute_and_print_compliance(base_stem=_os.path.basename(base_path)) # type: ignore[attr-defined]
|
||||
compliance = builder.compute_and_print_compliance(base_stem=_os.path.basename(base_path))
|
||||
except Exception:
|
||||
compliance = None
|
||||
if summary:
|
||||
|
|
@ -2051,7 +2051,7 @@ async def hx_random_reroll(request: Request):
|
|||
except Exception:
|
||||
_permalink = None
|
||||
resp = templates.TemplateResponse(
|
||||
"partials/random_result.html", # type: ignore
|
||||
"partials/random_result.html",
|
||||
{
|
||||
"request": request,
|
||||
"seed": int(res.seed),
|
||||
|
|
@ -2467,7 +2467,7 @@ async def logs_page(
|
|||
# Respect feature flag
|
||||
raise HTTPException(status_code=404, detail="Not Found")
|
||||
# Reuse status_logs logic
|
||||
data = await status_logs(tail=tail, q=q, level=level) # type: ignore[arg-type]
|
||||
data = await status_logs(tail=tail, q=q, level=level)
|
||||
lines: list[str]
|
||||
if isinstance(data, JSONResponse):
|
||||
payload = data.body
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ from ..services.build_utils import (
|
|||
from ..app import templates
|
||||
from deck_builder import builder_constants as bc
|
||||
from ..services import orchestrator as orch
|
||||
from ..services.orchestrator import is_setup_ready as _is_setup_ready, is_setup_stale as _is_setup_stale # type: ignore
|
||||
from ..services.orchestrator import is_setup_ready as _is_setup_ready, is_setup_stale as _is_setup_stale
|
||||
from ..services.build_utils import owned_names as owned_names_helper
|
||||
from ..services.tasks import get_session, new_sid
|
||||
from html import escape as _esc
|
||||
|
|
@ -119,7 +119,7 @@ def _available_cards_normalized() -> tuple[set[str], dict[str, str]]:
|
|||
from deck_builder.include_exclude_utils import normalize_punctuation
|
||||
except Exception:
|
||||
# Fallback: identity normalization
|
||||
def normalize_punctuation(x: str) -> str: # type: ignore
|
||||
def normalize_punctuation(x: str) -> str:
|
||||
return str(x).strip().casefold()
|
||||
norm_map: dict[str, str] = {}
|
||||
for name in names:
|
||||
|
|
@ -470,7 +470,7 @@ def _background_options_from_commander_catalog() -> list[dict[str, Any]]:
|
|||
|
||||
seen: set[str] = set()
|
||||
options: list[dict[str, Any]] = []
|
||||
for record in getattr(catalog, "entries", ()): # type: ignore[attr-defined]
|
||||
for record in getattr(catalog, "entries", ()):
|
||||
if not getattr(record, "is_background", False):
|
||||
continue
|
||||
name = getattr(record, "display_name", None)
|
||||
|
|
@ -2865,7 +2865,7 @@ async def build_step5_rewind(request: Request, to: str = Form(...)) -> HTMLRespo
|
|||
snap = h.get("snapshot")
|
||||
break
|
||||
if snap is not None:
|
||||
orch._restore_builder(ctx["builder"], snap) # type: ignore[attr-defined]
|
||||
orch._restore_builder(ctx["builder"], snap)
|
||||
ctx["idx"] = int(target_i) - 1
|
||||
ctx["last_visible_idx"] = int(target_i) - 1
|
||||
except Exception:
|
||||
|
|
@ -3869,7 +3869,7 @@ async def build_step5_reset_stage(request: Request) -> HTMLResponse:
|
|||
if not ctx or not ctx.get("snapshot"):
|
||||
return await build_step5_get(request)
|
||||
try:
|
||||
orch._restore_builder(ctx["builder"], ctx["snapshot"]) # type: ignore[attr-defined]
|
||||
orch._restore_builder(ctx["builder"], ctx["snapshot"])
|
||||
except Exception:
|
||||
return await build_step5_get(request)
|
||||
# Re-render step 5 with cleared added list
|
||||
|
|
@ -4293,7 +4293,7 @@ async def build_alternatives(
|
|||
try:
|
||||
if rng is not None:
|
||||
return rng.sample(seq, limit) if len(seq) >= limit else list(seq)
|
||||
import random as _rnd # type: ignore
|
||||
import random as _rnd
|
||||
return _rnd.sample(seq, limit) if len(seq) >= limit else list(seq)
|
||||
except Exception:
|
||||
return list(seq[:limit])
|
||||
|
|
@ -4344,7 +4344,7 @@ async def build_alternatives(
|
|||
# Helper: map display names
|
||||
def _display_map_for(lower_pool: set[str]) -> dict[str, str]:
|
||||
try:
|
||||
return builder_display_map(b, lower_pool) # type: ignore[arg-type]
|
||||
return builder_display_map(b, lower_pool)
|
||||
except Exception:
|
||||
return {nm: nm for nm in lower_pool}
|
||||
|
||||
|
|
@ -4522,7 +4522,7 @@ async def build_alternatives(
|
|||
pass
|
||||
# Sort by priority like the builder
|
||||
try:
|
||||
pool = bu.sort_by_priority(pool, ["edhrecRank","manaValue"]) # type: ignore[arg-type]
|
||||
pool = bu.sort_by_priority(pool, ["edhrecRank","manaValue"])
|
||||
except Exception:
|
||||
pass
|
||||
# Exclusions and ownership (for non-random roles this stays before slicing)
|
||||
|
|
@ -5020,13 +5020,13 @@ async def build_compliance_panel(request: Request) -> HTMLResponse:
|
|||
comp = None
|
||||
try:
|
||||
if hasattr(b, 'compute_and_print_compliance'):
|
||||
comp = b.compute_and_print_compliance(base_stem=None) # type: ignore[attr-defined]
|
||||
comp = b.compute_and_print_compliance(base_stem=None)
|
||||
except Exception:
|
||||
comp = None
|
||||
try:
|
||||
if comp:
|
||||
from ..services import orchestrator as orch
|
||||
comp = orch._attach_enforcement_plan(b, comp) # type: ignore[attr-defined]
|
||||
comp = orch._attach_enforcement_plan(b, comp)
|
||||
except Exception:
|
||||
pass
|
||||
if not comp:
|
||||
|
|
@ -5151,11 +5151,11 @@ async def build_enforce_apply(request: Request) -> HTMLResponse:
|
|||
# If missing, export once to establish base
|
||||
if not base_stem:
|
||||
try:
|
||||
ctx["csv_path"] = b.export_decklist_csv() # type: ignore[attr-defined]
|
||||
ctx["csv_path"] = b.export_decklist_csv()
|
||||
import os as _os
|
||||
base_stem = _os.path.splitext(_os.path.basename(ctx["csv_path"]))[0]
|
||||
# Also produce a text export for completeness
|
||||
ctx["txt_path"] = b.export_decklist_text(filename=base_stem + '.txt') # type: ignore[attr-defined]
|
||||
ctx["txt_path"] = b.export_decklist_text(filename=base_stem + '.txt')
|
||||
except Exception:
|
||||
base_stem = None
|
||||
# Add lock placeholders into the library before enforcement so user choices are present
|
||||
|
|
@ -5200,7 +5200,7 @@ async def build_enforce_apply(request: Request) -> HTMLResponse:
|
|||
pass
|
||||
# Run enforcement + re-exports (tops up to 100 internally)
|
||||
try:
|
||||
rep = b.enforce_and_reexport(base_stem=base_stem, mode='auto') # type: ignore[attr-defined]
|
||||
rep = b.enforce_and_reexport(base_stem=base_stem, mode='auto')
|
||||
except Exception as e:
|
||||
err_ctx = step5_error_ctx(request, sess, f"Enforcement failed: {e}")
|
||||
resp = templates.TemplateResponse("build/_step5.html", err_ctx)
|
||||
|
|
@ -5274,13 +5274,13 @@ async def build_enforcement_fullpage(request: Request) -> HTMLResponse:
|
|||
comp = None
|
||||
try:
|
||||
if hasattr(b, 'compute_and_print_compliance'):
|
||||
comp = b.compute_and_print_compliance(base_stem=None) # type: ignore[attr-defined]
|
||||
comp = b.compute_and_print_compliance(base_stem=None)
|
||||
except Exception:
|
||||
comp = None
|
||||
try:
|
||||
if comp:
|
||||
from ..services import orchestrator as orch
|
||||
comp = orch._attach_enforcement_plan(b, comp) # type: ignore[attr-defined]
|
||||
comp = orch._attach_enforcement_plan(b, comp)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -425,7 +425,7 @@ async def decks_compare(request: Request, A: Optional[str] = None, B: Optional[s
|
|||
mt_val = str(int(mt))
|
||||
except Exception:
|
||||
mt_val = "0"
|
||||
options.append({"name": it.get("name"), "label": label, "mtime": mt_val}) # type: ignore[arg-type]
|
||||
options.append({"name": it.get("name"), "label": label, "mtime": mt_val})
|
||||
|
||||
diffs = None
|
||||
metaA: Dict[str, str] = {}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from pathlib import Path
|
|||
import json as _json
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from ..app import templates
|
||||
from ..services.orchestrator import _ensure_setup_ready # type: ignore
|
||||
from ..services.orchestrator import _ensure_setup_ready
|
||||
|
||||
router = APIRouter(prefix="/setup")
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ def _kickoff_setup_async(force: bool = False):
|
|||
def runner():
|
||||
try:
|
||||
print(f"[SETUP THREAD] Starting setup/tagging (force={force})...")
|
||||
_ensure_setup_ready(print, force=force) # type: ignore[arg-type]
|
||||
_ensure_setup_ready(print, force=force)
|
||||
print("[SETUP THREAD] Setup/tagging completed successfully")
|
||||
except Exception as e: # pragma: no cover - background best effort
|
||||
try:
|
||||
|
|
@ -36,7 +36,7 @@ def _kickoff_setup_async(force: bool = False):
|
|||
|
||||
|
||||
@router.get("/running", response_class=HTMLResponse)
|
||||
async def setup_running(request: Request, start: Optional[int] = 0, next: Optional[str] = None, force: Optional[bool] = None) -> HTMLResponse: # type: ignore[override]
|
||||
async def setup_running(request: Request, start: Optional[int] = 0, next: Optional[str] = None, force: Optional[bool] = None) -> HTMLResponse:
|
||||
# Optionally start the setup/tagging in the background if requested
|
||||
try:
|
||||
if start and int(start) != 0:
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from typing import Optional, Dict, Any
|
|||
|
||||
from fastapi import APIRouter, Request, HTTPException, Query
|
||||
from fastapi import BackgroundTasks
|
||||
from ..services.orchestrator import _ensure_setup_ready, _run_theme_metadata_enrichment # type: ignore
|
||||
from ..services.orchestrator import _ensure_setup_ready, _run_theme_metadata_enrichment
|
||||
from fastapi.responses import JSONResponse, HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from ..services.theme_catalog_loader import (
|
||||
|
|
@ -17,10 +17,10 @@ from ..services.theme_catalog_loader import (
|
|||
filter_slugs_fast,
|
||||
summaries_for_slugs,
|
||||
)
|
||||
from ..services.theme_preview import get_theme_preview # type: ignore
|
||||
from ..services.theme_catalog_loader import catalog_metrics, prewarm_common_filters # type: ignore
|
||||
from ..services.theme_preview import preview_metrics # type: ignore
|
||||
from ..services import theme_preview as _theme_preview_mod # type: ignore # for error counters
|
||||
from ..services.theme_preview import get_theme_preview
|
||||
from ..services.theme_catalog_loader import catalog_metrics, prewarm_common_filters
|
||||
from ..services.theme_preview import preview_metrics
|
||||
from ..services import theme_preview as _theme_preview_mod # for error counters
|
||||
import os
|
||||
from fastapi import Body
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ router = APIRouter(prefix="/themes", tags=["themes"]) # /themes/status
|
|||
|
||||
# Reuse the main app's template environment so nav globals stay consistent.
|
||||
try: # circular-safe import: app defines templates before importing this router
|
||||
from ..app import templates as _templates # type: ignore
|
||||
from ..app import templates as _templates
|
||||
except Exception: # Fallback (tests/minimal contexts)
|
||||
_templates = Jinja2Templates(directory=str(Path(__file__).resolve().parent.parent / 'templates'))
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ async def theme_suggest(
|
|||
# Optional rate limit using app helper if available
|
||||
rl_result = None
|
||||
try:
|
||||
from ..app import rate_limit_check # type: ignore
|
||||
from ..app import rate_limit_check
|
||||
rl_result = rate_limit_check(request, "suggest")
|
||||
except HTTPException as http_ex: # propagate 429 with headers
|
||||
raise http_ex
|
||||
|
|
@ -231,7 +231,7 @@ async def theme_status():
|
|||
yaml_file_count = 0
|
||||
if yaml_catalog_exists:
|
||||
try:
|
||||
yaml_file_count = len([p for p in CATALOG_DIR.iterdir() if p.suffix == ".yml"]) # type: ignore[arg-type]
|
||||
yaml_file_count = len([p for p in CATALOG_DIR.iterdir() if p.suffix == ".yml"])
|
||||
except Exception:
|
||||
yaml_file_count = -1
|
||||
tagged_time = _load_tag_flag_time()
|
||||
|
|
@ -547,7 +547,7 @@ async def theme_yaml(theme_id: str):
|
|||
raise HTTPException(status_code=404, detail="yaml_not_found")
|
||||
# Reconstruct minimal YAML (we have dict already)
|
||||
import yaml as _yaml # local import to keep top-level lean
|
||||
text = _yaml.safe_dump(y, sort_keys=False) # type: ignore
|
||||
text = _yaml.safe_dump(y, sort_keys=False)
|
||||
headers = {"Content-Type": "text/plain; charset=utf-8"}
|
||||
return HTMLResponse(text, headers=headers)
|
||||
|
||||
|
|
@ -631,7 +631,7 @@ async def api_theme_search(
|
|||
prefix: list[dict[str, Any]] = []
|
||||
substr: list[dict[str, Any]] = []
|
||||
seen: set[str] = set()
|
||||
themes_iter = list(idx.catalog.themes) # type: ignore[attr-defined]
|
||||
themes_iter = list(idx.catalog.themes)
|
||||
# Phase 1 + 2: exact / prefix
|
||||
for t in themes_iter:
|
||||
name = t.theme
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ def commander_hover_context(
|
|||
from .summary_utils import format_theme_label, format_theme_list
|
||||
except Exception:
|
||||
# Fallbacks in the unlikely event of circular import issues
|
||||
def format_theme_label(value: Any) -> str: # type: ignore[redef]
|
||||
def format_theme_label(value: Any) -> str:
|
||||
text = str(value or "").strip().replace("_", " ")
|
||||
if not text:
|
||||
return ""
|
||||
|
|
@ -214,10 +214,10 @@ def commander_hover_context(
|
|||
parts.append(chunk[:1].upper() + chunk[1:].lower())
|
||||
return " ".join(parts)
|
||||
|
||||
def format_theme_list(values: Iterable[Any]) -> list[str]: # type: ignore[redef]
|
||||
def format_theme_list(values: Iterable[Any]) -> list[str]:
|
||||
seen: set[str] = set()
|
||||
result: list[str] = []
|
||||
for raw in values or []: # type: ignore[arg-type]
|
||||
for raw in values or []:
|
||||
label = format_theme_label(raw)
|
||||
if not label or len(label) <= 1:
|
||||
continue
|
||||
|
|
@ -420,7 +420,7 @@ def step5_ctx_from_result(
|
|||
else:
|
||||
entry = {}
|
||||
try:
|
||||
entry.update(vars(item)) # type: ignore[arg-type]
|
||||
entry.update(vars(item))
|
||||
except Exception:
|
||||
pass
|
||||
# Preserve common attributes when vars() empty
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ def _global_prune_disallowed_pool(b: DeckBuilder) -> None:
|
|||
drop_idx = tags_series.apply(lambda lst, nd=needles: _has_any(lst, nd))
|
||||
mask_keep = [mk and (not di) for mk, di in zip(mask_keep, drop_idx.tolist())]
|
||||
try:
|
||||
import pandas as _pd # type: ignore
|
||||
import pandas as _pd
|
||||
mask_keep = _pd.Series(mask_keep, index=work.index)
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -480,7 +480,7 @@ def commander_candidates(query: str, limit: int = 10) -> List[Tuple[str, int, Li
|
|||
tmp = DeckBuilder()
|
||||
try:
|
||||
if hasattr(tmp, '_normalize_commander_query'):
|
||||
query = tmp._normalize_commander_query(query) # type: ignore[attr-defined]
|
||||
query = tmp._normalize_commander_query(query)
|
||||
else:
|
||||
# Light fallback: basic title case
|
||||
query = ' '.join([w[:1].upper() + w[1:].lower() if w else w for w in str(query).split(' ')])
|
||||
|
|
@ -653,7 +653,7 @@ def commander_select(name: str) -> Dict[str, Any]:
|
|||
if row.empty:
|
||||
try:
|
||||
if hasattr(tmp, '_normalize_commander_query'):
|
||||
name2 = tmp._normalize_commander_query(name) # type: ignore[attr-defined]
|
||||
name2 = tmp._normalize_commander_query(name)
|
||||
else:
|
||||
name2 = ' '.join([w[:1].upper() + w[1:].lower() if w else w for w in str(name).split(' ')])
|
||||
row = df[df["name"] == name2]
|
||||
|
|
@ -1288,8 +1288,8 @@ def _ensure_setup_ready(out, force: bool = False) -> None:
|
|||
pass
|
||||
# Bust theme-related in-memory caches so new catalog reflects immediately
|
||||
try:
|
||||
from .theme_catalog_loader import bust_filter_cache # type: ignore
|
||||
from .theme_preview import bust_preview_cache # type: ignore
|
||||
from .theme_catalog_loader import bust_filter_cache
|
||||
from .theme_preview import bust_preview_cache
|
||||
bust_filter_cache("catalog_refresh")
|
||||
bust_preview_cache("catalog_refresh")
|
||||
try:
|
||||
|
|
@ -1327,7 +1327,7 @@ def _ensure_setup_ready(out, force: bool = False) -> None:
|
|||
|
||||
try:
|
||||
# M4 (Parquet Migration): Check for processed Parquet file instead of CSV
|
||||
from path_util import get_processed_cards_path # type: ignore
|
||||
from path_util import get_processed_cards_path
|
||||
cards_path = get_processed_cards_path()
|
||||
flag_path = os.path.join('csv_files', '.tagging_complete.json')
|
||||
auto_setup_enabled = _is_truthy_env('WEB_AUTO_SETUP', '1')
|
||||
|
|
@ -1416,7 +1416,7 @@ def _ensure_setup_ready(out, force: bool = False) -> None:
|
|||
_write_status({"running": True, "phase": "setup", "message": "GitHub download failed, running local setup...", "percent": 0})
|
||||
|
||||
try:
|
||||
from file_setup.setup import initial_setup # type: ignore
|
||||
from file_setup.setup import initial_setup
|
||||
# Always run initial_setup when forced or when cards are missing/stale
|
||||
initial_setup()
|
||||
except Exception as e:
|
||||
|
|
@ -1425,7 +1425,7 @@ def _ensure_setup_ready(out, force: bool = False) -> None:
|
|||
return
|
||||
# M4 (Parquet Migration): Use unified run_tagging with parallel support
|
||||
try:
|
||||
from tagging import tagger as _tagger # type: ignore
|
||||
from tagging import tagger as _tagger
|
||||
use_parallel = str(os.getenv('WEB_TAG_PARALLEL', '1')).strip().lower() in {"1","true","yes","on"}
|
||||
max_workers_env = os.getenv('WEB_TAG_WORKERS')
|
||||
try:
|
||||
|
|
@ -1466,7 +1466,7 @@ def _ensure_setup_ready(out, force: bool = False) -> None:
|
|||
try:
|
||||
_write_status({"running": True, "phase": "aggregating", "message": "Consolidating card data...", "percent": 90})
|
||||
out("Aggregating card CSVs into Parquet files...")
|
||||
from file_setup.card_aggregator import CardAggregator # type: ignore
|
||||
from file_setup.card_aggregator import CardAggregator
|
||||
aggregator = CardAggregator()
|
||||
|
||||
# Aggregate all_cards.parquet
|
||||
|
|
@ -1474,7 +1474,7 @@ def _ensure_setup_ready(out, force: bool = False) -> None:
|
|||
out(f"Aggregated {stats['total_cards']} cards into all_cards.parquet ({stats['file_size_mb']} MB)")
|
||||
|
||||
# Convert commander_cards.csv and background_cards.csv to Parquet
|
||||
import pandas as pd # type: ignore
|
||||
import pandas as pd
|
||||
|
||||
# Convert commander_cards.csv
|
||||
commander_csv = 'csv_files/commander_cards.csv'
|
||||
|
|
@ -1524,8 +1524,8 @@ def _ensure_setup_ready(out, force: bool = False) -> None:
|
|||
# Generate / refresh theme catalog (JSON + per-theme YAML) BEFORE marking done so UI sees progress
|
||||
_refresh_theme_catalog(out, force=True, fast_path=False)
|
||||
try:
|
||||
from .theme_catalog_loader import bust_filter_cache # type: ignore
|
||||
from .theme_preview import bust_preview_cache # type: ignore
|
||||
from .theme_catalog_loader import bust_filter_cache
|
||||
from .theme_preview import bust_preview_cache
|
||||
bust_filter_cache("tagging_complete")
|
||||
bust_preview_cache("tagging_complete")
|
||||
except Exception:
|
||||
|
|
@ -1721,19 +1721,19 @@ def run_build(commander: str, tags: List[str], bracket: int, ideals: Dict[str, i
|
|||
# Owned/Prefer-owned integration (optional for headless runs)
|
||||
try:
|
||||
if use_owned_only:
|
||||
b.use_owned_only = True # type: ignore[attr-defined]
|
||||
b.use_owned_only = True
|
||||
# Prefer explicit owned_names list if provided; else let builder discover from files
|
||||
if owned_names:
|
||||
try:
|
||||
b.owned_card_names = set(str(n).strip() for n in owned_names if str(n).strip()) # type: ignore[attr-defined]
|
||||
b.owned_card_names = set(str(n).strip() for n in owned_names if str(n).strip())
|
||||
except Exception:
|
||||
b.owned_card_names = set() # type: ignore[attr-defined]
|
||||
b.owned_card_names = set()
|
||||
# Soft preference flag does not filter; only biases selection order
|
||||
if prefer_owned:
|
||||
try:
|
||||
b.prefer_owned = True # type: ignore[attr-defined]
|
||||
b.prefer_owned = True
|
||||
if owned_names and not getattr(b, 'owned_card_names', None):
|
||||
b.owned_card_names = set(str(n).strip() for n in owned_names if str(n).strip()) # type: ignore[attr-defined]
|
||||
b.owned_card_names = set(str(n).strip() for n in owned_names if str(n).strip())
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
|
|
@ -1751,13 +1751,13 @@ def run_build(commander: str, tags: List[str], bracket: int, ideals: Dict[str, i
|
|||
# Thread combo preferences (if provided)
|
||||
try:
|
||||
if prefer_combos is not None:
|
||||
b.prefer_combos = bool(prefer_combos) # type: ignore[attr-defined]
|
||||
b.prefer_combos = bool(prefer_combos)
|
||||
if combo_target_count is not None:
|
||||
b.combo_target_count = int(combo_target_count) # type: ignore[attr-defined]
|
||||
b.combo_target_count = int(combo_target_count)
|
||||
if combo_balance:
|
||||
bal = str(combo_balance).strip().lower()
|
||||
if bal in ('early','late','mix'):
|
||||
b.combo_balance = bal # type: ignore[attr-defined]
|
||||
b.combo_balance = bal
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
@ -1934,7 +1934,7 @@ def run_build(commander: str, tags: List[str], bracket: int, ideals: Dict[str, i
|
|||
except Exception:
|
||||
pass
|
||||
if hasattr(b, 'export_decklist_csv'):
|
||||
csv_path = b.export_decklist_csv() # type: ignore[attr-defined]
|
||||
csv_path = b.export_decklist_csv()
|
||||
except Exception as e:
|
||||
out(f"CSV export failed: {e}")
|
||||
try:
|
||||
|
|
@ -1942,7 +1942,7 @@ def run_build(commander: str, tags: List[str], bracket: int, ideals: Dict[str, i
|
|||
# Try to mirror build_deck_full behavior by displaying the contents
|
||||
import os as _os
|
||||
base, _ext = _os.path.splitext(_os.path.basename(csv_path)) if csv_path else (f"deck_{b.timestamp}", "")
|
||||
txt_path = b.export_decklist_text(filename=base + '.txt') # type: ignore[attr-defined]
|
||||
txt_path = b.export_decklist_text(filename=base + '.txt')
|
||||
try:
|
||||
b._display_txt_contents(txt_path)
|
||||
except Exception:
|
||||
|
|
@ -1950,7 +1950,7 @@ def run_build(commander: str, tags: List[str], bracket: int, ideals: Dict[str, i
|
|||
# Compute bracket compliance and save JSON alongside exports
|
||||
try:
|
||||
if hasattr(b, 'compute_and_print_compliance'):
|
||||
rep0 = b.compute_and_print_compliance(base_stem=base) # type: ignore[attr-defined]
|
||||
rep0 = b.compute_and_print_compliance(base_stem=base)
|
||||
# Attach planning preview (no mutation) and only auto-enforce if explicitly enabled
|
||||
rep0 = _attach_enforcement_plan(b, rep0)
|
||||
try:
|
||||
|
|
@ -1959,7 +1959,7 @@ def run_build(commander: str, tags: List[str], bracket: int, ideals: Dict[str, i
|
|||
except Exception:
|
||||
_auto = False
|
||||
if _auto and isinstance(rep0, dict) and rep0.get('overall') == 'FAIL' and hasattr(b, 'enforce_and_reexport'):
|
||||
b.enforce_and_reexport(base_stem=base, mode='auto') # type: ignore[attr-defined]
|
||||
b.enforce_and_reexport(base_stem=base, mode='auto')
|
||||
except Exception:
|
||||
pass
|
||||
# Load compliance JSON for UI consumption
|
||||
|
|
@ -1981,7 +1981,7 @@ def run_build(commander: str, tags: List[str], bracket: int, ideals: Dict[str, i
|
|||
# Build structured summary for UI
|
||||
try:
|
||||
if hasattr(b, 'build_deck_summary'):
|
||||
summary = b.build_deck_summary() # type: ignore[attr-defined]
|
||||
summary = b.build_deck_summary()
|
||||
except Exception:
|
||||
summary = None
|
||||
# Write sidecar summary JSON next to CSV (if available)
|
||||
|
|
@ -1999,7 +1999,7 @@ def run_build(commander: str, tags: List[str], bracket: int, ideals: Dict[str, i
|
|||
"txt": txt_path,
|
||||
}
|
||||
try:
|
||||
commander_meta = b.get_commander_export_metadata() # type: ignore[attr-defined]
|
||||
commander_meta = b.get_commander_export_metadata()
|
||||
except Exception:
|
||||
commander_meta = {}
|
||||
names = commander_meta.get("commander_names") or []
|
||||
|
|
@ -2383,21 +2383,21 @@ def _apply_combined_commander_to_builder(builder: DeckBuilder, combined: Any) ->
|
|||
"""Attach combined commander metadata to the builder."""
|
||||
|
||||
try:
|
||||
builder.combined_commander = combined # type: ignore[attr-defined]
|
||||
builder.combined_commander = combined
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
builder.partner_mode = getattr(combined, "partner_mode", None) # type: ignore[attr-defined]
|
||||
builder.partner_mode = getattr(combined, "partner_mode", None)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
builder.secondary_commander = getattr(combined, "secondary_name", None) # type: ignore[attr-defined]
|
||||
builder.secondary_commander = getattr(combined, "secondary_name", None)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
builder.combined_color_identity = getattr(combined, "color_identity", None) # type: ignore[attr-defined]
|
||||
builder.combined_theme_tags = getattr(combined, "theme_tags", None) # type: ignore[attr-defined]
|
||||
builder.partner_warnings = getattr(combined, "warnings", None) # type: ignore[attr-defined]
|
||||
builder.combined_color_identity = getattr(combined, "color_identity", None)
|
||||
builder.combined_theme_tags = getattr(combined, "theme_tags", None)
|
||||
builder.partner_warnings = getattr(combined, "warnings", None)
|
||||
except Exception:
|
||||
pass
|
||||
commander_dict = getattr(builder, "commander_dict", None)
|
||||
|
|
@ -2583,17 +2583,17 @@ def start_build_ctx(
|
|||
# Owned-only / prefer-owned (if requested)
|
||||
try:
|
||||
if use_owned_only:
|
||||
b.use_owned_only = True # type: ignore[attr-defined]
|
||||
b.use_owned_only = True
|
||||
if owned_names:
|
||||
try:
|
||||
b.owned_card_names = set(str(n).strip() for n in owned_names if str(n).strip()) # type: ignore[attr-defined]
|
||||
b.owned_card_names = set(str(n).strip() for n in owned_names if str(n).strip())
|
||||
except Exception:
|
||||
b.owned_card_names = set() # type: ignore[attr-defined]
|
||||
b.owned_card_names = set()
|
||||
if prefer_owned:
|
||||
try:
|
||||
b.prefer_owned = True # type: ignore[attr-defined]
|
||||
b.prefer_owned = True
|
||||
if owned_names and not getattr(b, 'owned_card_names', None):
|
||||
b.owned_card_names = set(str(n).strip() for n in owned_names if str(n).strip()) # type: ignore[attr-defined]
|
||||
b.owned_card_names = set(str(n).strip() for n in owned_names if str(n).strip())
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
|
|
@ -2646,14 +2646,14 @@ def start_build_ctx(
|
|||
# Thread combo config
|
||||
try:
|
||||
if combo_target_count is not None:
|
||||
b.combo_target_count = int(combo_target_count) # type: ignore[attr-defined]
|
||||
b.combo_target_count = int(combo_target_count)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if combo_balance:
|
||||
bal = str(combo_balance).strip().lower()
|
||||
if bal in ('early','late','mix'):
|
||||
b.combo_balance = bal # type: ignore[attr-defined]
|
||||
b.combo_balance = bal
|
||||
except Exception:
|
||||
pass
|
||||
# Stages
|
||||
|
|
@ -2735,23 +2735,23 @@ def run_stage(ctx: Dict[str, Any], rerun: bool = False, show_skipped: bool = Fal
|
|||
pass
|
||||
if not ctx.get("txt_path") and hasattr(b, 'export_decklist_text'):
|
||||
try:
|
||||
ctx["csv_path"] = b.export_decklist_csv() # type: ignore[attr-defined]
|
||||
ctx["csv_path"] = b.export_decklist_csv()
|
||||
except Exception as e:
|
||||
logs.append(f"CSV export failed: {e}")
|
||||
if not ctx.get("txt_path") and hasattr(b, 'export_decklist_text'):
|
||||
try:
|
||||
import os as _os
|
||||
base, _ext = _os.path.splitext(_os.path.basename(ctx.get("csv_path") or f"deck_{b.timestamp}.csv"))
|
||||
ctx["txt_path"] = b.export_decklist_text(filename=base + '.txt') # type: ignore[attr-defined]
|
||||
ctx["txt_path"] = b.export_decklist_text(filename=base + '.txt')
|
||||
# Export the run configuration JSON for manual builds
|
||||
try:
|
||||
b.export_run_config_json(directory='config', filename=base + '.json') # type: ignore[attr-defined]
|
||||
b.export_run_config_json(directory='config', filename=base + '.json')
|
||||
except Exception:
|
||||
pass
|
||||
# Compute bracket compliance and save JSON alongside exports
|
||||
try:
|
||||
if hasattr(b, 'compute_and_print_compliance'):
|
||||
rep0 = b.compute_and_print_compliance(base_stem=base) # type: ignore[attr-defined]
|
||||
rep0 = b.compute_and_print_compliance(base_stem=base)
|
||||
rep0 = _attach_enforcement_plan(b, rep0)
|
||||
try:
|
||||
import os as __os
|
||||
|
|
@ -2759,7 +2759,7 @@ def run_stage(ctx: Dict[str, Any], rerun: bool = False, show_skipped: bool = Fal
|
|||
except Exception:
|
||||
_auto = False
|
||||
if _auto and isinstance(rep0, dict) and rep0.get('overall') == 'FAIL' and hasattr(b, 'enforce_and_reexport'):
|
||||
b.enforce_and_reexport(base_stem=base, mode='auto') # type: ignore[attr-defined]
|
||||
b.enforce_and_reexport(base_stem=base, mode='auto')
|
||||
except Exception:
|
||||
pass
|
||||
# Load compliance JSON for UI consumption
|
||||
|
|
@ -2811,7 +2811,7 @@ def run_stage(ctx: Dict[str, Any], rerun: bool = False, show_skipped: bool = Fal
|
|||
summary = None
|
||||
try:
|
||||
if hasattr(b, 'build_deck_summary'):
|
||||
summary = b.build_deck_summary() # type: ignore[attr-defined]
|
||||
summary = b.build_deck_summary()
|
||||
except Exception:
|
||||
summary = None
|
||||
# Write sidecar summary JSON next to CSV (if available)
|
||||
|
|
@ -2830,7 +2830,7 @@ def run_stage(ctx: Dict[str, Any], rerun: bool = False, show_skipped: bool = Fal
|
|||
"txt": ctx.get("txt_path"),
|
||||
}
|
||||
try:
|
||||
commander_meta = b.get_commander_export_metadata() # type: ignore[attr-defined]
|
||||
commander_meta = b.get_commander_export_metadata()
|
||||
except Exception:
|
||||
commander_meta = {}
|
||||
names = commander_meta.get("commander_names") or []
|
||||
|
|
@ -2890,12 +2890,12 @@ def run_stage(ctx: Dict[str, Any], rerun: bool = False, show_skipped: bool = Fal
|
|||
comp_now = None
|
||||
try:
|
||||
if hasattr(b, 'compute_and_print_compliance'):
|
||||
comp_now = b.compute_and_print_compliance(base_stem=None) # type: ignore[attr-defined]
|
||||
comp_now = b.compute_and_print_compliance(base_stem=None)
|
||||
except Exception:
|
||||
comp_now = None
|
||||
try:
|
||||
if comp_now:
|
||||
comp_now = _attach_enforcement_plan(b, comp_now) # type: ignore[attr-defined]
|
||||
comp_now = _attach_enforcement_plan(b, comp_now)
|
||||
except Exception:
|
||||
pass
|
||||
# If still FAIL, return the saved result without advancing or rerunning
|
||||
|
|
@ -3407,7 +3407,7 @@ def run_stage(ctx: Dict[str, Any], rerun: bool = False, show_skipped: bool = Fal
|
|||
comp = None
|
||||
try:
|
||||
if hasattr(b, 'compute_and_print_compliance'):
|
||||
comp = b.compute_and_print_compliance(base_stem=None) # type: ignore[attr-defined]
|
||||
comp = b.compute_and_print_compliance(base_stem=None)
|
||||
except Exception:
|
||||
comp = None
|
||||
try:
|
||||
|
|
@ -3508,7 +3508,7 @@ def run_stage(ctx: Dict[str, Any], rerun: bool = False, show_skipped: bool = Fal
|
|||
comp = None
|
||||
try:
|
||||
if hasattr(b, 'compute_and_print_compliance'):
|
||||
comp = b.compute_and_print_compliance(base_stem=None) # type: ignore[attr-defined]
|
||||
comp = b.compute_and_print_compliance(base_stem=None)
|
||||
except Exception:
|
||||
comp = None
|
||||
try:
|
||||
|
|
@ -3575,7 +3575,7 @@ def run_stage(ctx: Dict[str, Any], rerun: bool = False, show_skipped: bool = Fal
|
|||
comp = None
|
||||
try:
|
||||
if hasattr(b, 'compute_and_print_compliance'):
|
||||
comp = b.compute_and_print_compliance(base_stem=None) # type: ignore[attr-defined]
|
||||
comp = b.compute_and_print_compliance(base_stem=None)
|
||||
except Exception:
|
||||
comp = None
|
||||
try:
|
||||
|
|
@ -3617,23 +3617,23 @@ def run_stage(ctx: Dict[str, Any], rerun: bool = False, show_skipped: bool = Fal
|
|||
pass
|
||||
if not ctx.get("csv_path") and hasattr(b, 'export_decklist_csv'):
|
||||
try:
|
||||
ctx["csv_path"] = b.export_decklist_csv() # type: ignore[attr-defined]
|
||||
ctx["csv_path"] = b.export_decklist_csv()
|
||||
except Exception as e:
|
||||
logs.append(f"CSV export failed: {e}")
|
||||
if not ctx.get("txt_path") and hasattr(b, 'export_decklist_text'):
|
||||
try:
|
||||
import os as _os
|
||||
base, _ext = _os.path.splitext(_os.path.basename(ctx.get("csv_path") or f"deck_{b.timestamp}.csv"))
|
||||
ctx["txt_path"] = b.export_decklist_text(filename=base + '.txt') # type: ignore[attr-defined]
|
||||
ctx["txt_path"] = b.export_decklist_text(filename=base + '.txt')
|
||||
# Export the run configuration JSON for manual builds
|
||||
try:
|
||||
b.export_run_config_json(directory='config', filename=base + '.json') # type: ignore[attr-defined]
|
||||
b.export_run_config_json(directory='config', filename=base + '.json')
|
||||
except Exception:
|
||||
pass
|
||||
# Compute bracket compliance and save JSON alongside exports
|
||||
try:
|
||||
if hasattr(b, 'compute_and_print_compliance'):
|
||||
rep0 = b.compute_and_print_compliance(base_stem=base) # type: ignore[attr-defined]
|
||||
rep0 = b.compute_and_print_compliance(base_stem=base)
|
||||
rep0 = _attach_enforcement_plan(b, rep0)
|
||||
try:
|
||||
import os as __os
|
||||
|
|
@ -3641,7 +3641,7 @@ def run_stage(ctx: Dict[str, Any], rerun: bool = False, show_skipped: bool = Fal
|
|||
except Exception:
|
||||
_auto = False
|
||||
if _auto and isinstance(rep0, dict) and rep0.get('overall') == 'FAIL' and hasattr(b, 'enforce_and_reexport'):
|
||||
b.enforce_and_reexport(base_stem=base, mode='auto') # type: ignore[attr-defined]
|
||||
b.enforce_and_reexport(base_stem=base, mode='auto')
|
||||
except Exception:
|
||||
pass
|
||||
# Load compliance JSON for UI consumption
|
||||
|
|
@ -3662,7 +3662,7 @@ def run_stage(ctx: Dict[str, Any], rerun: bool = False, show_skipped: bool = Fal
|
|||
summary = None
|
||||
try:
|
||||
if hasattr(b, 'build_deck_summary'):
|
||||
summary = b.build_deck_summary() # type: ignore[attr-defined]
|
||||
summary = b.build_deck_summary()
|
||||
except Exception:
|
||||
summary = None
|
||||
# Write sidecar summary JSON next to CSV (if available)
|
||||
|
|
@ -3681,7 +3681,7 @@ def run_stage(ctx: Dict[str, Any], rerun: bool = False, show_skipped: bool = Fal
|
|||
"txt": ctx.get("txt_path"),
|
||||
}
|
||||
try:
|
||||
commander_meta = b.get_commander_export_metadata() # type: ignore[attr-defined]
|
||||
commander_meta = b.get_commander_export_metadata()
|
||||
except Exception:
|
||||
commander_meta = {}
|
||||
names = commander_meta.get("commander_names") or []
|
||||
|
|
|
|||
|
|
@ -362,7 +362,7 @@ def load_dataset(*, force: bool = False, refresh: bool = False) -> Optional[Part
|
|||
if allow_auto_refresh:
|
||||
_DATASET_REFRESH_ATTEMPTED = True
|
||||
try:
|
||||
from .orchestrator import _maybe_refresh_partner_synergy # type: ignore
|
||||
from .orchestrator import _maybe_refresh_partner_synergy
|
||||
|
||||
_maybe_refresh_partner_synergy(None, force=True)
|
||||
except Exception as refresh_exc: # pragma: no cover - best-effort
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import json
|
|||
import threading
|
||||
import math
|
||||
|
||||
from .preview_metrics import record_eviction # type: ignore
|
||||
from .preview_metrics import record_eviction
|
||||
|
||||
# Phase 2 extraction: adaptive TTL band policy moved into preview_policy
|
||||
from .preview_policy import (
|
||||
|
|
@ -30,7 +30,7 @@ from .preview_policy import (
|
|||
DEFAULT_TTL_MIN as _POLICY_TTL_MIN,
|
||||
DEFAULT_TTL_MAX as _POLICY_TTL_MAX,
|
||||
)
|
||||
from .preview_cache_backend import redis_store # type: ignore
|
||||
from .preview_cache_backend import redis_store
|
||||
|
||||
TTL_SECONDS = 600
|
||||
# Backward-compat variable names retained (tests may reference) mapping to policy constants
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import os
|
|||
import time
|
||||
|
||||
try: # lazy optional dependency
|
||||
import redis # type: ignore
|
||||
import redis
|
||||
except Exception: # pragma: no cover - absence path
|
||||
redis = None # type: ignore
|
||||
redis = None
|
||||
|
||||
_URL = os.getenv("THEME_PREVIEW_REDIS_URL")
|
||||
_DISABLED = (os.getenv("THEME_PREVIEW_REDIS_DISABLE") or "").lower() in {"1","true","yes","on"}
|
||||
|
|
@ -42,7 +42,7 @@ def _init() -> None:
|
|||
_INIT_ERR = "disabled_or_missing"
|
||||
return
|
||||
try:
|
||||
_CLIENT = redis.Redis.from_url(_URL, socket_timeout=0.25) # type: ignore
|
||||
_CLIENT = redis.Redis.from_url(_URL, socket_timeout=0.25)
|
||||
# lightweight ping (non-fatal)
|
||||
try:
|
||||
_CLIENT.ping()
|
||||
|
|
@ -86,7 +86,7 @@ def redis_get(key: Tuple[str, int, str | None, str | None, str]) -> Optional[Dic
|
|||
return None
|
||||
try:
|
||||
skey = "tpv:" + "|".join([str(part) for part in key])
|
||||
raw: bytes | None = _CLIENT.get(skey) # type: ignore
|
||||
raw: bytes | None = _CLIENT.get(skey)
|
||||
if not raw:
|
||||
return None
|
||||
obj = json.loads(raw.decode("utf-8"))
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ def sample_real_cards_for_theme(theme: str, limit: int, colors_filter: Optional[
|
|||
if allow_splash:
|
||||
off = ci - commander_colors
|
||||
if len(off) == 1:
|
||||
c["_splash_off_color"] = True # type: ignore
|
||||
c["_splash_off_color"] = True
|
||||
new_pool.append(c)
|
||||
continue
|
||||
pool = new_pool
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from .combo_utils import detect_for_summary as _detect_for_summary
|
|||
|
||||
def _owned_set_helper() -> set[str]:
|
||||
try:
|
||||
from .build_utils import owned_set as _owned_set # type: ignore
|
||||
from .build_utils import owned_set as _owned_set
|
||||
|
||||
return _owned_set()
|
||||
except Exception:
|
||||
|
|
@ -21,7 +21,7 @@ def _owned_set_helper() -> set[str]:
|
|||
|
||||
def _sanitize_tag_list(values: Iterable[Any]) -> List[str]:
|
||||
cleaned: List[str] = []
|
||||
for raw in values or []: # type: ignore[arg-type]
|
||||
for raw in values or []:
|
||||
text = str(raw or "").strip()
|
||||
if not text:
|
||||
continue
|
||||
|
|
@ -78,7 +78,7 @@ def format_theme_label(raw: Any) -> str:
|
|||
def format_theme_list(values: Iterable[Any]) -> List[str]:
|
||||
seen: set[str] = set()
|
||||
result: List[str] = []
|
||||
for raw in values or []: # type: ignore[arg-type]
|
||||
for raw in values or []:
|
||||
label = format_theme_label(raw)
|
||||
if not label:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ from pydantic import BaseModel
|
|||
# - Docker (WORKDIR /app/code): modules also available top-level.
|
||||
# - Package/zip installs (rare): may require 'code.' prefix.
|
||||
try:
|
||||
from type_definitions_theme_catalog import ThemeCatalog, ThemeEntry # type: ignore
|
||||
from type_definitions_theme_catalog import ThemeCatalog, ThemeEntry
|
||||
except ImportError: # pragma: no cover - fallback path
|
||||
try:
|
||||
from code.type_definitions_theme_catalog import ThemeCatalog, ThemeEntry # type: ignore
|
||||
from code.type_definitions_theme_catalog import ThemeCatalog, ThemeEntry
|
||||
except ImportError: # pragma: no cover - last resort (avoid beyond top-level relative import)
|
||||
raise
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ def _needs_reload() -> bool:
|
|||
if not CATALOG_JSON.exists():
|
||||
return bool(_CACHE)
|
||||
mtime = CATALOG_JSON.stat().st_mtime
|
||||
idx: SlugThemeIndex | None = _CACHE.get("index") # type: ignore
|
||||
idx: SlugThemeIndex | None = _CACHE.get("index")
|
||||
if idx is None:
|
||||
return True
|
||||
if mtime > idx.mtime:
|
||||
|
|
@ -121,7 +121,7 @@ def _needs_reload() -> bool:
|
|||
# Fast path: use os.scandir for lower overhead vs Path.glob
|
||||
newest = 0.0
|
||||
try:
|
||||
with _os.scandir(YAML_DIR) as it: # type: ignore[arg-type]
|
||||
with _os.scandir(YAML_DIR) as it:
|
||||
for entry in it:
|
||||
if entry.is_file() and entry.name.endswith('.yml'):
|
||||
try:
|
||||
|
|
@ -164,7 +164,7 @@ def _compute_etag(size: int, mtime: float, yaml_mtime: float) -> str:
|
|||
|
||||
def load_index() -> SlugThemeIndex:
|
||||
if not _needs_reload():
|
||||
return _CACHE["index"] # type: ignore
|
||||
return _CACHE["index"]
|
||||
if not CATALOG_JSON.exists():
|
||||
raise FileNotFoundError("theme_list.json missing")
|
||||
raw = json.loads(CATALOG_JSON.read_text(encoding="utf-8") or "{}")
|
||||
|
|
@ -220,7 +220,7 @@ def validate_catalog_integrity(rebuild: bool = True) -> Dict[str, Any]:
|
|||
out.update({"ok": False, "error": f"read_error:{e}"})
|
||||
return out
|
||||
# Recompute hash using same heuristic as build script
|
||||
from scripts.build_theme_catalog import load_catalog_yaml # type: ignore
|
||||
from scripts.build_theme_catalog import load_catalog_yaml
|
||||
try:
|
||||
yaml_catalog = load_catalog_yaml(verbose=False) # keyed by display_name
|
||||
except Exception:
|
||||
|
|
@ -495,7 +495,7 @@ def prewarm_common_filters(max_archetypes: int = 12) -> None:
|
|||
# Gather archetypes & buckets (limited)
|
||||
archetypes: List[str] = []
|
||||
try:
|
||||
archetypes = [a for a in {t.deck_archetype for t in idx.catalog.themes if t.deck_archetype}][:max_archetypes] # type: ignore[arg-type]
|
||||
archetypes = [a for a in {t.deck_archetype for t in idx.catalog.themes if t.deck_archetype}][:max_archetypes]
|
||||
except Exception:
|
||||
archetypes = []
|
||||
buckets = ["Very Common", "Common", "Uncommon", "Niche", "Rare"]
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import json
|
|||
try:
|
||||
import yaml # type: ignore
|
||||
except Exception: # pragma: no cover - PyYAML already in requirements; defensive
|
||||
yaml = None # type: ignore
|
||||
yaml = None
|
||||
from .preview_metrics import (
|
||||
record_build_duration,
|
||||
record_role_counts,
|
||||
|
|
@ -51,8 +51,8 @@ from .preview_cache import (
|
|||
store_cache_entry,
|
||||
evict_if_needed,
|
||||
)
|
||||
from .preview_cache_backend import redis_get # type: ignore
|
||||
from .preview_metrics import record_redis_get, record_redis_store # type: ignore
|
||||
from .preview_cache_backend import redis_get
|
||||
from .preview_metrics import record_redis_get, record_redis_store
|
||||
|
||||
# Local alias to maintain existing internal variable name usage
|
||||
_PREVIEW_CACHE = PREVIEW_CACHE
|
||||
|
|
@ -66,7 +66,7 @@ __all__ = ["get_theme_preview", "preview_metrics", "bust_preview_cache"]
|
|||
## (duplicate imports removed)
|
||||
|
||||
# Legacy constant alias retained for any external references; now a function in cache module.
|
||||
TTL_SECONDS = ttl_seconds # type: ignore
|
||||
TTL_SECONDS = ttl_seconds
|
||||
|
||||
# Per-theme error histogram (P2 observability)
|
||||
_PREVIEW_PER_THEME_ERRORS: Dict[str, int] = {}
|
||||
|
|
@ -89,7 +89,7 @@ def _load_curated_synergy_matrix() -> None:
|
|||
# Expect top-level key 'pairs' but allow raw mapping
|
||||
pairs = data.get('pairs', data)
|
||||
if isinstance(pairs, dict):
|
||||
_CURATED_SYNERGY_MATRIX = pairs # type: ignore
|
||||
_CURATED_SYNERGY_MATRIX = pairs
|
||||
else:
|
||||
_CURATED_SYNERGY_MATRIX = None
|
||||
else:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue