mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-08-20 20:47:12 +02:00
refactor: enforce strict linting and type checking across codebase
- Expand ruff rules (B, S, SIM, RET, COM, C4, G, PT, PIE, T20, DTZ, ICN, TCH, RUF, ANN) - Switch pyright to strict mode with relaxed test environment - Replace try-except-pass with contextlib.suppress throughout - Move type-only imports into TYPE_CHECKING blocks - Replace ambiguous Unicode chars (en dash, multiplication sign, Greek alpha) with ASCII - Move color-matcher from base deps to [gpu], remove unused requests dep - Add pyright to dev deps, update dependabot to uv ecosystem - Fix hardcoded version in test_version, unused unpacked vars in tests - Update maintain.sh, CLAUDE.md, .gitignore, .claude/settings.json - Remove obsolete .agents/rules/project.md - Upgrade all dependencies (Pygments vulnerability fix) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
3298598925
commit
7eb32fedee
+11
-8
@@ -2,7 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
@@ -11,25 +14,25 @@ from PIL import Image
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def tmp_image_path(tmp_path: Path) -> Path:
|
||||
"""Create a minimal 200×200 test PNG image and return its path."""
|
||||
"""Create a minimal 200x200 test PNG image and return its path."""
|
||||
img = np.random.randint(0, 255, (200, 200, 3), dtype=np.uint8)
|
||||
path = tmp_path / "test_image.png"
|
||||
cv2.imwrite(str(path), img)
|
||||
return path
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def tmp_large_image_path(tmp_path: Path) -> Path:
|
||||
"""Create a 1200×1200 test PNG image (triggers large watermark branch)."""
|
||||
"""Create a 1200x1200 test PNG image (triggers large watermark branch)."""
|
||||
img = np.random.randint(0, 255, (1200, 1200, 3), dtype=np.uint8)
|
||||
path = tmp_path / "test_large.png"
|
||||
cv2.imwrite(str(path), img)
|
||||
return path
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def tmp_jpeg_path(tmp_path: Path) -> Path:
|
||||
"""Create a minimal JPEG test image."""
|
||||
img = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
|
||||
@@ -38,7 +41,7 @@ def tmp_jpeg_path(tmp_path: Path) -> Path:
|
||||
return path
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def tmp_png_with_ai_metadata(tmp_path: Path) -> Path:
|
||||
"""Create a PNG with AI-related metadata keys."""
|
||||
img = Image.new("RGB", (64, 64), color=(128, 128, 128))
|
||||
@@ -51,7 +54,7 @@ def tmp_png_with_ai_metadata(tmp_path: Path) -> Path:
|
||||
return path
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def tmp_clean_png(tmp_path: Path) -> Path:
|
||||
"""Create a PNG with no AI metadata."""
|
||||
img = Image.new("RGB", (64, 64), color=(200, 100, 50))
|
||||
|
||||
+12
-8
@@ -2,9 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import pytest
|
||||
@@ -15,12 +18,12 @@ from PIL.PngImagePlugin import PngInfo
|
||||
from remove_ai_watermarks.cli import main
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def runner():
|
||||
return CliRunner()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def sample_png(tmp_path: Path) -> Path:
|
||||
"""Create a sample PNG for CLI testing."""
|
||||
img = np.random.randint(0, 255, (200, 200, 3), dtype=np.uint8)
|
||||
@@ -79,7 +82,8 @@ class TestMainGroup:
|
||||
def test_version(self, runner):
|
||||
result = runner.invoke(main, ["--version"])
|
||||
assert result.exit_code == 0
|
||||
assert "0.3.1" in result.output
|
||||
assert "remove-ai-watermarks" in result.output
|
||||
assert "version" in result.output
|
||||
|
||||
def test_no_command_shows_banner(self, runner):
|
||||
result = runner.invoke(main, [])
|
||||
@@ -164,7 +168,7 @@ class TestInvisibleCommand:
|
||||
mock_engine.remove_watermark.assert_called_once()
|
||||
|
||||
def test_invisible_default_output(self, runner, sample_png):
|
||||
mock_cls, mock_engine = _mock_invisible_engine()
|
||||
mock_cls, _mock_engine = _mock_invisible_engine()
|
||||
with (
|
||||
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
|
||||
patch("remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls),
|
||||
@@ -188,7 +192,7 @@ class TestAllCommand:
|
||||
assert "visible" in result.output.lower()
|
||||
|
||||
def test_all_basic(self, runner, sample_png, tmp_path):
|
||||
mock_cls, mock_engine = _mock_invisible_engine()
|
||||
mock_cls, _mock_engine = _mock_invisible_engine()
|
||||
output = tmp_path / "clean.png"
|
||||
with (
|
||||
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
|
||||
@@ -284,7 +288,7 @@ class TestBatchCommand:
|
||||
def test_batch_invisible_mode(self, runner, tmp_path):
|
||||
input_dir = _make_batch_dir(tmp_path)
|
||||
output_dir = tmp_path / "output"
|
||||
mock_cls, mock_engine = _mock_invisible_engine()
|
||||
mock_cls, _mock_engine = _mock_invisible_engine()
|
||||
with (
|
||||
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
|
||||
patch("remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls),
|
||||
@@ -301,7 +305,7 @@ class TestBatchCommand:
|
||||
def test_batch_all_mode(self, runner, tmp_path):
|
||||
input_dir = _make_batch_dir(tmp_path)
|
||||
output_dir = tmp_path / "output"
|
||||
mock_cls, mock_engine = _mock_invisible_engine()
|
||||
mock_cls, _mock_engine = _mock_invisible_engine()
|
||||
with (
|
||||
patch("remove_ai_watermarks.cli.InvisibleEngine", mock_cls, create=True),
|
||||
patch("remove_ai_watermarks.invisible_engine.InvisibleEngine", mock_cls),
|
||||
|
||||
@@ -29,7 +29,7 @@ class TestWatermarkConfig:
|
||||
assert get_watermark_size(1920, 1080) == WatermarkSize.LARGE
|
||||
|
||||
def test_boundary_image_stays_small(self):
|
||||
"""Exactly 1024×1024 should be SMALL (rule: > 1024 for LARGE)."""
|
||||
"""Exactly 1024x1024 should be SMALL (rule: > 1024 for LARGE)."""
|
||||
assert get_watermark_size(1024, 1024) == WatermarkSize.SMALL
|
||||
|
||||
def test_one_dimension_small(self):
|
||||
|
||||
@@ -8,7 +8,7 @@ def test_humanizer_does_not_modify_original_if_disabled():
|
||||
img[50, 50] = [100, 150, 200]
|
||||
org_img = img.copy()
|
||||
|
||||
# grain=0, shift=0 means disabled essentially. But wait, apply_analog_humanizer currently applies chromatic shift even if grain=0.
|
||||
# grain=0, shift=0 means disabled — result should match original.
|
||||
result = apply_analog_humanizer(img, grain_intensity=0.0, chromatic_shift=0)
|
||||
assert np.array_equal(result, org_img)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user