fix(lint): improved type checking and code quality (77% error reduction)

This commit is contained in:
matt 2025-10-31 08:18:09 -07:00
parent 3c45a31aa3
commit 83fe527979
37 changed files with 423 additions and 303 deletions

View file

@ -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

View file

@ -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 []

View file

@ -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

View file

@ -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

View file

@ -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"))

View file

@ -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

View file

@ -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

View file

@ -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"]

View file

@ -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: