fix(types): clear all 10 pre-existing mypy errors

Master CI lint job runs both ruff and mypy. Previous commit cleaned ruff;
this knocks out the mypy backlog too so the parallel-queue PR can ship
fully green.

- fragments.py: FragmentLibrary defines a `list()` method, which shadows
  the builtin in class-scope name resolution and broke `list[str]`
  annotations in `resolve()`. Qualify with `builtins.list` (imported
  under TYPE_CHECKING since it's static-only).
- remote.py: `result.get("civitai", result)` returns Any to mypy because
  dict.get widens. Capture into a typed local first.
- cli.py:
  - Drop redundant type re-annotation on `civitai_results` in the
    non-remote branch (same name was annotated in the early-return
    remote branch above; mypy treats class/module-scope re-annotation
    as no-redef even when control flow rules out overlap).
  - Guard `p.name is not None` before passing to `get_parameter_source`
    (click stubs type Parameter.name as `str | None`).
  - Parameterize bare `list | dict` in `_load_json_file_or_inline`.
  - Widen `_write_sweep_manifest`'s template_path arg to `Path | None`
    (callers already pass None when --list is used without --template);
    serialize as empty string in manifest to keep schema stable.
  - `# type: ignore[arg-type]` on the deprecated `tsr comfy generate`
    delegator that passes a typer function where click.Command is
    expected — duck-typed at runtime, only matters for the deprecation
    shim.

Tests: 374 still passing. Ruff: clean. Mypy: clean.
This commit is contained in:
2026-05-18 23:43:15 +02:00
parent b0b5bca5f8
commit 5a9b935753
3 changed files with 30 additions and 10 deletions
+16 -7
View File
@@ -346,7 +346,10 @@ def search(
return
key = api_key or load_api_key()
civitai_results: dict[str, Any] | None = None
# Reuse the name from the remote-mode branch above (which already returned)
# without redeclaring its type — mypy treats class-scope re-annotation as
# a no-redef even when control flow guarantees the branches don't overlap.
civitai_results = None
hf_results: list[dict[str, Any]] | None = None
# Search CivitAI
@@ -946,7 +949,9 @@ def generate( # noqa: PLR0915
{
p.name
for p in click_ctx.command.params
if click_ctx.get_parameter_source(p.name) == click.core.ParameterSource.COMMANDLINE
# click's Parameter.name is typed `str | None` in stubs but is always
# a real string at runtime for any param that's been registered.
if p.name is not None and click_ctx.get_parameter_source(p.name) == click.core.ParameterSource.COMMANDLINE
}
if hasattr(click_ctx, "get_parameter_source")
else set()
@@ -1709,7 +1714,7 @@ _STYLE_SWEEP_TEMPLATE_KEYS = {
}
def _load_json_file_or_inline(value: str | list | dict, *, what: str) -> Any:
def _load_json_file_or_inline(value: str | list[Any] | dict[str, Any], *, what: str) -> Any:
"""Load JSON from a file path or accept already-parsed inline data.
`value` may be a path string, a JSON string, or an already-parsed list/dict
@@ -2188,14 +2193,16 @@ def style_sweep( # noqa: PLR0915
def _write_sweep_manifest(
out_dir: Path,
template_path: Path,
template_path: Path | None,
styles_origin: str,
results: list[dict[str, Any]],
) -> Path:
"""Write the per-sweep manifest JSON. Returns the path."""
manifest_path = out_dir / "_sweep.json"
manifest: dict[str, Any] = {
"template": str(template_path),
# template_path is None when --list is used with only --styles (no template
# required). Serialize as empty string to keep manifest schema stable.
"template": str(template_path) if template_path is not None else "",
"styles_source": styles_origin,
"results": results,
}
@@ -3628,8 +3635,10 @@ def comfy_generate(
) -> None:
"""[Deprecated] Use 'tsr generate' instead. All features have been merged into the top-level command."""
console.print("[yellow]Warning: 'tsr comfy generate' is deprecated. Use 'tsr generate' instead.[/yellow]")
# Delegate to the unified generate command via context invocation
ctx = typer.Context(generate)
# Delegate to the unified generate command via context invocation.
# typer.Context expects a click.Command, but passing the typer function directly
# works at runtime via duck-typing — keeping it for back-compat with deprecated alias.
ctx = typer.Context(generate) # type: ignore[arg-type]
generate(
ctx=ctx,
prompt=prompt,
+12 -2
View File
@@ -16,9 +16,16 @@ from __future__ import annotations
import json
import re
from pathlib import Path # noqa: TC003 # used in runtime return annotations exposed to typer
from typing import TYPE_CHECKING
from tensors.config import DATA_DIR
if TYPE_CHECKING:
# Qualified `builtins.list` is referenced in annotations inside FragmentLibrary
# because the class defines a method named `list` that shadows the builtin
# at class-scope name resolution. Static-only — not needed at runtime.
import builtins
# Restrict fragment names to a safe subset so they can't escape the storage dir
# via path traversal and so file listings stay tidy.
_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]+$")
@@ -132,8 +139,11 @@ class FragmentLibrary:
*,
name: str | None = None,
inline: str | None = None,
extra: list[str] | None = None,
) -> list[str]:
# NOTE: `builtins.list` qualifier needed because this class defines a
# `list()` method below, which shadows the builtin in class-scope name
# resolution. Affects mypy/pyright even with `from __future__ import annotations`.
extra: builtins.list[str] | None = None,
) -> builtins.list[str]:
"""Merge a named fragment with an inline CSV string and optional extras.
Resolution order (first match wins per duplicate): named → inline → extra.
+2 -1
View File
@@ -209,7 +209,8 @@ def remote_search(
response.raise_for_status()
result: dict[str, Any] = response.json()
# The remote API wraps CivitAI results under "civitai" key
return result.get("civitai", result)
civitai_section: dict[str, Any] = result.get("civitai", result)
return civitai_section
except httpx.HTTPStatusError as e:
if console:
console.print(f"[red]Remote API error: {e.response.status_code}[/red]")