From 5a9b9357539dc92139b8e112b22185531deb06ae Mon Sep 17 00:00:00 2001 From: aladac Date: Mon, 18 May 2026 23:43:15 +0200 Subject: [PATCH] fix(types): clear all 10 pre-existing mypy errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- tensors/cli.py | 23 ++++++++++++++++------- tensors/fragments.py | 14 ++++++++++++-- tensors/remote.py | 3 ++- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/tensors/cli.py b/tensors/cli.py index b29c673..95906b9 100644 --- a/tensors/cli.py +++ b/tensors/cli.py @@ -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, diff --git a/tensors/fragments.py b/tensors/fragments.py index 8ad564b..fb4cfba 100644 --- a/tensors/fragments.py +++ b/tensors/fragments.py @@ -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. diff --git a/tensors/remote.py b/tensors/remote.py index 77e6824..e0a252e 100644 --- a/tensors/remote.py +++ b/tensors/remote.py @@ -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]")