fix(types): resolve pre-existing basedpyright errors in tests

Fix ~790 basedpyright errors across the test suite:
- Type stub subclasses of real production classes with super().__init__()
- Add missing generic type arguments and Dict[str, Any] annotations
- Add None guards before subscript/member access
- Adapt tests to production API changes (removed dead handlers,
  PersistentModelCache.get_default, _i18n_filter_added location)
This commit is contained in:
Will Miao
2026-08-08 20:12:59 +08:00
parent 8e724538bd
commit d2f955266d
95 changed files with 953 additions and 666 deletions

View File

@@ -76,6 +76,7 @@ class TestRewritePreviewUrl:
for url in test_cases:
result, was_rewritten = rewrite_preview_url(url, "image")
assert was_rewritten is True
assert result is not None
assert "width=450,optimized=true" in result
def test_handles_urls_with_explicit_port(self):
@@ -83,6 +84,7 @@ class TestRewritePreviewUrl:
url = "https://image.civitai.com:443/checkpoints/original=true"
result, was_rewritten = rewrite_preview_url(url, "image")
assert was_rewritten is True
assert result is not None
assert "width=450,optimized=true" in result
# Port is preserved in the URL (this is acceptable behavior)
assert ":443" in result
@@ -104,6 +106,8 @@ class TestRewritePreviewUrl:
result2, was2 = rewrite_preview_url(url, "Video")
assert was1 is True
assert was2 is True
assert result1 is not None
assert result2 is not None
assert "transcode=true" in result1
assert "transcode=true" in result2
@@ -119,6 +123,7 @@ class TestRewritePreviewUrl:
url = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/abc123/original=true/12345.png"
result, was_rewritten = rewrite_preview_url(url, "image")
assert was_rewritten is True
assert result is not None
assert result.startswith(
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/abc123/"
)
@@ -129,6 +134,7 @@ class TestRewritePreviewUrl:
url = "https://image.civitai.com/original=true/test.png"
result, was_rewritten = rewrite_preview_url(url, None)
assert was_rewritten is True
assert result is not None
assert "transcode=true" not in result
assert "width=450,optimized=true" in result

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
import asyncio
import time
from typing import Any, Dict
from typing import Any, Dict, Generator
import pytest
@@ -19,7 +19,7 @@ class RecordingWebSocketManager:
@pytest.fixture(autouse=True)
def restore_settings() -> None:
def restore_settings() -> Generator[None, None, None]:
manager = get_settings_manager()
original = manager.settings.copy()
try:
@@ -45,7 +45,9 @@ async def test_start_download_requires_configured_path(
result = await manager.start_download({"auto_mode": True})
assert result["success"] is True
assert "skipping auto download" in result["message"]
message = result["message"]
assert isinstance(message, str)
assert "skipping auto download" in message
async def test_start_download_bootstraps_progress_and_task(

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import json
import os
import subprocess
from typing import Any, Dict
from typing import Any, Dict, Generator
import pytest
@@ -20,8 +20,13 @@ class JsonRequest:
return self._payload
def _parse_json(response) -> Dict[str, Any]:
assert response.text is not None
return json.loads(response.text)
@pytest.fixture(autouse=True)
def restore_settings() -> None:
def restore_settings() -> Generator[None, None, None]:
manager = get_settings_manager()
original = manager.settings.copy()
try:
@@ -54,7 +59,7 @@ async def test_open_folder_requires_existing_model_directory(monkeypatch: pytest
request = JsonRequest({"model_hash": model_hash})
response = await ExampleImagesFileManager.open_folder(request)
body = json.loads(response.text)
body = _parse_json(response)
assert body["success"] is True
# On Windows, os.startfile is used; on other platforms, subprocess.Popen
@@ -89,7 +94,7 @@ async def test_open_folder_returns_clipboard_mode_with_mapped_local_path(
request = JsonRequest({"model_hash": model_hash})
response = await ExampleImagesFileManager.open_folder(request)
body = json.loads(response.text)
body = _parse_json(response)
assert response.status == 200
assert body == {
@@ -126,7 +131,7 @@ async def test_open_folder_returns_uri_mode_with_rendered_template(
request = JsonRequest({"model_hash": model_hash})
response = await ExampleImagesFileManager.open_folder(request)
body = json.loads(response.text)
body = _parse_json(response)
assert response.status == 200
assert body["success"] is True
@@ -150,7 +155,7 @@ async def test_open_folder_rejects_missing_uri_template(monkeypatch: pytest.Monk
(model_folder / "image.png").write_text("data", encoding="utf-8")
response = await ExampleImagesFileManager.open_folder(JsonRequest({"model_hash": model_hash}))
body = json.loads(response.text)
body = _parse_json(response)
assert response.status == 400
assert body["success"] is False
@@ -168,7 +173,7 @@ async def test_open_folder_rejects_invalid_paths(monkeypatch: pytest.MonkeyPatch
request = JsonRequest({"model_hash": "a" * 64})
response = await ExampleImagesFileManager.open_folder(request)
body = json.loads(response.text)
body = _parse_json(response)
assert response.status == 400
assert body["success"] is False
@@ -186,7 +191,7 @@ async def test_get_files_lists_supported_media(tmp_path) -> None:
request = JsonRequest({}, {"model_hash": model_hash})
response = await ExampleImagesFileManager.get_files(request)
body = json.loads(response.text)
body = _parse_json(response)
assert response.status == 200
names = {entry["name"] for entry in body["files"]}
@@ -203,18 +208,18 @@ async def test_has_images_reports_presence(tmp_path) -> None:
request = JsonRequest({}, {"model_hash": model_hash})
response = await ExampleImagesFileManager.has_images(request)
body = json.loads(response.text)
body = _parse_json(response)
assert body["has_images"] is True
empty_request = JsonRequest({}, {"model_hash": "missing"})
empty_response = await ExampleImagesFileManager.has_images(empty_request)
empty_body = json.loads(empty_response.text)
empty_body = _parse_json(empty_response)
assert empty_body["has_images"] is False
async def test_has_images_requires_model_hash() -> None:
response = await ExampleImagesFileManager.has_images(JsonRequest({}, {}))
body = json.loads(response.text)
body = _parse_json(response)
assert response.status == 400
assert body["success"] is False

View File

@@ -102,7 +102,7 @@ async def test_update_metadata_after_import_preserves_existing_metadata(
model_file.write_text("content", encoding="utf-8")
metadata_path = tmp_path / "preserve.metadata.json"
existing_payload = {
existing_payload: Dict[str, Any] = {
"model_name": "Example",
"file_path": str(model_file),
"civitai": {
@@ -200,7 +200,7 @@ async def test_update_metadata_from_local_examples_generates_entries(monkeypatch
model_dir = tmp_path / model_hash
model_dir.mkdir()
(model_dir / "image.png").write_text("data", encoding="utf-8")
model_data = {"model_name": "Local", "civitai": {}, "file_path": str(tmp_path / "model.safetensors")}
model_data: Dict[str, Any] = {"model_name": "Local", "civitai": {}, "file_path": str(tmp_path / "model.safetensors")}
async def fake_save(path, metadata):
return True

View File

@@ -4,7 +4,7 @@ import json
import os
from pathlib import Path
from types import SimpleNamespace
from typing import Any, Dict, Tuple
from typing import Any, Dict, Generator, Tuple
import pytest
@@ -15,7 +15,7 @@ from py.utils.example_images_paths import get_model_folder
@pytest.fixture(autouse=True)
def restore_settings() -> None:
def restore_settings() -> Generator[None, None, None]:
manager = get_settings_manager()
original = manager.settings.copy()
try:
@@ -206,7 +206,7 @@ async def test_import_images_creates_hash_directory(monkeypatch: pytest.MonkeyPa
monkeypatch.setattr(processor_module.MetadataUpdater, "update_metadata_after_import", staticmethod(fake_update_metadata))
result = await processor_module.ExampleImagesProcessor.import_images("a" * 64, [str(source_file)])
result: Dict[str, Any] = await processor_module.ExampleImagesProcessor.import_images("a" * 64, [str(source_file)])
assert result["success"] is True
assert result["files"][0]["name"].startswith("custom_short")
@@ -255,7 +255,7 @@ async def test_delete_custom_image_preserves_existing_metadata(monkeypatch: pyte
model_file.write_text("content", encoding="utf-8")
metadata_path = tmp_path / "keep.metadata.json"
existing_metadata = {
existing_metadata: Dict[str, Any] = {
"model_name": "Keep",
"file_path": str(model_file),
"civitai": {
@@ -313,6 +313,7 @@ async def test_delete_custom_image_preserves_existing_metadata(monkeypatch: pyte
)
assert response.status == 200
assert response.text is not None
body = json.loads(response.text)
assert body["success"] is True
assert body["custom_images"] == []

View File

@@ -1,6 +1,7 @@
import json
from typing import Any, Dict
import piexif
import piexif # pyright: ignore[reportMissingTypeStubs]
from PIL import Image, PngImagePlugin
from py.utils.exif_utils import ExifUtils
@@ -84,10 +85,12 @@ def test_optimize_image_preserves_workflow_when_converting_png_to_webp(tmp_path)
optimized_path.write_bytes(optimized_data)
exif_dict = piexif.load(str(optimized_path))
assert exif_dict["0th"] is not None
assert (
exif_dict["0th"][piexif.ImageIFD.ImageDescription].decode("utf-8")
== 'Workflow:{"nodes": [{"id": 1}]}'
)
assert exif_dict["Exif"] is not None
user_comment = exif_dict["Exif"][piexif.ExifIFD.UserComment]
assert user_comment.startswith(b"UNICODE\0")
assert user_comment[8:].decode("utf-16be") == "prompt text\nSteps: 20"
@@ -113,10 +116,12 @@ def test_update_image_metadata_preserves_webp_workflow(tmp_path):
)
updated_exif = piexif.load(str(image_path))
assert updated_exif["0th"] is not None
assert (
updated_exif["0th"][piexif.ImageIFD.ImageDescription].decode("utf-8")
== 'Workflow:{"nodes":[{"id":1}]}'
)
assert updated_exif["Exif"] is not None
updated_comment = updated_exif["Exif"][piexif.ExifIFD.UserComment]
assert (
updated_comment[8:].decode("utf-16be")
@@ -147,10 +152,10 @@ def test_update_image_metadata_preserves_png_workflow(tmp_path):
import struct
import brotli
import brotli # pyright: ignore[reportMissingTypeStubs]
def _build_jxl_with_brob(payload_json: dict) -> bytes:
def _build_jxl_with_brob(payload_json: Dict[str, Any]) -> bytes:
"""Build a minimal JXL container with a brob box containing brotli-compressed JSON."""
# ISOBMFF box 1: JXL signature box (size=12, type='JXL ', signature)
box1 = struct.pack(">I", 12) + b"JXL " + bytes([0x0d, 0x0a, 0x87, 0x0a])
@@ -163,7 +168,7 @@ def _build_jxl_with_brob(payload_json: dict) -> bytes:
return box1 + box2 + box3
def _build_avif_with_brob(payload_json: dict) -> bytes:
def _build_avif_with_brob(payload_json: Dict[str, Any]) -> bytes:
"""Build a minimal AVIF container with a brob box containing brotli-compressed JSON."""
compressed = brotli.compress(json.dumps(payload_json).encode("utf-8"))
brob_payload = b"comf" + compressed
@@ -262,6 +267,7 @@ class TestIsobmffBrotliExtraction:
path.write_bytes(data)
result = ExifUtils._load_structured_metadata(str(path))
assert result["prompt"] is not None
assert json.loads(result["prompt"]) == {"text": "hello", "negative": "bad"}
def test_extract_workflow_as_list(self, tmp_path):
@@ -272,6 +278,7 @@ class TestIsobmffBrotliExtraction:
path.write_bytes(data)
result = ExifUtils._load_structured_metadata(str(path))
assert result["workflow"] is not None
assert json.loads(result["workflow"]) == [{"id": 1}, {"id": 2}]
def test_over_decompressed_size_limit(self, tmp_path, monkeypatch):

View File

@@ -1,5 +1,7 @@
"""Tests for model sub_type field refactoring."""
from typing import Any, Dict
import pytest
from py.utils.models import (
BaseModelMetadata,
@@ -44,7 +46,7 @@ class TestCheckpointMetadataSubType:
def test_checkpoint_from_civitai_info_uses_sub_type(self):
"""from_civitai_info should use sub_type from version_info."""
version_info = {
version_info: Dict[str, Any] = {
"baseModel": "SDXL",
"model": {"name": "Test", "description": "", "tags": []},
"files": [{"name": "model.safetensors", "sizeKB": 1000, "hashes": {"SHA256": "abc123"}, "primary": True}],
@@ -79,7 +81,7 @@ class TestEmbeddingMetadataSubType:
def test_embedding_from_civitai_info_uses_sub_type(self):
"""from_civitai_info should use sub_type from version_info."""
version_info = {
version_info: Dict[str, Any] = {
"baseModel": "SD1.5",
"model": {"name": "Test", "description": "", "tags": []},
"files": [{"name": "model.pt", "sizeKB": 1000, "hashes": {"SHA256": "abc123"}, "primary": True}],
@@ -113,7 +115,7 @@ class TestLoraMetadataConsistency:
def test_lora_from_civitai_info_extracts_type(self):
"""from_civitai_info should extract type from civitai data."""
version_info = {
version_info: Dict[str, Any] = {
"baseModel": "SDXL",
"model": {"name": "Test", "description": "", "tags": [], "type": "Lora"},
"files": [{"name": "model.safetensors", "sizeKB": 1000, "hashes": {"SHA256": "abc123"}, "primary": True}],

View File

@@ -12,6 +12,7 @@ def test_select_preview_returns_first_when_blur_disabled():
selected, level = select_preview_media(images, blur_mature_content=False)
assert selected is not None
assert selected["url"] == "nsfw"
assert level == 32
@@ -40,6 +41,7 @@ def test_select_preview_respects_configurable_threshold(threshold_name, expected
mature_threshold=NSFW_LEVELS[threshold_name],
)
assert selected is not None
assert selected["url"] == expected_url
assert level == next(item["nsfwLevel"] for item in images if item["url"] == expected_url)

View File

@@ -6,6 +6,8 @@ property-based testing to catch edge cases and ensure correctness.
from __future__ import annotations
from typing import Any, Dict
import pytest
from hypothesis import given, settings, strategies as st
@@ -80,6 +82,8 @@ class TestNormalizePath:
@given(st.text(alphabet='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-/\\') | st.none())
def test_normalize_path_is_idempotent_for_ascii(self, path: str | None):
"""Normalizing an already normalized ASCII path should not change it."""
if path is None:
return
normalized = normalize_path(path)
renormalized = normalize_path(normalized)
assert normalized == renormalized
@@ -160,14 +164,14 @@ class TestCalculateRecipeFingerprint:
"""Property-based tests for calculate_recipe_fingerprint function."""
@given(st.lists(st.dictionaries(st.text(), st.text() | st.integers() | st.floats(), min_size=1), min_size=0, max_size=50))
def test_fingerprint_is_deterministic(self, loras: list):
def test_fingerprint_is_deterministic(self, loras: list[Dict[str, Any]]):
"""Same input should always produce same fingerprint."""
fp1 = calculate_recipe_fingerprint(loras)
fp2 = calculate_recipe_fingerprint(loras)
assert fp1 == fp2
@given(st.lists(st.dictionaries(st.text(), st.text() | st.integers() | st.floats(), min_size=1), min_size=0, max_size=50))
def test_fingerprint_returns_string(self, loras: list):
def test_fingerprint_returns_string(self, loras: list[Dict[str, Any]]):
"""Function should always return a string."""
result = calculate_recipe_fingerprint(loras)
assert isinstance(result, str)
@@ -178,7 +182,7 @@ class TestCalculateRecipeFingerprint:
assert result == ""
@given(st.lists(st.dictionaries(st.text(), st.text() | st.integers() | st.floats(), min_size=1), min_size=1, max_size=10))
def test_fingerprint_different_inputs_produce_different_results(self, loras1: list):
def test_fingerprint_different_inputs_produce_different_results(self, loras1: list[Dict[str, Any]]):
"""Different inputs should generally produce different fingerprints."""
# Create a different input by modifying the first LoRA
loras2 = loras1.copy()