Reduce per-file-ignores by fixing lint violations across codebase

Fix ruff violations in ~15 source files and ~60+ test files to minimize
per-file-ignores in pyproject.toml. Remaining ignores are justified with
comments explaining why each suppression is necessary.

Source fixes: FBT003 (keyword args), S310 (URL validation), SLF001
(private access), T201 (print→logging), C901 (complexity), E501 (line
length), E402 (import order).

Test fixes: SIM117 (combined with), FBT (boolean args), PERF203 (try in
loop), S310/S607 (URLs/executables), E402/E501 (imports/lines), S108
(tmp paths), PLR0913 (too many args), ARG (unused args), ANN (type
annotations), RUF059 (unused unpacked vars), PT019 (fixture naming).

Remaining per-file-ignores (with justifications):
- Tests: ARG, D, PLC0415, PLR2004, S101, SLF001
- music_gen sources: PLC0415 (heavy ML lazy imports)
- moviepy_showcase: PLC0415 (circular dependency)
- generate_images: PLR0913 (matplotlib helpers need many params)
- praca_magisterska_video: E501, E402 (long paths, mpl.use)
This commit is contained in:
Krzysztof kuhy Rudnicki 2026-03-25 18:58:05 +01:00
parent 996617d4a0
commit e5fd82c822
86 changed files with 1168 additions and 1241 deletions

View File

@ -9,12 +9,14 @@ import geopandas as gpd
import matplotlib.pyplot as plt
import pytest
from shapely.geometry import LineString, Point, Polygon
from typing_extensions import Self
from python_pkg.anki_decks.polish_coastal_features import (
polish_coastal_features_anki as _mod,
)
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
from pathlib import Path
_init_worker = _mod._init_worker
@ -62,17 +64,26 @@ def _line_feature() -> gpd.GeoDataFrame:
class _FakePool:
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
def __init__(
self,
_processes: int | None = None,
initializer: Callable[..., object] | None = None,
initargs: tuple[object, ...] = (),
) -> None:
if initializer:
initializer(*initargs)
def imap_unordered(self, func, items):
def imap_unordered(
self,
func: Callable[[object], object],
items: Iterable[object],
) -> list[object]:
return [func(item) for item in items]
def __enter__(self):
def __enter__(self) -> Self:
return self
def __exit__(self, *a):
def __exit__(self, *_args: object) -> None:
pass

View File

@ -3,12 +3,17 @@
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from unittest.mock import patch
import geopandas as gpd
import matplotlib.pyplot as plt
import pytest
from shapely.geometry import Polygon
from typing_extensions import Self
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
try:
from python_pkg.anki_decks.polish_forests.polish_forests_anki import (
@ -58,17 +63,26 @@ def _forests() -> gpd.GeoDataFrame:
class _FakePool:
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
def __init__(
self,
_processes: int | None = None,
initializer: Callable[..., object] | None = None,
initargs: tuple[object, ...] = (),
) -> None:
if initializer:
initializer(*initargs)
def imap_unordered(self, func, items):
def imap_unordered(
self,
func: Callable[[object], object],
items: Iterable[object],
) -> list[object]:
return [func(item) for item in items]
def __enter__(self):
def __enter__(self) -> Self:
return self
def __exit__(self, *a):
def __exit__(self, *_args: object) -> None:
pass

View File

@ -3,12 +3,17 @@
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from unittest.mock import patch
import geopandas as gpd
import matplotlib.pyplot as plt
import pytest
from shapely.geometry import Polygon
from typing_extensions import Self
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
try:
from python_pkg.anki_decks.polish_gminy.polish_gminy_anki import (
@ -59,17 +64,26 @@ def _gminy() -> gpd.GeoDataFrame:
class _FakePool:
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
def __init__(
self,
_processes: int | None = None,
initializer: Callable[..., object] | None = None,
initargs: tuple[object, ...] = (),
) -> None:
if initializer:
initializer(*initargs)
def imap_unordered(self, func, items):
def imap_unordered(
self,
func: Callable[[object], object],
items: Iterable[object],
) -> list[object]:
return [func(item) for item in items]
def __enter__(self):
def __enter__(self) -> Self:
return self
def __exit__(self, *a):
def __exit__(self, *_args: object) -> None:
pass

View File

@ -3,12 +3,17 @@
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from unittest.mock import patch
import geopandas as gpd
import matplotlib.pyplot as plt
import pytest
from shapely.geometry import Polygon
from typing_extensions import Self
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
try:
from python_pkg.anki_decks.polish_islands.polish_islands_anki import (
@ -73,17 +78,26 @@ def _island_outside() -> gpd.GeoDataFrame:
class _FakePool:
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
def __init__(
self,
_processes: int | None = None,
initializer: Callable[..., object] | None = None,
initargs: tuple[object, ...] = (),
) -> None:
if initializer:
initializer(*initargs)
def imap_unordered(self, func, items):
def imap_unordered(
self,
func: Callable[[object], object],
items: Iterable[object],
) -> list[object]:
return [func(item) for item in items]
def __enter__(self):
def __enter__(self) -> Self:
return self
def __exit__(self, *a):
def __exit__(self, *_args: object) -> None:
pass

View File

@ -3,12 +3,17 @@
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from unittest.mock import patch
import geopandas as gpd
import matplotlib.pyplot as plt
import pytest
from shapely.geometry import Polygon
from typing_extensions import Self
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
try:
from python_pkg.anki_decks.polish_lakes.polish_lakes_anki import (
@ -58,17 +63,26 @@ def _lakes() -> gpd.GeoDataFrame:
class _FakePool:
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
def __init__(
self,
_processes: int | None = None,
initializer: Callable[..., object] | None = None,
initargs: tuple[object, ...] = (),
) -> None:
if initializer:
initializer(*initargs)
def imap_unordered(self, func, items):
def imap_unordered(
self,
func: Callable[[object], object],
items: Iterable[object],
) -> list[object]:
return [func(item) for item in items]
def __enter__(self):
def __enter__(self) -> Self:
return self
def __exit__(self, *a):
def __exit__(self, *_args: object) -> None:
pass

View File

@ -9,10 +9,12 @@ import geopandas as gpd
import matplotlib.pyplot as plt
import pytest
from shapely.geometry import Polygon
from typing_extensions import Self
import python_pkg.anki_decks.polish_landscape_parks.polish_landscape_parks_anki as _mod
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
from pathlib import Path
_init_worker = _mod._init_worker
@ -47,17 +49,26 @@ def _parks() -> gpd.GeoDataFrame:
class _FakePool:
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
def __init__(
self,
_processes: int | None = None,
initializer: Callable[..., object] | None = None,
initargs: tuple[object, ...] = (),
) -> None:
if initializer:
initializer(*initargs)
def imap_unordered(self, func, items):
def imap_unordered(
self,
func: Callable[[object], object],
items: Iterable[object],
) -> list[object]:
return [func(item) for item in items]
def __enter__(self):
def __enter__(self) -> Self:
return self
def __exit__(self, *a):
def __exit__(self, *_args: object) -> None:
pass

View File

@ -5,7 +5,6 @@ from __future__ import annotations
import importlib
from pathlib import Path
import sys
from typing import Any
from unittest.mock import MagicMock, patch
import pytest
@ -34,7 +33,7 @@ class TestImportError:
original_import = builtins.__import__
def mock_import(name: str, *args: Any, **kwargs: Any) -> Any:
def mock_import(name: str, *args: object, **kwargs: object) -> object:
if name in ("bs4", "requests"):
msg = f"No module named '{name}'"
raise ImportError(msg)
@ -119,7 +118,7 @@ class TestFetchWikipediaHtml:
)
def test_returns_cached_data_when_valid(
self,
_mock_valid: MagicMock,
mock_valid: MagicMock,
mock_cache_path: MagicMock,
tmp_path: Path,
) -> None:
@ -144,7 +143,7 @@ class TestFetchWikipediaHtml:
def test_fetches_fresh_when_cache_read_fails(
self,
mock_get: MagicMock,
_mock_valid: MagicMock,
mock_valid: MagicMock,
mock_cache_path: MagicMock,
tmp_path: Path,
) -> None:
@ -181,7 +180,7 @@ class TestFetchWikipediaHtml:
def test_fetches_from_wikipedia_when_cache_invalid(
self,
mock_get: MagicMock,
_mock_valid: MagicMock,
mock_valid: MagicMock,
mock_cache_path: MagicMock,
tmp_path: Path,
) -> None:
@ -211,7 +210,7 @@ class TestFetchWikipediaHtml:
def test_force_refresh_ignores_cache(
self,
mock_get: MagicMock,
_mock_valid: MagicMock,
mock_valid: MagicMock,
mock_cache_path: MagicMock,
tmp_path: Path,
) -> None:
@ -239,7 +238,7 @@ class TestFetchWikipediaHtml:
def test_force_refresh_skips_valid_cache(
self,
mock_get: MagicMock,
_mock_valid: MagicMock,
mock_valid: MagicMock,
mock_cache_path: MagicMock,
tmp_path: Path,
) -> None:
@ -268,7 +267,7 @@ class TestFetchWikipediaHtml:
def test_raises_runtime_error_on_request_exception(
self,
mock_get: MagicMock,
_mock_valid: MagicMock,
mock_valid: MagicMock,
mock_cache_path: MagicMock,
tmp_path: Path,
) -> None:
@ -295,7 +294,7 @@ class TestFetchWikipediaHtml:
def test_continues_when_cache_write_fails(
self,
mock_get: MagicMock,
_mock_valid: MagicMock,
mock_valid: MagicMock,
mock_cache_path: MagicMock,
) -> None:
"""Should return data even when cache write fails."""

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from io import StringIO
from pathlib import Path
import tempfile
from unittest.mock import MagicMock, patch
from python_pkg.anki_decks.polish_license_plates.fetch_license_plates import (
@ -34,7 +35,7 @@ class TestFetchWikipediaLicensePlates:
@patch(f"{MOD}.parse_license_plates_from_html", return_value={"KR": "Kraków"})
@patch(f"{MOD}.fetch_wikipedia_html", return_value="<html></html>")
def test_force_refresh_passed(
self, mock_fetch: MagicMock, _mock_parse: MagicMock
self, mock_fetch: MagicMock, mock_parse: MagicMock
) -> None:
fetch_wikipedia_license_plates(force_refresh=True)
mock_fetch.assert_called_once_with(force_refresh=True)
@ -99,7 +100,7 @@ class TestGenerateLicensePlateDataFile:
class TestMain:
"""Tests for main entry point."""
@patch(f"{MOD}.get_cache_path", return_value=Path("/tmp/cache"))
@patch(f"{MOD}.get_cache_path", return_value=Path(tempfile.gettempdir(), "cache"))
@patch(f"{MOD}.generate_license_plate_data_file")
@patch(
f"{MOD}.fetch_wikipedia_license_plates",
@ -109,9 +110,9 @@ class TestMain:
def test_success(
self,
mock_args: MagicMock,
_mock_fetch: MagicMock,
mock_fetch: MagicMock,
mock_gen: MagicMock,
_mock_cache: MagicMock,
mock_cache: MagicMock,
) -> None:
mock_args.return_value = MagicMock(force=False)
with patch("sys.stdout", new_callable=StringIO):
@ -127,14 +128,14 @@ class TestMain:
def test_runtime_error(
self,
mock_args: MagicMock,
_mock_fetch: MagicMock,
mock_fetch: MagicMock,
) -> None:
mock_args.return_value = MagicMock(force=False)
with patch("sys.stderr", new_callable=StringIO):
result = main()
assert result == 1
@patch(f"{MOD}.get_cache_path", return_value=Path("/tmp/cache"))
@patch(f"{MOD}.get_cache_path", return_value=Path(tempfile.gettempdir(), "cache"))
@patch(f"{MOD}.generate_license_plate_data_file")
@patch(
f"{MOD}.fetch_wikipedia_license_plates",
@ -145,8 +146,8 @@ class TestMain:
self,
mock_args: MagicMock,
mock_fetch: MagicMock,
_mock_gen: MagicMock,
_mock_cache: MagicMock,
mock_gen: MagicMock,
mock_cache: MagicMock,
) -> None:
mock_args.return_value = MagicMock(force=True)
with patch("sys.stdout", new_callable=StringIO):
@ -154,7 +155,7 @@ class TestMain:
assert result == 0
mock_fetch.assert_called_once_with(force_refresh=True)
@patch(f"{MOD}.get_cache_path", return_value=Path("/tmp/cache"))
@patch(f"{MOD}.get_cache_path", return_value=Path(tempfile.gettempdir(), "cache"))
@patch(f"{MOD}.generate_license_plate_data_file")
@patch(
f"{MOD}.fetch_wikipedia_license_plates",
@ -164,9 +165,9 @@ class TestMain:
def test_prints_summary(
self,
mock_args: MagicMock,
_mock_fetch: MagicMock,
_mock_gen: MagicMock,
_mock_cache: MagicMock,
mock_fetch: MagicMock,
mock_gen: MagicMock,
mock_cache: MagicMock,
) -> None:
mock_args.return_value = MagicMock(force=False)
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:

View File

@ -3,12 +3,17 @@
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from unittest.mock import patch
import geopandas as gpd
import matplotlib.pyplot as plt
import pytest
from shapely.geometry import Point, Polygon
from typing_extensions import Self
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
try:
from python_pkg.anki_decks.polish_mountain_peaks.polish_mountain_peaks_anki import (
@ -58,17 +63,26 @@ def _peaks() -> gpd.GeoDataFrame:
class _FakePool:
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
def __init__(
self,
_processes: int | None = None,
initializer: Callable[..., object] | None = None,
initargs: tuple[object, ...] = (),
) -> None:
if initializer:
initializer(*initargs)
def imap_unordered(self, func, items):
def imap_unordered(
self,
func: Callable[[object], object],
items: Iterable[object],
) -> list[object]:
return [func(item) for item in items]
def __enter__(self):
def __enter__(self) -> Self:
return self
def __exit__(self, *a):
def __exit__(self, *_args: object) -> None:
pass

View File

@ -9,10 +9,12 @@ import geopandas as gpd
import matplotlib.pyplot as plt
import pytest
from shapely.geometry import Polygon
from typing_extensions import Self
import python_pkg.anki_decks.polish_mountain_ranges.polish_mountain_ranges_anki as _mod
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
from pathlib import Path
_init_worker = _mod._init_worker
@ -49,17 +51,26 @@ def _ranges() -> gpd.GeoDataFrame:
class _FakePool:
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
def __init__(
self,
_processes: int | None = None,
initializer: Callable[..., object] | None = None,
initargs: tuple[object, ...] = (),
) -> None:
if initializer:
initializer(*initargs)
def imap_unordered(self, func, items):
def imap_unordered(
self,
func: Callable[[object], object],
items: Iterable[object],
) -> list[object]:
return [func(item) for item in items]
def __enter__(self):
def __enter__(self) -> Self:
return self
def __exit__(self, *a):
def __exit__(self, *_args: object) -> None:
pass

View File

@ -3,6 +3,7 @@
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from unittest.mock import patch
import geopandas as gpd
@ -10,6 +11,11 @@ import matplotlib.pyplot as plt
import pytest
from shapely.geometry import Polygon
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
from typing_extensions import Self
try:
from python_pkg.anki_decks.polish_national_parks.polish_national_parks_anki import (
_init_worker,
@ -73,17 +79,26 @@ def _small_park() -> gpd.GeoDataFrame:
class _FakePool:
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
def __init__(
self,
_processes: int | None = None,
initializer: Callable[..., object] | None = None,
initargs: tuple[object, ...] = (),
) -> None:
if initializer:
initializer(*initargs)
def imap_unordered(self, func, items):
def imap_unordered(
self,
func: Callable[[object], object],
items: Iterable[object],
) -> list[object]:
return [func(item) for item in items]
def __enter__(self):
def __enter__(self) -> Self:
return self
def __exit__(self, *a):
def __exit__(self, *_args: object) -> None:
pass

View File

@ -9,10 +9,12 @@ import geopandas as gpd
import matplotlib.pyplot as plt
import pytest
from shapely.geometry import Polygon
from typing_extensions import Self
import python_pkg.anki_decks.polish_nature_reserves.polish_nature_reserves_anki as _mod
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
from pathlib import Path
_init_worker = _mod._init_worker
@ -47,17 +49,26 @@ def _reserves() -> gpd.GeoDataFrame:
class _FakePool:
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
def __init__(
self,
_processes: int | None = None,
initializer: Callable[..., object] | None = None,
initargs: tuple[object, ...] = (),
) -> None:
if initializer:
initializer(*initargs)
def imap_unordered(self, func, items):
def imap_unordered(
self,
func: Callable[[object], object],
items: Iterable[object],
) -> list[object]:
return [func(item) for item in items]
def __enter__(self):
def __enter__(self) -> Self:
return self
def __exit__(self, *a):
def __exit__(self, *_args: object) -> None:
pass

View File

@ -3,7 +3,7 @@
from __future__ import annotations
from pathlib import Path
from typing import Any
from typing import TYPE_CHECKING
from unittest.mock import patch
import geopandas as gpd
@ -12,6 +12,9 @@ import pytest
from shapely.geometry import LineString, Polygon
from typing_extensions import Self
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
try:
from python_pkg.anki_decks.polish_rivers.polish_rivers_anki import (
_init_worker,
@ -77,18 +80,18 @@ def _river_outside() -> gpd.GeoDataFrame:
class _FakePool:
def __init__(
self,
processes: int | None = None,
initializer: Any = None,
initargs: tuple[Any, ...] = (),
_processes: int | None = None,
initializer: Callable[..., object] | None = None,
initargs: tuple[object, ...] = (),
) -> None:
if initializer:
initializer(*initargs)
def imap_unordered(
self,
func: Any,
items: Any,
) -> list[Any]:
func: Callable[[object], object],
items: Iterable[object],
) -> list[object]:
return [func(item) for item in items]
def __enter__(self) -> Self:

View File

@ -3,7 +3,7 @@
from __future__ import annotations
from pathlib import Path
from typing import Any
from typing import TYPE_CHECKING
from unittest.mock import patch
import geopandas as gpd
@ -12,6 +12,9 @@ import pytest
from shapely.geometry import Point, Polygon
from typing_extensions import Self
if TYPE_CHECKING:
from collections.abc import Callable, Iterable
try:
from python_pkg.anki_decks.polish_unesco_sites.polish_unesco_sites_anki import (
_init_worker,
@ -79,18 +82,18 @@ def _site_polygon() -> gpd.GeoDataFrame:
class _FakePool:
def __init__(
self,
processes: int | None = None,
initializer: Any = None,
initargs: tuple[Any, ...] = (),
_processes: int | None = None,
initializer: Callable[..., object] | None = None,
initargs: tuple[object, ...] = (),
) -> None:
if initializer:
initializer(*initargs)
def imap_unordered(
self,
func: Any,
items: Any,
) -> list[Any]:
func: Callable[[object], object],
items: Iterable[object],
) -> list[object]:
return [func(item) for item in items]
def __enter__(self) -> Self:

View File

@ -167,7 +167,7 @@ class TestLoadStreetData:
patch.object(_mod_ref, "__file__", str(fake_file)),
patch(f"{_MOD}.get_warsaw_streets", return_value=_street_segments_gdf()),
):
streets, boundary = load_street_data()
_, boundary = load_street_data()
assert len(boundary) == 1
def test_file_not_found(self, tmp_path: Path) -> None:

View File

@ -1,37 +1,78 @@
"""Integration tests for the articles C server API."""
from http import HTTPStatus
import http.client
import json
import os
from pathlib import Path
import shutil
import socket
import subprocess
import time
from typing import Any
import urllib.error
import urllib.request
import urllib.parse
import pytest
class _HTTPError(Exception):
"""HTTP error with status code."""
def __init__(self, code: int) -> None:
super().__init__(f"HTTP {code}")
self.code = code
def _req(
url: str, method: str = "GET", data: dict[str, Any] | bytes | None = None
) -> tuple[int, bytes]:
"""Send an HTTP request and return status code and body."""
if data is not None and not isinstance(data, bytes | bytearray):
data = json.dumps(data).encode("utf-8")
req = urllib.request.Request(url, data=data, method=method)
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req, timeout=5) as resp:
parsed = urllib.parse.urlparse(url)
conn = http.client.HTTPConnection(parsed.hostname, parsed.port, timeout=5)
try:
headers = {"Content-Type": "application/json"}
conn.request(method, parsed.path or "/", body=data, headers=headers)
resp = conn.getresponse()
body = resp.read()
return resp.getcode(), body
status = resp.status
finally:
conn.close()
if status >= 400:
raise _HTTPError(status)
return status, body
def _probe_server(host: str, port: int) -> bool:
"""Try a single GET to the server. Return True if it responded."""
try:
conn = http.client.HTTPConnection(host, port, timeout=0.2)
try:
conn.request("GET", "/api/articles")
conn.getresponse().read()
return True
finally:
conn.close()
except OSError:
return False
def _wait_for_server(host: str, port: int, attempts: int = 30) -> None:
"""Poll the server until it responds or attempts are exhausted."""
for _ in range(attempts):
if _probe_server(host, port):
return
time.sleep(0.05)
def test_crud_roundtrip(tmp_path: Path) -> None:
"""Test full CRUD lifecycle for articles API."""
# Build C server
here = Path(__file__).resolve().parent.parent
subprocess.run(["make", "-s", "server_c"], check=True, cwd=str(here))
make_path = shutil.which("make")
assert make_path is not None, "make not found in PATH"
subprocess.run([make_path, "-s", "server_c"], check=True, cwd=str(here))
# Find a free port
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
@ -47,16 +88,7 @@ def test_crud_roundtrip(tmp_path: Path) -> None:
env["PORT"] = str(port)
srv = subprocess.Popen(["./server_c"], cwd=str(here), env=env)
try:
# wait briefly for server to be ready
for _ in range(30):
try:
with urllib.request.urlopen(
base + "/api/articles", timeout=0.2
) as resp:
resp.read()
break
except (OSError, urllib.error.URLError):
time.sleep(0.05)
_wait_for_server(host, port)
# Create
code, body = _req(
@ -97,10 +129,9 @@ def test_crud_roundtrip(tmp_path: Path) -> None:
assert code == HTTPStatus.NO_CONTENT
# Ensure gone
with pytest.raises(urllib.error.HTTPError) as exc_info:
with pytest.raises(_HTTPError) as exc_info:
_req(base + f"/api/articles/{art_id}")
assert exc_info.value.code == HTTPStatus.NOT_FOUND
exc_info.value.close()
finally:
srv.terminate()

View File

@ -20,12 +20,12 @@ class TestFindAlsDevice:
"glob",
return_value=[Path("/sys/bus/iio/devices/iio0/in_illuminance_raw")],
)
def test_found(self, _mock_glob: MagicMock) -> None:
def test_found(self, mock_glob: MagicMock) -> None:
result = auto_brightness_daemon._find_als_device()
assert result == Path("/sys/bus/iio/devices/iio0")
@patch.object(Path, "glob", return_value=[])
def test_not_found(self, _mock_glob: MagicMock) -> None:
def test_not_found(self, mock_glob: MagicMock) -> None:
assert auto_brightness_daemon._find_als_device() is None

View File

@ -17,7 +17,7 @@ class TestMainNoAls:
"""Tests for main() when no ALS device is found."""
@patch(f"{MOD}._find_als_device", return_value=None)
def test_exits_when_no_als(self, _mock_find: MagicMock) -> None:
def test_exits_when_no_als(self, mock_find: MagicMock) -> None:
with pytest.raises(SystemExit, match="1"):
auto_brightness_daemon.main()
@ -62,9 +62,9 @@ class TestMainDaemonLoop:
patch(f"{MOD}._lux_to_brightness", return_value=75),
patch(f"{MOD}._get_brightness", return_value=current_brightness),
patch(f"{MOD}._set_brightness", mock_set_brightness),
contextlib.suppress(KeyboardInterrupt),
):
# Simulate SIGINT by raising KeyboardInterrupt in sleep
with contextlib.suppress(KeyboardInterrupt):
auto_brightness_daemon.main()
return mock_set_brightness, mock_lux
@ -96,8 +96,8 @@ class TestMainDaemonLoop:
patch(f"{MOD}._lux_to_brightness", return_value=74),
patch(f"{MOD}._get_brightness", return_value=74),
patch(f"{MOD}._set_brightness") as mock_set,
contextlib.suppress(KeyboardInterrupt),
):
with contextlib.suppress(KeyboardInterrupt):
auto_brightness_daemon.main()
mock_set.assert_not_called()
@ -116,8 +116,8 @@ class TestMainDaemonLoop:
patch(f"{MOD}._lux_to_brightness", return_value=75),
patch(f"{MOD}._get_brightness", return_value=-1),
patch(f"{MOD}._set_brightness") as mock_set,
contextlib.suppress(KeyboardInterrupt),
):
with contextlib.suppress(KeyboardInterrupt):
auto_brightness_daemon.main()
mock_set.assert_not_called()
@ -133,8 +133,8 @@ class TestMainDaemonLoop:
patch(f"{MOD}.signal.signal"),
patch(f"{MOD}.time.sleep", side_effect=KeyboardInterrupt),
patch(f"{MOD}._is_enabled", return_value=False),
contextlib.suppress(KeyboardInterrupt),
):
with contextlib.suppress(KeyboardInterrupt):
auto_brightness_daemon.main()
mock_set_enabled.assert_called_once_with(enabled=True)
@ -150,8 +150,8 @@ class TestMainDaemonLoop:
patch(f"{MOD}.signal.signal"),
patch(f"{MOD}.time.sleep", side_effect=KeyboardInterrupt),
patch(f"{MOD}._is_enabled", return_value=False),
contextlib.suppress(KeyboardInterrupt),
):
with contextlib.suppress(KeyboardInterrupt):
auto_brightness_daemon.main()
mock_set_enabled.assert_not_called()
@ -166,8 +166,8 @@ class TestMainDaemonLoop:
patch(f"{MOD}.signal.signal"),
patch(f"{MOD}.time.sleep", side_effect=[None, KeyboardInterrupt]),
patch(f"{MOD}._is_enabled", side_effect=OSError("disk fail")),
contextlib.suppress(KeyboardInterrupt),
):
with contextlib.suppress(KeyboardInterrupt):
auto_brightness_daemon.main()
# No crash = exception was handled
@ -189,8 +189,8 @@ class TestMainDaemonLoop:
patch(f"{MOD}.signal.signal", side_effect=capture_signal),
patch(f"{MOD}.time.sleep", side_effect=KeyboardInterrupt),
patch(f"{MOD}._is_enabled", return_value=False),
contextlib.suppress(KeyboardInterrupt),
):
with contextlib.suppress(KeyboardInterrupt):
auto_brightness_daemon.main()
# Verify we captured a SIGTERM handler
@ -217,8 +217,8 @@ class TestMainDaemonLoop:
patch(f"{MOD}._lux_to_brightness", return_value=10),
patch(f"{MOD}._get_brightness", return_value=90),
patch(f"{MOD}._set_brightness") as mock_set,
contextlib.suppress(KeyboardInterrupt),
):
with contextlib.suppress(KeyboardInterrupt):
auto_brightness_daemon.main()
# delta=-80, step=-5, new_val=85
mock_set.assert_called_with(85)

View File

@ -20,12 +20,12 @@ class TestFindAlsDevice:
"glob",
return_value=[Path("/sys/bus/iio/devices/iio0/in_illuminance_raw")],
)
def test_found(self, _mock_glob: MagicMock) -> None:
def test_found(self, mock_glob: MagicMock) -> None:
result = brightness_controller._find_als_device()
assert result == Path("/sys/bus/iio/devices/iio0")
@patch.object(Path, "glob", return_value=[])
def test_not_found(self, _mock_glob: MagicMock) -> None:
def test_not_found(self, mock_glob: MagicMock) -> None:
assert brightness_controller._find_als_device() is None
@ -327,7 +327,7 @@ class TestRefreshBrightness:
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=75,
)
def test_updates_ui(self, _mock_get: MagicMock) -> None:
def test_updates_ui(self, mock_get: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl.slider_var = MagicMock()
@ -339,7 +339,7 @@ class TestRefreshBrightness:
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=-1,
)
def test_error(self, _mock_get: MagicMock) -> None:
def test_error(self, mock_get: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl._refresh_brightness()
@ -378,7 +378,7 @@ class TestOnSliderMove:
ctrl.pct_var.set.assert_not_called()
@patch("python_pkg.brightness_controller.brightness_controller._set_brightness")
def test_disables_auto_mode(self, _mock_set: MagicMock) -> None:
def test_disables_auto_mode(self, mock_set: MagicMock) -> None:
ctrl = _make_controller(daemon_state=True)
ctrl.auto_mode = True
ctrl.pct_var = MagicMock()
@ -396,7 +396,7 @@ class TestSetPct:
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=25,
)
def test_sets_brightness(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
def test_sets_brightness(self, mock_get: MagicMock, mock_set: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl.slider_var = MagicMock()
@ -417,7 +417,7 @@ class TestDecrease:
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=50,
)
def test_decrease(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
def test_decrease(self, mock_get: MagicMock, mock_set: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl.slider_var = MagicMock()
@ -429,7 +429,7 @@ class TestDecrease:
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=2,
)
def test_clamps_to_zero(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
def test_clamps_to_zero(self, mock_get: MagicMock, mock_set: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl.slider_var = MagicMock()
@ -449,7 +449,7 @@ class TestIncrease:
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=50,
)
def test_increase(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
def test_increase(self, mock_get: MagicMock, mock_set: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl.slider_var = MagicMock()
@ -461,7 +461,7 @@ class TestIncrease:
"python_pkg.brightness_controller.brightness_controller._get_brightness",
return_value=98,
)
def test_clamps_to_100(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
def test_clamps_to_100(self, mock_get: MagicMock, mock_set: MagicMock) -> None:
ctrl = _make_controller()
ctrl.pct_var = MagicMock()
ctrl.slider_var = MagicMock()

View File

@ -91,7 +91,7 @@ class TestPollAls:
"""Tests for _poll_als."""
@patch(f"{MOD}._read_lux", return_value=42.5)
def test_updates_lux_display(self, _mock_lux: MagicMock) -> None:
def test_updates_lux_display(self, mock_lux: MagicMock) -> None:
ctrl = _make_controller(als_path=Path("/fake"))
ctrl.lux_var = MagicMock()
ctrl.root = MagicMock()
@ -105,7 +105,7 @@ class TestPollAls:
ctrl.root.after.assert_called_once()
@patch(f"{MOD}._read_lux", side_effect=OSError("sensor fail"))
def test_sensor_error(self, _mock_lux: MagicMock) -> None:
def test_sensor_error(self, mock_lux: MagicMock) -> None:
ctrl = _make_controller(als_path=Path("/fake"))
ctrl.lux_var = MagicMock()
ctrl.root = MagicMock()
@ -118,7 +118,7 @@ class TestPollAls:
ctrl.lux_var.set.assert_called_with("sensor error")
@patch(f"{MOD}._read_lux", side_effect=ValueError("bad value"))
def test_sensor_value_error(self, _mock_lux: MagicMock) -> None:
def test_sensor_value_error(self, mock_lux: MagicMock) -> None:
ctrl = _make_controller(als_path=Path("/fake"))
ctrl.lux_var = MagicMock()
ctrl.root = MagicMock()
@ -131,7 +131,7 @@ class TestPollAls:
ctrl.lux_var.set.assert_called_with("sensor error")
@patch(f"{MOD}._read_lux", return_value=10.0)
def test_syncs_daemon_state_change(self, _mock_lux: MagicMock) -> None:
def test_syncs_daemon_state_change(self, mock_lux: MagicMock) -> None:
"""When daemon state differs from auto_mode, syncs it."""
ctrl = _make_controller(als_path=Path("/fake"))
ctrl.auto_mode = False
@ -148,7 +148,7 @@ class TestPollAls:
assert ctrl.auto_mode is True
@patch(f"{MOD}._read_lux", return_value=10.0)
def test_no_sync_when_same(self, _mock_lux: MagicMock) -> None:
def test_no_sync_when_same(self, mock_lux: MagicMock) -> None:
"""When daemon state matches auto_mode, no sync needed."""
ctrl = _make_controller(als_path=Path("/fake"))
ctrl.auto_mode = False
@ -177,7 +177,7 @@ class TestPollBrightness:
"""Tests for _poll_brightness."""
@patch(f"{MOD}._get_brightness", return_value=60)
def test_refreshes_when_not_auto(self, _mock_get: MagicMock) -> None:
def test_refreshes_when_not_auto(self, mock_get: MagicMock) -> None:
ctrl = _make_controller()
ctrl.auto_mode = False
ctrl.pct_var = MagicMock()

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from pathlib import Path
import subprocess
from typing import Any
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, mock_open, patch
import pytest
@ -24,6 +24,9 @@ from python_pkg.cinema_planner._cinema_parsing import (
parse_time,
)
if TYPE_CHECKING:
import contextlib
class TestParseTime:
"""Tests for parse_time."""
@ -208,13 +211,13 @@ class TestParseCinemaCityHtml:
f"{times_html}"
)
def _patch_open(self, html: str) -> Any:
def _patch_open(self, html: str) -> contextlib.AbstractContextManager[MagicMock]:
return patch.object(Path, "open", mock_open(read_data=html))
def test_parse_single_movie(self) -> None:
html = "header" + self._make_html_section("Movie A", 120, ["10:00", "14:00"])
with self._patch_open(html):
movies, date = parse_cinema_city_html("test.html")
movies, _ = parse_cinema_city_html("test.html")
assert len(movies) == 1
assert movies[0].name == "Movie A"
assert movies[0].duration == 120
@ -223,7 +226,7 @@ class TestParseCinemaCityHtml:
def test_parse_with_date(self) -> None:
html = "2025-01-25 stuff" + self._make_html_section("Movie A", 90, ["18:00"])
with self._patch_open(html):
movies, date = parse_cinema_city_html("test.html")
_, date = parse_cinema_city_html("test.html")
assert date == "2025-01-25"
def test_parse_with_genres(self) -> None:
@ -231,7 +234,7 @@ class TestParseCinemaCityHtml:
"Horror Film", 100, ["20:00"], genre="Horror, Thriller"
)
with self._patch_open(html):
movies, date = parse_cinema_city_html("test.html")
movies, _ = parse_cinema_city_html("test.html")
assert len(movies) == 1
assert "Horror" in movies[0].genres
assert "Thriller" in movies[0].genres
@ -239,7 +242,7 @@ class TestParseCinemaCityHtml:
def test_no_name_match(self) -> None:
html = 'header class="row movie-row"> no name here'
with self._patch_open(html):
movies, date = parse_cinema_city_html("test.html")
movies, _ = parse_cinema_city_html("test.html")
assert len(movies) == 0
def test_no_duration_match(self) -> None:
@ -250,7 +253,7 @@ class TestParseCinemaCityHtml:
'<button class="btn btn-primary btn-lg">10:00</button>'
)
with self._patch_open(html):
movies, date = parse_cinema_city_html("test.html")
movies, _ = parse_cinema_city_html("test.html")
assert len(movies) == 0
def test_no_times_match(self) -> None:
@ -260,7 +263,7 @@ class TestParseCinemaCityHtml:
"<span>100 min</span>"
)
with self._patch_open(html):
movies, date = parse_cinema_city_html("test.html")
movies, _ = parse_cinema_city_html("test.html")
assert len(movies) == 0
def test_alternate_time_pattern(self) -> None:
@ -271,7 +274,7 @@ class TestParseCinemaCityHtml:
"> 10:00 (HTTPS://something"
)
with self._patch_open(html):
movies, date = parse_cinema_city_html("test.html")
movies, _ = parse_cinema_city_html("test.html")
assert len(movies) == 1
def test_deduplicate_movies(self) -> None:
@ -467,7 +470,7 @@ class TestParseCinemaCityText:
text = "MOVIE TITLE\n110 min\n10:00\n"
with patch(
"python_pkg.cinema_planner._cinema_parsing._try_parse_time",
side_effect=lambda t: None,
side_effect=lambda _t: None,
):
result = parse_cinema_city_text(text)
assert len(result) == 0

View File

@ -5,7 +5,6 @@ from __future__ import annotations
import argparse
from io import StringIO
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, mock_open, patch
import pytest
@ -94,7 +93,7 @@ class TestLoadMoviesInteractive:
"""Tests for _load_movies_interactive."""
@patch("builtins.input", side_effect=["Movie A, 10:00, 90min", ""])
def test_single_movie(self, _mock: MagicMock) -> None:
def test_single_movie(self, mock: MagicMock) -> None:
result = _load_movies_interactive()
assert len(result) == 1
assert result[0].name == "Movie A"
@ -107,17 +106,17 @@ class TestLoadMoviesInteractive:
"",
],
)
def test_multiple_movies(self, _mock: MagicMock) -> None:
def test_multiple_movies(self, mock: MagicMock) -> None:
result = _load_movies_interactive()
assert len(result) == 2
@patch("builtins.input", side_effect=EOFError)
def test_eof(self, _mock: MagicMock) -> None:
def test_eof(self, mock: MagicMock) -> None:
result = _load_movies_interactive()
assert result == []
@patch("builtins.input", side_effect=["bad line", ""])
def test_invalid_input(self, _mock: MagicMock) -> None:
def test_invalid_input(self, mock: MagicMock) -> None:
result = _load_movies_interactive()
assert result == []
@ -125,7 +124,7 @@ class TestLoadMoviesInteractive:
"builtins.input",
side_effect=["bad line", "Movie A, 10:00, 90min", ""],
)
def test_mixed_valid_invalid(self, _mock: MagicMock) -> None:
def test_mixed_valid_invalid(self, mock: MagicMock) -> None:
result = _load_movies_interactive()
assert len(result) == 1
@ -147,7 +146,7 @@ class TestLoadMoviesFromFile:
)
def test_htm_file(self, mock_parse: MagicMock) -> None:
mock_parse.return_value = ([Movie("A", [600], 120)], None)
movies, date = _load_movies_from_file(Path("test.htm"))
_, _ = _load_movies_from_file(Path("test.htm"))
mock_parse.assert_called_once()
@patch(
@ -161,17 +160,21 @@ class TestLoadMoviesFromFile:
def test_text_file(self) -> None:
content = "Movie A, 10:00, 90min\n# comment\nMovie B, 14:00, 120min\n"
with patch.object(Path, "open", mock_open(read_data=content)):
with patch.object(Path, "suffix", new=".txt"):
with (
patch.object(Path, "open", mock_open(read_data=content)),
patch.object(Path, "suffix", new=".txt"),
):
movies, date = _load_movies_from_file(Path("test.txt"))
assert len(movies) == 2
assert date is None
def test_text_file_with_bad_line(self) -> None:
content = "Movie A, 10:00, 90min\nbad line\n"
with patch.object(Path, "open", mock_open(read_data=content)):
with patch.object(Path, "suffix", new=".txt"):
movies, date = _load_movies_from_file(Path("test.txt"))
with (
patch.object(Path, "open", mock_open(read_data=content)),
patch.object(Path, "suffix", new=".txt"),
):
movies, _ = _load_movies_from_file(Path("test.txt"))
assert len(movies) == 1
@ -192,7 +195,7 @@ class TestLoadMoviesFromStdin:
class TestFilterMovies:
"""Tests for _filter_movies."""
def _make_args(self, **kwargs: Any) -> argparse.Namespace:
def _make_args(self, **kwargs: str | bool | None) -> argparse.Namespace:
defaults = {
"select": None,
"exclude": None,
@ -204,7 +207,7 @@ class TestFilterMovies:
def test_no_filters(self) -> None:
movies = [Movie("A", [600], 120)]
result, excluded = _filter_movies(movies, self._make_args())
result, _ = _filter_movies(movies, self._make_args())
# Default horror exclusion but no genre matches
assert len(result) == 1
@ -259,7 +262,7 @@ class TestFilterMovies:
Movie("Action Movie", [600], 120, ["Action"]),
Movie("Drama Movie", [600], 120, ["Drama"]),
]
result, excluded = _filter_movies(
result, _ = _filter_movies(
movies,
self._make_args(all_genres=True, exclude_genre="action"),
)
@ -268,7 +271,7 @@ class TestFilterMovies:
def test_no_genre_filtered(self) -> None:
movies = [Movie("Movie", [600], 120, ["Comedy"])]
result, excluded = _filter_movies(movies, self._make_args())
result, _ = _filter_movies(movies, self._make_args())
assert len(result) == 1
@ -301,7 +304,7 @@ class TestApplyMustWatchFilter:
class TestOutputSchedules:
"""Tests for _output_schedules."""
def _make_args(self, **kwargs: Any) -> argparse.Namespace:
def _make_args(self, **kwargs: str | int | None) -> argparse.Namespace:
defaults = {
"buffer": 0,
"max_schedules": 5,

View File

@ -297,7 +297,9 @@ class KeyboardCoopGame:
pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2)
# Draw letter
text = self.fonts.small.render(letter.upper(), True, TEXT_COLOR)
text = self.fonts.small.render(
letter.upper(), antialias=True, color=TEXT_COLOR
)
text_rect = text.get_rect(center=rect.center)
self.screen.blit(text, text_rect)
@ -305,14 +307,14 @@ class KeyboardCoopGame:
self, text: str, pos: tuple[int, int], font: pygame.font.Font
) -> None:
"""Draw a single line of text at the given position."""
rendered = font.render(text, True, TEXT_COLOR)
rendered = font.render(text, antialias=True, color=TEXT_COLOR)
self.screen.blit(rendered, pos)
def _draw_button(self, rect: pygame.Rect, label: str) -> None:
"""Draw a button with the given label."""
pygame.draw.rect(self.screen, KEY_COLOR, rect)
pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2)
text = self.fonts.small.render(label, True, TEXT_COLOR)
text = self.fonts.small.render(label, antialias=True, color=TEXT_COLOR)
self.screen.blit(text, text.get_rect(center=rect.center))
def _draw_ui(self) -> tuple[pygame.Rect, pygame.Rect]:
@ -329,7 +331,9 @@ class KeyboardCoopGame:
# Current player with color
player_color = PLAYER_COLORS[self.state.current_player]
player_text = self.fonts.normal.render(
f"Current Player: {self.state.current_player + 1}", True, player_color
f"Current Player: {self.state.current_player + 1}",
antialias=True,
color=player_color,
)
self.screen.blit(player_text, (30, 100))

View File

@ -194,7 +194,9 @@ def main() -> None:
def _build(tmpdir: str) -> None:
# ── Lazy imports of moved part builders ───────────────────────
# ── Lazy imports to avoid circular dependency ────────────────
# These submodules import constants from this module, so they
# cannot be imported at the top level.
from moviepy.audio.fx import MultiplyVolume
from python_pkg.moviepy_showcase._moviepy_audio_output import (
@ -208,7 +210,9 @@ def _build(tmpdir: str) -> None:
part1_clip_types,
part2_clip_methods,
)
from python_pkg.moviepy_showcase._moviepy_video_effects import part3_video_effects
from python_pkg.moviepy_showcase._moviepy_video_effects import (
part3_video_effects,
)
# ── Render each part to its own temp file ─────────────────────
# Title card

View File

@ -16,7 +16,7 @@ import pytest
_H, _W = 1080, 1920
def create_mock_clip(**overrides: Any) -> MagicMock:
def create_mock_clip(**overrides: float | tuple[int, int]) -> MagicMock:
"""Return a MagicMock that behaves enough like a moviepy clip."""
clip = MagicMock()
clip.duration = overrides.get("duration", 2.0)
@ -71,12 +71,12 @@ _clip_classes = [
"CompositeAudioClip",
]
for _cls in _clip_classes:
getattr(mock_moviepy, _cls).side_effect = lambda *a, **kw: create_mock_clip()
getattr(mock_moviepy, _cls).side_effect = lambda *_a, **_kw: create_mock_clip()
mock_moviepy.concatenate_videoclips.side_effect = lambda *a, **kw: create_mock_clip()
mock_moviepy.concatenate_audioclips.side_effect = lambda *a, **kw: create_mock_clip()
mock_moviepy.concatenate_videoclips.side_effect = lambda *_a, **_kw: create_mock_clip()
mock_moviepy.concatenate_audioclips.side_effect = lambda *_a, **_kw: create_mock_clip()
mock_moviepy.video.compositing.CompositeVideoClip.clips_array.side_effect = (
lambda *a, **kw: create_mock_clip()
lambda *_a, **_kw: create_mock_clip()
)
# Drawing tools must return real numpy arrays (used in numpy ops)

View File

@ -25,7 +25,7 @@ def test_make_sine_maker_scalar() -> None:
"""maker() with scalar t → t_arr.ndim == 0 → returns 1-D."""
import moviepy as mp
mp.AudioClip.side_effect = lambda *a, **kw: MagicMock()
mp.AudioClip.side_effect = lambda *_a, **_kw: MagicMock()
_make_sine(440.0, 1.0)
maker = mp.AudioClip.call_args[0][0]
@ -39,7 +39,7 @@ def test_make_sine_maker_array() -> None:
"""maker() with array t → t_arr.ndim > 0 → returns 2-D."""
import moviepy as mp
mp.AudioClip.side_effect = lambda *a, **kw: MagicMock()
mp.AudioClip.side_effect = lambda *_a, **_kw: MagicMock()
_make_sine(440.0, 1.0)
maker = mp.AudioClip.call_args[0][0]

View File

@ -25,7 +25,7 @@ def test_part1_data_to_frame() -> None:
"""Extract and test the inner data_to_frame function."""
import moviepy as mp
mp.DataVideoClip.side_effect = lambda *a, **kw: create_mock_clip()
mp.DataVideoClip.side_effect = lambda *_a, **_kw: create_mock_clip()
result = part1_clip_types()
assert len(result) > 0

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import contextlib
from pathlib import Path
import tempfile
from typing import Any
from unittest.mock import MagicMock, patch
@ -102,17 +103,18 @@ def test_resize_to_canvas() -> None:
def test_render_part() -> None:
s1 = create_mock_clip()
s2 = create_mock_clip()
_render_part([s1, s2], "/tmp/test_part.mp4", "test")
_render_part([s1, s2], tempfile.gettempdir() + "/test_part.mp4", "test")
s1.close.assert_called_once()
s2.close.assert_called_once()
# ── main ─────────────────────────────────────────────────────────
def test_main_success() -> None:
mock_dir = tempfile.gettempdir() + "/mock_dir"
with (
patch(
"python_pkg.moviepy_showcase.moviepy_showcase.tempfile.mkdtemp",
return_value="/tmp/mock_dir",
return_value=mock_dir,
),
patch(
"python_pkg.moviepy_showcase.moviepy_showcase._build",
@ -122,15 +124,16 @@ def test_main_success() -> None:
) as mock_rmtree,
):
main()
mock_build.assert_called_once_with("/tmp/mock_dir")
mock_rmtree.assert_called_once_with("/tmp/mock_dir", ignore_errors=True)
mock_build.assert_called_once_with(mock_dir)
mock_rmtree.assert_called_once_with(mock_dir, ignore_errors=True)
def test_main_build_raises() -> None:
mock_dir = tempfile.gettempdir() + "/mock_dir"
with (
patch(
"python_pkg.moviepy_showcase.moviepy_showcase.tempfile.mkdtemp",
return_value="/tmp/mock_dir",
return_value=mock_dir,
),
patch(
"python_pkg.moviepy_showcase.moviepy_showcase._build",
@ -142,7 +145,7 @@ def test_main_build_raises() -> None:
):
with contextlib.suppress(RuntimeError):
main()
mock_rmtree.assert_called_once_with("/tmp/mock_dir", ignore_errors=True)
mock_rmtree.assert_called_once_with(mock_dir, ignore_errors=True)
# ── _build ───────────────────────────────────────────────────────
@ -155,4 +158,4 @@ def test_build() -> None:
),
patch.object(Path, "stat", return_value=mock_stat),
):
_build("/tmp/test_build")
_build(tempfile.gettempdir() + "/test_build")

View File

@ -14,6 +14,8 @@ Usage:
from __future__ import annotations
import argparse
import importlib.util
import logging
from pathlib import Path
import sys
import warnings
@ -49,6 +51,8 @@ from python_pkg.music_gen._music_speech import (
warnings.filterwarnings("ignore", category=FutureWarning)
warnings.filterwarnings("ignore", category=UserWarning)
logger = logging.getLogger(__name__)
# Re-export all public symbols for backwards compatibility
__all__ = [
"BARK_MAX_CHARS",
@ -85,8 +89,6 @@ def check_dependencies(*, include_bark: bool = False) -> bool:
Args:
include_bark: Whether to check for Bark dependencies as well.
"""
import importlib.util
missing = []
if importlib.util.find_spec("torch") is None:
@ -102,32 +104,23 @@ def check_dependencies(*, include_bark: bool = False) -> bool:
missing.append("git+https://github.com/suno-ai/bark.git")
if missing:
print("Missing dependencies. Install with:")
print(f" pip install {' '.join(missing)}")
print("\nFor CUDA support:")
print(" pip install torch --index-url https://download.pytorch.org/whl/cu121")
print(" pip install transformers scipy")
logger.error("Missing dependencies. Install with:")
logger.error(" pip install %s", " ".join(missing))
logger.error("For CUDA support:")
logger.error(
" pip install torch --index-url https://download.pytorch.org/whl/cu121",
)
logger.error(" pip install transformers scipy")
if include_bark:
print("\nFor Bark vocals:")
print(" pip install git+https://github.com/suno-ai/bark.git")
logger.error("For Bark vocals:")
logger.error(
" pip install git+https://github.com/suno-ai/bark.git",
)
return False
return True
def interactive_mode(model: object, processor: object) -> None:
"""Run interactive prompt mode."""
print("\n" + "=" * 60)
print("INTERACTIVE MODE")
print("=" * 60)
print("Enter prompts to generate music. Commands:")
print(" :q or :quit - Exit")
print(" :d <seconds> - Set duration (e.g., ':d 15')")
print(" :h or :help - Show example prompts")
print("=" * 60)
duration = 10
example_prompts = [
EXAMPLE_PROMPTS = [
"upbeat electronic dance music with heavy bass",
"calm acoustic guitar melody with soft percussion",
"epic orchestral soundtrack with dramatic strings",
@ -140,49 +133,99 @@ def interactive_mode(model: object, processor: object) -> None:
"tropical house with steel drums",
]
def _show_help() -> None:
"""Display example prompts."""
logger.info("Example prompts:")
for i, ex in enumerate(EXAMPLE_PROMPTS, 1):
logger.info(" %d. %s", i, ex)
def _handle_duration(raw: str) -> int | None:
"""Parse and return a new duration, or None on failure."""
try:
value = int(raw.strip())
except ValueError:
logger.warning(
"Invalid duration. Use ':d <number>' e.g., ':d 15'",
)
return None
else:
clamped = max(1, min(30, value))
logger.info("Duration set to %ds", clamped)
return clamped
def _resolve_prompt(prompt: str) -> str | None:
"""Resolve a numeric prompt to an example, or return as-is.
Returns None if the number is out of range.
"""
if prompt.isdigit():
idx = int(prompt) - 1
if 0 <= idx < len(EXAMPLE_PROMPTS):
resolved = EXAMPLE_PROMPTS[idx]
logger.info("Using: %s", resolved)
return resolved
logger.warning(
"Invalid number. Enter 1-%d",
len(EXAMPLE_PROMPTS),
)
return None
return prompt
def interactive_mode(model: object, processor: object) -> None:
"""Run interactive prompt mode."""
banner = "=" * 60
logger.info("\n%s", banner)
logger.info("INTERACTIVE MODE")
logger.info("%s", banner)
logger.info("Enter prompts to generate music. Commands:")
logger.info(" :q or :quit - Exit")
logger.info(" :d <seconds> - Set duration (e.g., ':d 15')")
logger.info(" :h or :help - Show example prompts")
logger.info("%s", banner)
duration = 10
while True:
try:
prompt = input(f"\n[{duration}s] Enter prompt: ").strip()
except (EOFError, KeyboardInterrupt):
print("\nExiting...")
logger.info("Exiting...")
break
if not prompt:
continue
if prompt.lower() in (":q", ":quit", "quit", "exit"):
print("Exiting...")
logger.info("Exiting...")
break
if prompt.lower() in (":h", ":help", "help"):
print("\nExample prompts:")
for i, ex in enumerate(example_prompts, 1):
print(f" {i}. {ex}")
_show_help()
continue
if prompt.startswith(":d "):
try:
duration = int(prompt[3:].strip())
duration = max(1, min(30, duration)) # Clamp to 1-30
print(f"Duration set to {duration}s")
except ValueError:
print("Invalid duration. Use ':d <number>' e.g., ':d 15'")
new_dur = _handle_duration(prompt[3:])
if new_dur is not None:
duration = new_dur
continue
# Check if user entered a number to use example prompt
if prompt.isdigit():
idx = int(prompt) - 1
if 0 <= idx < len(example_prompts):
prompt = example_prompts[idx]
print(f"Using: {prompt}")
else:
print(f"Invalid number. Enter 1-{len(example_prompts)}")
resolved = _resolve_prompt(prompt)
if resolved is None:
continue
try:
generate_music(prompt, model, processor, duration_seconds=duration)
except (RuntimeError, ValueError, OSError) as e:
print(f"Error generating music: {e}")
generate_music(
resolved,
model,
processor,
duration_seconds=duration,
)
except (RuntimeError, ValueError, OSError):
logger.exception("Error generating music")
def main() -> None:
@ -275,7 +318,9 @@ Bark tokens: [laughter] [laughs] [sighs] [music] [gasps] ♪ (singing)
if not args.prompt and not args.interactive:
parser.print_help()
print("\nError: Either provide a prompt or use --interactive mode")
logger.error(
"Either provide a prompt or use --interactive mode",
)
sys.exit(1)
# Check dependencies

View File

@ -57,8 +57,8 @@ class TestGetDevice:
patch.dict("sys.modules", {"torch": mock_torch}),
patch("shutil.which", return_value="/usr/bin/nvidia-smi"),
patch("subprocess.run", return_value=mock_result),
pytest.raises(RuntimeError, match="NVIDIA GPU detected"),
):
with pytest.raises(RuntimeError, match="NVIDIA GPU detected"):
get_device()
def test_nvidia_smi_not_found(self) -> None:

View File

@ -2,7 +2,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
import logging
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
from python_pkg.music_gen.music_generator import (
@ -21,56 +22,64 @@ class TestCheckDependencies:
with patch("importlib.util.find_spec", return_value=MagicMock()):
assert check_dependencies() is True
def test_torch_missing(self, capsys: pytest.CaptureFixture[str]) -> None:
def mock_find_spec(name: str) -> Any:
def test_torch_missing(self, caplog: pytest.LogCaptureFixture) -> None:
def mock_find_spec(name: str) -> MagicMock | None:
if name == "torch":
return None
return MagicMock()
with patch("importlib.util.find_spec", side_effect=mock_find_spec):
with (
caplog.at_level(logging.DEBUG),
patch("importlib.util.find_spec", side_effect=mock_find_spec),
):
assert check_dependencies() is False
captured = capsys.readouterr()
assert "torch" in captured.out
assert "torch" in caplog.text
def test_transformers_missing(self, capsys: pytest.CaptureFixture[str]) -> None:
def mock_find_spec(name: str) -> Any:
def test_transformers_missing(self, caplog: pytest.LogCaptureFixture) -> None:
def mock_find_spec(name: str) -> MagicMock | None:
if name == "transformers":
return None
return MagicMock()
with patch("importlib.util.find_spec", side_effect=mock_find_spec):
with (
caplog.at_level(logging.DEBUG),
patch("importlib.util.find_spec", side_effect=mock_find_spec),
):
assert check_dependencies() is False
captured = capsys.readouterr()
assert "transformers" in captured.out
assert "transformers" in caplog.text
def test_scipy_missing(self, capsys: pytest.CaptureFixture[str]) -> None:
def mock_find_spec(name: str) -> Any:
def test_scipy_missing(self, caplog: pytest.LogCaptureFixture) -> None:
def mock_find_spec(name: str) -> MagicMock | None:
if name == "scipy":
return None
return MagicMock()
with patch("importlib.util.find_spec", side_effect=mock_find_spec):
with (
caplog.at_level(logging.DEBUG),
patch("importlib.util.find_spec", side_effect=mock_find_spec),
):
assert check_dependencies() is False
captured = capsys.readouterr()
assert "scipy" in captured.out
assert "scipy" in caplog.text
def test_bark_missing_with_include_bark(
self,
capsys: pytest.CaptureFixture[str],
caplog: pytest.LogCaptureFixture,
) -> None:
def mock_find_spec(name: str) -> Any:
def mock_find_spec(name: str) -> MagicMock | None:
if name == "bark":
return None
return MagicMock()
with patch("importlib.util.find_spec", side_effect=mock_find_spec):
with (
caplog.at_level(logging.DEBUG),
patch("importlib.util.find_spec", side_effect=mock_find_spec),
):
assert check_dependencies(include_bark=True) is False
captured = capsys.readouterr()
assert "bark" in captured.out.lower()
assert "bark" in caplog.text.lower()
def test_bark_not_checked_without_flag(self) -> None:
with patch("importlib.util.find_spec", return_value=MagicMock()):
@ -84,64 +93,78 @@ class TestCheckDependencies:
class TestInteractiveMode:
"""Tests for interactive_mode()."""
def test_quit_command(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", return_value=":q"):
def test_quit_command(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", return_value=":q"),
):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Exiting" in captured.out
assert "Exiting" in caplog.text
def test_quit_word(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", return_value="quit"):
def test_quit_word(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", return_value="quit"),
):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Exiting" in captured.out
assert "Exiting" in caplog.text
def test_exit_word(self, capsys: pytest.CaptureFixture[str]) -> None:
def test_exit_word(self) -> None:
with patch("builtins.input", return_value="exit"):
interactive_mode(MagicMock(), MagicMock())
def test_help_command(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", side_effect=[":h", ":q"]):
def test_help_command(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=[":h", ":q"]),
):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Example prompts" in captured.out
assert "Example prompts" in caplog.text
def test_help_word(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", side_effect=["help", ":q"]):
def test_help_word(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=["help", ":q"]),
):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Example prompts" in captured.out
assert "Example prompts" in caplog.text
def test_set_duration(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", side_effect=[":d 15", ":q"]):
def test_set_duration(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=[":d 15", ":q"]),
):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Duration set to 15s" in captured.out
assert "Duration set to 15s" in caplog.text
def test_set_duration_clamped(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", side_effect=[":d 100", ":q"]):
def test_set_duration_clamped(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=[":d 100", ":q"]),
):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Duration set to 30s" in captured.out
assert "Duration set to 30s" in caplog.text
def test_set_duration_invalid(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", side_effect=[":d abc", ":q"]):
def test_set_duration_invalid(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=[":d abc", ":q"]),
):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Invalid duration" in captured.out
assert "Invalid duration" in caplog.text
def test_empty_prompt(self) -> None:
with patch("builtins.input", side_effect=["", ":q"]):
interactive_mode(MagicMock(), MagicMock())
def test_number_prompt_valid(self, capsys: pytest.CaptureFixture[str]) -> None:
def test_number_prompt_valid(self) -> None:
with (
patch("builtins.input", side_effect=["1", ":q"]),
patch(
@ -152,12 +175,14 @@ class TestInteractiveMode:
mock_gen.assert_called_once()
def test_number_prompt_invalid(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", side_effect=["99", ":q"]):
def test_number_prompt_invalid(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=["99", ":q"]),
):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Invalid number" in captured.out
assert "Invalid number" in caplog.text
def test_normal_prompt(self) -> None:
with (
@ -170,8 +195,9 @@ class TestInteractiveMode:
mock_gen.assert_called_once()
def test_generation_error(self, capsys: pytest.CaptureFixture[str]) -> None:
def test_generation_error(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=["jazz music", ":q"]),
patch(
"python_pkg.music_gen.music_generator.generate_music",
@ -180,46 +206,56 @@ class TestInteractiveMode:
):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Error generating music" in captured.out
assert "Error generating music" in caplog.text
def test_eof_error(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", side_effect=EOFError):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Exiting" in captured.out
def test_keyboard_interrupt(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", side_effect=KeyboardInterrupt):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Exiting" in captured.out
def test_quit_long(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", return_value=":quit"):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Exiting" in captured.out
def test_help_long(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", side_effect=[":help", ":q"]):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Example prompts" in captured.out
def test_duration_clamp_minimum(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch("builtins.input", side_effect=[":d 0", ":q"]):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Duration set to 1s" in captured.out
def test_generation_value_error(self, capsys: pytest.CaptureFixture[str]) -> None:
def test_eof_error(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=EOFError),
):
interactive_mode(MagicMock(), MagicMock())
assert "Exiting" in caplog.text
def test_keyboard_interrupt(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=KeyboardInterrupt),
):
interactive_mode(MagicMock(), MagicMock())
assert "Exiting" in caplog.text
def test_quit_long(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", return_value=":quit"),
):
interactive_mode(MagicMock(), MagicMock())
assert "Exiting" in caplog.text
def test_help_long(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=[":help", ":q"]),
):
interactive_mode(MagicMock(), MagicMock())
assert "Example prompts" in caplog.text
def test_duration_clamp_minimum(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=[":d 0", ":q"]),
):
interactive_mode(MagicMock(), MagicMock())
assert "Duration set to 1s" in caplog.text
def test_generation_value_error(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=["jazz", ":q"]),
patch(
"python_pkg.music_gen.music_generator.generate_music",
@ -228,11 +264,11 @@ class TestInteractiveMode:
):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Error generating music" in captured.out
assert "Error generating music" in caplog.text
def test_generation_os_error(self, capsys: pytest.CaptureFixture[str]) -> None:
def test_generation_os_error(self, caplog: pytest.LogCaptureFixture) -> None:
with (
caplog.at_level(logging.DEBUG),
patch("builtins.input", side_effect=["jazz", ":q"]),
patch(
"python_pkg.music_gen.music_generator.generate_music",
@ -241,5 +277,4 @@ class TestInteractiveMode:
):
interactive_mode(MagicMock(), MagicMock())
captured = capsys.readouterr()
assert "Error generating music" in captured.out
assert "Error generating music" in caplog.text

View File

@ -2,6 +2,7 @@
from __future__ import annotations
import tempfile
from unittest.mock import MagicMock, patch
import pytest
@ -227,7 +228,7 @@ class TestMain:
with (
patch(
"sys.argv",
["music_generator", "--output", "/tmp/out", "test"],
["music_generator", "--output", tempfile.gettempdir() + "/out", "test"],
),
patch(
"python_pkg.music_gen.music_generator.check_dependencies",

View File

@ -426,7 +426,7 @@ class TestGenerateVocalsForSong:
return_value=["Hello."],
),
):
vocals, sr = _generate_vocals_for_song("Hello.", "v2/en_speaker_6")
_, sr = _generate_vocals_for_song("Hello.", "v2/en_speaker_6")
assert sr == 24000
# The original_load should have been called via patched_load
@ -487,6 +487,6 @@ class TestGenerateInstrumentalForSong:
return_value=audio,
),
):
instrumental, sr = _generate_instrumental_for_song("test", 60)
_, sr = _generate_instrumental_for_song("test", 60)
assert sr == 100

View File

@ -31,7 +31,7 @@ class PokerGuiMixin:
self.root.title("🃏 Texas Hold'em Modifier")
self.root.geometry("650x750")
self.root.configure(bg="#0f4c3a")
self.root.resizable(True, True)
self.root.resizable(width=True, height=True)
style = ttk.Style()
style.theme_use("clam")
@ -188,7 +188,7 @@ class PokerGuiMixin:
parent, bg="#2d2d2d", relief=tk.RIDGE, bd=3, height=150
)
self.result_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 20), padx=10)
self.result_frame.pack_propagate(False)
self.result_frame.pack_propagate(flag=False)
self.result_label = tk.Label(
self.result_frame,

View File

@ -3,9 +3,12 @@
from __future__ import annotations
import sys
from typing import Any
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
if TYPE_CHECKING:
from python_pkg.poker_modifier_app._poker_gui import PokerGuiMixin
def _install_tk_mocks() -> dict[str, MagicMock]:
"""Install mock tkinter modules and return them."""
@ -26,19 +29,19 @@ def _install_tk_mocks() -> dict[str, MagicMock]:
# Make constructors return fresh mocks each time
mock_tk.Tk.return_value = MagicMock(name="root")
mock_tk.Frame.side_effect = lambda *a, **kw: MagicMock(name="Frame")
mock_tk.Label.side_effect = lambda *a, **kw: MagicMock(name="Label")
mock_tk.LabelFrame.side_effect = lambda *a, **kw: MagicMock(name="LabelFrame")
mock_tk.Scale.side_effect = lambda *a, **kw: MagicMock(name="Scale")
mock_tk.IntVar.side_effect = lambda *a, **kw: MagicMock(name="IntVar")
mock_tk.BooleanVar.side_effect = lambda *a, **kw: MagicMock(name="BooleanVar")
mock_tk.Checkbutton.side_effect = lambda *a, **kw: MagicMock(name="Checkbutton")
mock_tk.Button.side_effect = lambda *a, **kw: MagicMock(name="Button")
mock_tk.Frame.side_effect = lambda *_a, **_kw: MagicMock(name="Frame")
mock_tk.Label.side_effect = lambda *_a, **_kw: MagicMock(name="Label")
mock_tk.LabelFrame.side_effect = lambda *_a, **_kw: MagicMock(name="LabelFrame")
mock_tk.Scale.side_effect = lambda *_a, **_kw: MagicMock(name="Scale")
mock_tk.IntVar.side_effect = lambda *_a, **_kw: MagicMock(name="IntVar")
mock_tk.BooleanVar.side_effect = lambda *_a, **_kw: MagicMock(name="BooleanVar")
mock_tk.Checkbutton.side_effect = lambda *_a, **_kw: MagicMock(name="Checkbutton")
mock_tk.Button.side_effect = lambda *_a, **_kw: MagicMock(name="Button")
return {"tk": mock_tk, "ttk": mock_ttk}
def _make_mixin() -> Any:
def _make_mixin() -> tuple[PokerGuiMixin, MagicMock, MagicMock]:
"""Create a PokerGuiMixin instance with mocked tkinter."""
tk_mocks = _install_tk_mocks()
@ -99,7 +102,7 @@ class TestSetupMainWindow:
root.title.assert_called_once_with("🃏 Texas Hold'em Modifier")
root.geometry.assert_called_once_with("650x750")
root.configure.assert_called_once_with(bg="#0f4c3a")
root.resizable.assert_called_once_with(True, True)
root.resizable.assert_called_once_with(width=True, height=True)
mock_ttk.Style.assert_called_once()
mock_ttk.Style.return_value.theme_use.assert_called_once_with("clam")
@ -239,7 +242,7 @@ class TestCreateResultDisplay:
frame_calls = mock_tk.Frame.call_args_list
assert any(c[1].get("height") == 150 for c in frame_calls)
assert hasattr(mixin, "result_frame")
mixin.result_frame.pack_propagate.assert_called_once_with(False)
mixin.result_frame.pack_propagate.assert_called_once_with(flag=False)
# Result label
label_calls = mock_tk.Label.call_args_list

View File

@ -2,7 +2,7 @@
from __future__ import annotations
from typing import Any
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
from python_pkg.poker_modifier_app._poker_modifiers import (
@ -11,8 +11,11 @@ from python_pkg.poker_modifier_app._poker_modifiers import (
Modifier,
)
if TYPE_CHECKING:
from python_pkg.poker_modifier_app.poker_modifier_app import PokerModifierApp
def _make_app() -> Any:
def _make_app() -> PokerModifierApp:
"""Create a PokerModifierApp with setup_gui mocked out."""
with patch(
"python_pkg.poker_modifier_app.poker_modifier_app.PokerGuiMixin.setup_gui"
@ -422,7 +425,7 @@ class TestMainBlock:
"""Test the if __name__ == '__main__' block."""
@patch("python_pkg.poker_modifier_app.poker_modifier_app.PokerGuiMixin.setup_gui")
def test_main_block(self, _mock_setup: MagicMock) -> None:
def test_main_block(self, mock_setup: MagicMock) -> None:
with patch(
"python_pkg.poker_modifier_app.poker_modifier_app.PokerModifierApp.run"
):

View File

@ -221,14 +221,16 @@ def _transformer_seg_demo() -> list[CompositeVideoClip]:
(100, 480),
),
(
"Z\u0142o\u017cono\u015b\u0107: O(n\u00b2) pami\u0119ci \u2014 n = liczba pikseli/token\u00f3w",
"Z\u0142o\u017cono\u015b\u0107: O(n\u00b2) pami\u0119ci \u2014 n = liczba "
"pikseli/token\u00f3w",
16,
"#EF9A9A",
FONT_R,
(100, 535),
),
(
"Dlatego SegFormer u\u017cywa efficient attention (liniowa z\u0142o\u017cono\u015b\u0107)",
"Dlatego SegFormer u\u017cywa efficient attention (liniowa "
"z\u0142o\u017cono\u015b\u0107)",
15,
"#78909C",
FONT_R,
@ -269,14 +271,16 @@ def _transformer_seg_demo() -> list[CompositeVideoClip]:
(80, 90),
),
(
"Encoder: obraz \u2192 cechy (zmniejsza rozdzielczo\u015b\u0107, wyci\u0105ga CO)",
"Encoder: obraz \u2192 cechy (zmniejsza rozdzielczo\u015b\u0107, "
"wyci\u0105ga CO)",
16,
"#64B5F6",
FONT_R,
(100, 140),
),
(
"Decoder: cechy \u2192 mapa (zwi\u0119ksza rozdzielczo\u015b\u0107, odtwarza GDZIE)",
"Decoder: cechy \u2192 mapa (zwi\u0119ksza rozdzielczo\u015b\u0107, "
"odtwarza GDZIE)",
16,
"#A5D6A7",
FONT_R,
@ -334,7 +338,8 @@ def _transformer_seg_demo() -> list[CompositeVideoClip]:
(80, 465),
),
(
" CNN lokal. \u2192 dilated (szersze RF) \u2192 transformer (global) \u2192 masked att.",
" CNN lokal. \u2192 dilated (szersze RF) \u2192 transformer (global) "
"\u2192 masked att.",
16,
"#B0BEC5",
FONT_R,

View File

@ -2,7 +2,9 @@
from __future__ import annotations
import contextlib
import importlib
import importlib.util as _ilu
from pathlib import Path
import sys
from typing import TYPE_CHECKING
@ -12,6 +14,7 @@ import numpy as np
import pytest
if TYPE_CHECKING:
from collections.abc import Callable
from types import ModuleType
# Add the source directory to sys.path so bare imports like
@ -35,7 +38,11 @@ def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]:
moviepy_mod = MagicMock()
# VideoClip: needs to accept make_frame callable -> return mock with methods
def _video_clip_factory(make_frame=None, duration=None, **kw):
def _video_clip_factory(
make_frame: Callable[[float], np.ndarray] | None = None,
duration: float | None = None,
**_kw: object,
) -> MagicMock:
clip = MagicMock()
clip.make_frame = make_frame
clip.duration = duration
@ -57,14 +64,18 @@ def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]:
moviepy_mod.VideoClip = _video_clip_factory
def _color_clip_factory(size=None, color=None, **kw):
def _color_clip_factory(
_size: tuple[int, int] | None = None,
_color: tuple[int, ...] | None = None,
**_kw: object,
) -> MagicMock:
clip = MagicMock()
clip.with_duration.return_value = clip
return clip
moviepy_mod.ColorClip = _color_clip_factory
def _text_clip_factory(**kw):
def _text_clip_factory(**_kw: object) -> MagicMock:
clip = MagicMock()
clip.with_duration.return_value = clip
clip.with_position.return_value = clip
@ -72,7 +83,11 @@ def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]:
moviepy_mod.TextClip = _text_clip_factory
def _composite_factory(clips=None, size=None, **kw):
def _composite_factory(
_clips: list[MagicMock] | None = None,
_size: tuple[int, int] | None = None,
**_kw: object,
) -> MagicMock:
clip = MagicMock()
clip.with_effects.return_value = clip
clip.with_duration.return_value = clip
@ -81,7 +96,11 @@ def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]:
moviepy_mod.CompositeVideoClip = _composite_factory
def _concat_factory(clips=None, method=None, **kw):
def _concat_factory(
_clips: list[MagicMock] | None = None,
_method: str | None = None,
**_kw: object,
) -> MagicMock:
clip = MagicMock()
clip.write_videofile = MagicMock()
return clip
@ -117,7 +136,6 @@ for _name, _mock in _MOVIEPY_MOCKS.items():
# modules (``_q24_classical.py``, etc.) find ``BG_COLOR`` etc.
# 3. Register both under their full package paths for coverage.
# ---------------------------------------------------------------------------
import importlib.util as _ilu
# Load generate_images _q24_common first.
_gen_q24_spec = _ilu.spec_from_file_location(
@ -127,9 +145,9 @@ _gen_q24_spec = _ilu.spec_from_file_location(
assert _gen_q24_spec is not None
assert _gen_q24_spec.loader is not None
_q24_common_gen = _ilu.module_from_spec(_gen_q24_spec)
_gen_q24_spec.loader.exec_module(_q24_common_gen)
# Cache as bare name so generate_images imports work during _BARE_MODULES.
# Register BEFORE exec so @dataclass can resolve __module__ in Python 3.14+.
sys.modules["_q24_common"] = _q24_common_gen
_gen_q24_spec.loader.exec_module(_q24_common_gen)
# Load top-level _q24_common.
_top_q24_spec = _ilu.spec_from_file_location(
@ -139,6 +157,8 @@ _top_q24_spec = _ilu.spec_from_file_location(
assert _top_q24_spec is not None
assert _top_q24_spec.loader is not None
_q24_common_top = _ilu.module_from_spec(_top_q24_spec)
# Register BEFORE exec so @dataclass can resolve __module__ in Python 3.14+.
sys.modules["_q24_common_top"] = _q24_common_top
_top_q24_spec.loader.exec_module(_q24_common_top)
@ -205,11 +225,9 @@ _BARE_MODULES = [
"generate_scheduling_diagrams",
]
for _bare in _BARE_MODULES:
try:
with contextlib.suppress(ImportError):
_mod = importlib.import_module(_bare)
sys.modules.setdefault(f"{_GEN_PKG}.{_bare}", _mod)
except ImportError:
pass
# Now swap _q24_common to the top-level version so that top-level source
# modules (``_q24_classical.py`` etc.) find BG_COLOR, W, H, etc.
@ -242,6 +260,7 @@ def _no_savefig(monkeypatch: pytest.MonkeyPatch) -> None:
def _compat_auto_set_font_size(
self: matplotlib.table.Table,
*,
value: bool = True,
**_kw: object,
) -> None:

View File

@ -117,7 +117,7 @@ def test_get_file_metadata_no_match(tmp_path: Path) -> None:
p = tmp_path / "readme.txt"
p.write_text("No Przedmiot here", encoding="utf-8")
num, subject, content = get_file_metadata(str(p))
num, subject, _ = get_file_metadata(str(p))
assert num == "00"
assert subject == "Ogólne"
@ -386,7 +386,7 @@ def test_generate_anki_basic(tmp_path: Path) -> None:
out_dir.mkdir()
with (
patch(f"{_PKG}.Path.__truediv__", side_effect=lambda self, x: tmp_path / x),
patch(f"{_PKG}.Path.__truediv__", side_effect=lambda _self, x: tmp_path / x),
patch(
f"{_PKG}.generate_anki.__defaults__",
(False, False, False),

View File

@ -14,6 +14,27 @@ mpl.use("Agg")
import matplotlib.pyplot as plt
import pytest
from python_pkg.praca_magisterska_video.generate_images._agent_cognitive import (
draw_bdi_model,
draw_behavior_tree,
)
from python_pkg.praca_magisterska_video.generate_images._agent_reactive import (
draw_3t_architecture,
draw_see_think_act,
)
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
BG,
DPI,
GRAY5,
OUTPUT_DIR,
ArrowCfg,
BoxStyle,
DashedArrowCfg,
draw_arrow,
draw_box,
draw_dashed_arrow,
)
pytestmark = pytest.mark.usefixtures("_no_savefig")
_MOD = "python_pkg.praca_magisterska_video.generate_images"
@ -26,79 +47,41 @@ class TestAgentHelpers:
"""Test draw_box, draw_arrow, draw_dashed_arrow and dataclasses."""
def test_draw_box_rounded(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
BoxStyle,
draw_box,
)
fig, ax = plt.subplots()
draw_box(ax, (0, 0), (1, 1), "hi", BoxStyle(rounded=True))
plt.close(fig)
def test_draw_box_not_rounded(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
BoxStyle,
draw_box,
)
fig, ax = plt.subplots()
draw_box(ax, (0, 0), (1, 1), "hi", BoxStyle(rounded=False))
plt.close(fig)
def test_draw_box_no_style(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
draw_box,
)
fig, ax = plt.subplots()
draw_box(ax, (0, 0), (1, 1), "hi")
plt.close(fig)
def test_draw_arrow_with_label(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
ArrowCfg,
draw_arrow,
)
fig, ax = plt.subplots()
draw_arrow(ax, (0, 0), (1, 1), ArrowCfg(label="lbl"))
plt.close(fig)
def test_draw_arrow_no_label(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
draw_arrow,
)
fig, ax = plt.subplots()
draw_arrow(ax, (0, 0), (1, 1))
plt.close(fig)
def test_draw_dashed_arrow_with_label(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
DashedArrowCfg,
draw_dashed_arrow,
)
fig, ax = plt.subplots()
draw_dashed_arrow(ax, (0, 0), (1, 1), DashedArrowCfg(label="lbl"))
plt.close(fig)
def test_draw_dashed_arrow_no_label(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
draw_dashed_arrow,
)
fig, ax = plt.subplots()
draw_dashed_arrow(ax, (0, 0), (1, 1))
plt.close(fig)
def test_dataclass_defaults(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
ArrowCfg,
BoxStyle,
DashedArrowCfg,
)
bs = BoxStyle()
assert bs.rounded is True
assert bs.fill == "white"
@ -108,13 +91,6 @@ class TestAgentHelpers:
assert dc.label == ""
def test_module_constants(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
BG,
DPI,
GRAY5,
OUTPUT_DIR,
)
assert DPI == 300
assert BG == "white"
assert isinstance(GRAY5, str)
@ -128,17 +104,9 @@ class TestAgentReactive:
"""Test draw_see_think_act and draw_3t_architecture."""
def test_draw_see_think_act(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._agent_reactive import (
draw_see_think_act,
)
draw_see_think_act()
def test_draw_3t_architecture(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._agent_reactive import (
draw_3t_architecture,
)
draw_3t_architecture()
@ -149,15 +117,7 @@ class TestAgentCognitive:
"""Test draw_behavior_tree (covers all node types) and draw_bdi_model."""
def test_draw_behavior_tree(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._agent_cognitive import (
draw_behavior_tree,
)
draw_behavior_tree()
def test_draw_bdi_model(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._agent_cognitive import (
draw_bdi_model,
)
draw_bdi_model()

View File

@ -122,7 +122,7 @@ class TestAnkiApproach1:
patch.object(
Path,
"open",
side_effect=lambda *a, **kw: StringIO(fake_md),
side_effect=lambda *_a, **_kw: StringIO(fake_md),
),
):
main()
@ -187,7 +187,7 @@ class TestAnkiApproach1:
patch.object(
Path,
"open",
side_effect=lambda *a, **kw: StringIO(fake_md),
side_effect=lambda *_a, **_kw: StringIO(fake_md),
),
):
main()
@ -324,7 +324,7 @@ class TestAnkiApproach2:
patch.object(
Path,
"open",
side_effect=lambda *a, **kw: StringIO(fake_md),
side_effect=lambda *_a, **_kw: StringIO(fake_md),
),
):
main()
@ -387,7 +387,7 @@ class TestAnkiApproach2:
patch.object(
Path,
"open",
side_effect=lambda *a, **kw: StringIO(fake_md),
side_effect=lambda *_a, **_kw: StringIO(fake_md),
),
):
main()

View File

@ -17,137 +17,9 @@ mpl.use("Agg")
import matplotlib.pyplot as plt
import pytest
pytestmark = pytest.mark.usefixtures("_no_savefig")
# ── _automata_common helpers ───────────────────────────────────────────
class TestAutomataCommon:
"""Test draw_state_circle, draw_curved_arrow, draw_self_loop."""
def test_state_circle_basic(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
draw_state_circle,
from python_pkg.praca_magisterska_video.generate_images import (
generate_automata_diagrams as _auto_diags,
)
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_state_circle(ax, (0, 0), 0.3, "q0")
plt.close(fig)
def test_state_circle_accepting(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
StateStyle,
draw_state_circle,
)
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_state_circle(ax, (0, 0), 0.3, "q1", StateStyle(accepting=True))
plt.close(fig)
def test_state_circle_initial(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
StateStyle,
draw_state_circle,
)
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_state_circle(ax, (0, 0), 0.3, "q0", StateStyle(initial=True))
plt.close(fig)
def test_state_circle_both(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
StateStyle,
draw_state_circle,
)
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_state_circle(
ax, (0, 0), 0.3, "q", StateStyle(accepting=True, initial=True)
)
plt.close(fig)
def test_curved_arrow(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
draw_curved_arrow,
)
fig, ax = plt.subplots()
draw_curved_arrow(ax, (0, 0), (1, 1), "a")
plt.close(fig)
def test_self_loop_top(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
LoopStyle,
draw_self_loop,
)
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_self_loop(ax, (0, 0), 0.3, "a", LoopStyle(direction="top"))
plt.close(fig)
def test_self_loop_bottom(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
LoopStyle,
draw_self_loop,
)
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_self_loop(ax, (0, 0), 0.3, "b", LoopStyle(direction="bottom"))
plt.close(fig)
def test_self_loop_default(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
draw_self_loop,
)
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_self_loop(ax, (0, 0), 0.3, "c")
plt.close(fig)
def test_self_loop_unknown_direction(self) -> None:
"""Cover implicit else when direction is not top/bottom."""
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
LoopStyle,
draw_self_loop,
)
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_self_loop(ax, (0, 0), 0.3, "x", LoopStyle(direction="left"))
plt.close(fig)
def test_dataclass_defaults(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
ArrowStyle,
LoopStyle,
StateStyle,
)
ss = StateStyle()
assert ss.accepting is False
assert ss.initial is False
a = ArrowStyle()
assert a.fontsize > 0
ls = LoopStyle()
assert ls.direction == "top"
def test_module_constants(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
BG,
DPI,
@ -166,8 +38,109 @@ class TestAutomataCommon:
LIGHT_YELLOW,
LN,
OUTPUT_DIR,
ArrowStyle,
LoopStyle,
StateStyle,
draw_curved_arrow,
draw_self_loop,
draw_state_circle,
)
from python_pkg.praca_magisterska_video.generate_images._automata_fa import (
draw_fa_recognition,
)
from python_pkg.praca_magisterska_video.generate_images._automata_lba import (
draw_lba_recognition,
)
from python_pkg.praca_magisterska_video.generate_images._automata_pda import (
draw_pda_recognition,
)
from python_pkg.praca_magisterska_video.generate_images._automata_tm import (
draw_tm_recognition,
)
pytestmark = pytest.mark.usefixtures("_no_savefig")
# ── _automata_common helpers ───────────────────────────────────────────
class TestAutomataCommon:
"""Test draw_state_circle, draw_curved_arrow, draw_self_loop."""
def test_state_circle_basic(self) -> None:
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_state_circle(ax, (0, 0), 0.3, "q0")
plt.close(fig)
def test_state_circle_accepting(self) -> None:
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_state_circle(ax, (0, 0), 0.3, "q1", StateStyle(accepting=True))
plt.close(fig)
def test_state_circle_initial(self) -> None:
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_state_circle(ax, (0, 0), 0.3, "q0", StateStyle(initial=True))
plt.close(fig)
def test_state_circle_both(self) -> None:
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_state_circle(
ax, (0, 0), 0.3, "q", StateStyle(accepting=True, initial=True)
)
plt.close(fig)
def test_curved_arrow(self) -> None:
fig, ax = plt.subplots()
draw_curved_arrow(ax, (0, 0), (1, 1), "a")
plt.close(fig)
def test_self_loop_top(self) -> None:
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_self_loop(ax, (0, 0), 0.3, "a", LoopStyle(direction="top"))
plt.close(fig)
def test_self_loop_bottom(self) -> None:
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_self_loop(ax, (0, 0), 0.3, "b", LoopStyle(direction="bottom"))
plt.close(fig)
def test_self_loop_default(self) -> None:
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_self_loop(ax, (0, 0), 0.3, "c")
plt.close(fig)
def test_self_loop_unknown_direction(self) -> None:
"""Cover implicit else when direction is not top/bottom."""
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
draw_self_loop(ax, (0, 0), 0.3, "x", LoopStyle(direction="left"))
plt.close(fig)
def test_dataclass_defaults(self) -> None:
ss = StateStyle()
assert ss.accepting is False
assert ss.initial is False
a = ArrowStyle()
assert a.fontsize > 0
ls = LoopStyle()
assert ls.direction == "top"
def test_module_constants(self) -> None:
assert DPI == 300
assert BG == "white"
assert isinstance(FS, int | float)
@ -194,31 +167,15 @@ class TestAutomataDiagrams:
"""Test all recognition diagram functions."""
def test_fa_recognition(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_fa import (
draw_fa_recognition,
)
draw_fa_recognition()
def test_pda_recognition(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_pda import (
draw_pda_recognition,
)
draw_pda_recognition()
def test_lba_recognition(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_lba import (
draw_lba_recognition,
)
draw_lba_recognition()
def test_tm_recognition(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._automata_tm import (
draw_tm_recognition,
)
draw_tm_recognition()
@ -229,15 +186,9 @@ class TestAutomataEntry:
"""Verify generate_automata_diagrams exports are accessible."""
def test_all_exports(self) -> None:
import python_pkg.praca_magisterska_video.generate_images.generate_automata_diagrams as mod
assert hasattr(mod, "__all__")
for name in mod.__all__:
assert hasattr(mod, name)
assert hasattr(_auto_diags, "__all__")
for name in _auto_diags.__all__:
assert hasattr(_auto_diags, name)
def test_output_dir(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_automata_diagrams import (
OUTPUT_DIR,
)
assert isinstance(OUTPUT_DIR, str)
assert isinstance(_auto_diags.OUTPUT_DIR, str)

View File

@ -13,6 +13,15 @@ mpl.use("Agg")
import matplotlib.pyplot as plt
import pytest
from python_pkg.praca_magisterska_video.generate_images import (
generate_bf_negative_diagram as _bf_neg,
)
from python_pkg.praca_magisterska_video.generate_images._bf_negative_diagrams import (
_add_annotation_box,
generate_bf_negative_cycle,
generate_bf_negative_weights,
)
pytestmark = pytest.mark.usefixtures("_no_savefig")
_MOD = "python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram"
@ -25,157 +34,100 @@ class TestBFHelpers:
"""Test draw_node, _choose_edge_style, draw_edge, draw_neg_graph."""
def test_draw_node_default(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
draw_node,
)
fig, ax = plt.subplots()
ax.set_xlim(-1, 5)
ax.set_ylim(-1, 5)
draw_node(ax, "S", (1, 1))
_bf_neg.draw_node(ax, "S", (1, 1))
plt.close(fig)
def test_draw_node_current(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
draw_node,
)
fig, ax = plt.subplots()
ax.set_xlim(-1, 5)
ax.set_ylim(-1, 5)
draw_node(ax, "A", (1, 1), current=True, dist_label="2")
_bf_neg.draw_node(ax, "A", (1, 1), current=True, dist_label="2")
plt.close(fig)
def test_draw_node_visited(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
draw_node,
)
fig, ax = plt.subplots()
ax.set_xlim(-1, 5)
ax.set_ylim(-1, 5)
draw_node(ax, "B", (1, 1), visited=True, dist_label="5")
_bf_neg.draw_node(ax, "B", (1, 1), visited=True, dist_label="5")
plt.close(fig)
def test_draw_node_error(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
draw_node,
)
fig, ax = plt.subplots()
ax.set_xlim(-1, 5)
ax.set_ylim(-1, 5)
draw_node(ax, "C", (1, 1), error=True, dist_label="?")
_bf_neg.draw_node(ax, "C", (1, 1), error=True, dist_label="?")
plt.close(fig)
def test_draw_node_no_dist_label(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
draw_node,
)
fig, ax = plt.subplots()
ax.set_xlim(-1, 5)
ax.set_ylim(-1, 5)
draw_node(ax, "X", (1, 1), visited=True)
_bf_neg.draw_node(ax, "X", (1, 1), visited=True)
plt.close(fig)
def test_choose_edge_style_cycle(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
_choose_edge_style,
)
color, lw, ls = _choose_edge_style(
_, lw, ls = _bf_neg._choose_edge_style(
negative=False, relaxed=False, highlighted=False, cycle_edge=True
)
assert ls == "--"
assert lw == 2.5
def test_choose_edge_style_negative(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
_choose_edge_style,
)
color, lw, ls = _choose_edge_style(
_, lw, ls = _bf_neg._choose_edge_style(
negative=True, relaxed=False, highlighted=False, cycle_edge=False
)
assert lw == 2.5
assert ls == "-"
def test_choose_edge_style_relaxed(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
_choose_edge_style,
)
color, lw, ls = _choose_edge_style(
_, lw, _ = _bf_neg._choose_edge_style(
negative=False, relaxed=True, highlighted=False, cycle_edge=False
)
assert lw == 2.5
def test_choose_edge_style_highlighted(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
_choose_edge_style,
)
color, lw, ls = _choose_edge_style(
color, _, ls = _bf_neg._choose_edge_style(
negative=False, relaxed=False, highlighted=True, cycle_edge=False
)
assert ls == "-"
assert color == "#1565C0"
def test_choose_edge_style_default(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
GRAY3,
_choose_edge_style,
)
color, lw, ls = _choose_edge_style(
color, lw, _ = _bf_neg._choose_edge_style(
negative=False, relaxed=False, highlighted=False, cycle_edge=False
)
assert color == GRAY3
assert color == _bf_neg.GRAY3
assert lw == 1.5
def test_draw_edge_no_offset(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
draw_edge,
)
fig, ax = plt.subplots()
ax.set_xlim(-1, 5)
ax.set_ylim(-1, 5)
draw_edge(ax, (0, 0), (2, 2), 3)
_bf_neg.draw_edge(ax, (0, 0), (2, 2), 3)
plt.close(fig)
def test_draw_edge_with_offset(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
draw_edge,
)
fig, ax = plt.subplots()
ax.set_xlim(-1, 5)
ax.set_ylim(-1, 5)
draw_edge(ax, (0, 0), (2, 2), -3, negative=True, offset=0.3)
_bf_neg.draw_edge(ax, (0, 0), (2, 2), -3, negative=True, offset=0.3)
plt.close(fig)
def test_draw_edge_highlighted(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
draw_edge,
)
fig, ax = plt.subplots()
ax.set_xlim(-1, 5)
ax.set_ylim(-1, 5)
draw_edge(ax, (0, 0), (2, 2), 5, highlighted=True)
_bf_neg.draw_edge(ax, (0, 0), (2, 2), 5, highlighted=True)
plt.close(fig)
def test_draw_edge_cycle(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
draw_edge,
)
fig, ax = plt.subplots()
ax.set_xlim(-1, 5)
ax.set_ylim(-1, 5)
draw_edge(ax, (0, 0), (2, 2), -2, cycle_edge=True)
_bf_neg.draw_edge(ax, (0, 0), (2, 2), -2, cycle_edge=True)
plt.close(fig)
@ -184,36 +136,20 @@ class TestDrawNegGraph:
def test_minimal(self) -> None:
"""All-defaults: visited, relaxed, dist, error_nodes all None."""
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
NEG_EDGES,
draw_neg_graph,
)
fig, ax = plt.subplots()
draw_neg_graph(ax, NEG_EDGES)
_bf_neg.draw_neg_graph(ax, _bf_neg.NEG_EDGES)
plt.close(fig)
def test_with_title(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
NEG_EDGES,
draw_neg_graph,
)
fig, ax = plt.subplots()
draw_neg_graph(ax, NEG_EDGES, title="Test")
_bf_neg.draw_neg_graph(ax, _bf_neg.NEG_EDGES, title="Test")
plt.close(fig)
def test_with_all_options(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
NEG_EDGES,
NEG_POS,
draw_neg_graph,
)
fig, ax = plt.subplots()
draw_neg_graph(
_bf_neg.draw_neg_graph(
ax,
NEG_EDGES,
_bf_neg.NEG_EDGES,
title="Full",
dist={"S": "0", "A": "1", "B": "5", "C": "4"},
current="S",
@ -221,19 +157,15 @@ class TestDrawNegGraph:
relaxed_edges={("S", "A")},
error_nodes={"C"},
extra_edges=[("C", "B", -3)],
node_positions=NEG_POS,
node_positions=_bf_neg.NEG_POS,
)
plt.close(fig)
def test_explicit_node_positions(self) -> None:
"""Cover node_positions is not None branch."""
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
draw_neg_graph,
)
pos = {"X": (1.0, 1.0), "Y": (3.0, 1.0)}
fig, ax = plt.subplots()
draw_neg_graph(
_bf_neg.draw_neg_graph(
ax,
[("X", "Y", 2)],
node_positions=pos,
@ -250,24 +182,12 @@ class TestBFDiagramFunctions:
"""Test the main diagram generation functions."""
def test_generate_bf_negative_weights(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._bf_negative_diagrams import (
generate_bf_negative_weights,
)
generate_bf_negative_weights()
def test_generate_bf_negative_cycle(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._bf_negative_diagrams import (
generate_bf_negative_cycle,
)
generate_bf_negative_cycle()
def test_add_annotation_box(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._bf_negative_diagrams import (
_add_annotation_box,
)
fig, ax = plt.subplots()
_add_annotation_box(ax, 1, 1, "test", color="red", bg_color="white")
plt.close(fig)
@ -277,40 +197,20 @@ class TestBFModuleConstants:
"""Verify module-level constants."""
def test_constants(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
BG,
DPI,
FS,
FS_EDGE,
FS_SMALL,
FS_TITLE,
GRAY1,
GRAY2,
GRAY3,
GRAY4,
LIGHT_GREEN,
LIGHT_RED,
LIGHT_YELLOW,
LN,
NEG_EDGES,
NEG_POS,
OUTPUT_DIR,
)
assert DPI == 300
assert BG == "white"
assert isinstance(FS, int | float)
assert isinstance(FS_EDGE, int | float)
assert isinstance(FS_SMALL, int | float)
assert isinstance(FS_TITLE, int | float)
assert isinstance(GRAY1, str)
assert isinstance(GRAY2, str)
assert isinstance(GRAY3, str)
assert isinstance(GRAY4, str)
assert isinstance(LIGHT_GREEN, str)
assert isinstance(LIGHT_RED, str)
assert isinstance(LIGHT_YELLOW, str)
assert isinstance(LN, str)
assert isinstance(OUTPUT_DIR, str)
assert len(NEG_EDGES) > 0
assert len(NEG_POS) > 0
assert _bf_neg.DPI == 300
assert _bf_neg.BG == "white"
assert isinstance(_bf_neg.FS, int | float)
assert isinstance(_bf_neg.FS_EDGE, int | float)
assert isinstance(_bf_neg.FS_SMALL, int | float)
assert isinstance(_bf_neg.FS_TITLE, int | float)
assert isinstance(_bf_neg.GRAY1, str)
assert isinstance(_bf_neg.GRAY2, str)
assert isinstance(_bf_neg.GRAY3, str)
assert isinstance(_bf_neg.GRAY4, str)
assert isinstance(_bf_neg.LIGHT_GREEN, str)
assert isinstance(_bf_neg.LIGHT_RED, str)
assert isinstance(_bf_neg.LIGHT_YELLOW, str)
assert isinstance(_bf_neg.LN, str)
assert isinstance(_bf_neg.OUTPUT_DIR, str)
assert len(_bf_neg.NEG_EDGES) > 0
assert len(_bf_neg.NEG_POS) > 0

View File

@ -17,6 +17,24 @@ mpl.use("Agg")
import matplotlib.pyplot as plt
import pytest
from python_pkg.praca_magisterska_video.generate_images import (
generate_normalization_diagrams as _norm_mod,
)
from python_pkg.praca_magisterska_video.generate_images._norm_advanced import (
draw_3nf,
draw_4nf,
draw_bcnf,
)
from python_pkg.praca_magisterska_video.generate_images._norm_basic import (
draw_0nf,
draw_1nf,
draw_2nf,
)
from python_pkg.praca_magisterska_video.generate_images._norm_higher import (
draw_5nf,
draw_summary_flow,
)
pytestmark = pytest.mark.usefixtures("_no_savefig")
_GEN = (
@ -34,51 +52,28 @@ class TestNormHelpers:
"""Test _compute_col_widths, draw_table, create_figure, add_arrow, add_label."""
def test_compute_col_widths_normal(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
_compute_col_widths,
)
result = _compute_col_widths(["Name", "Age"], [["Alice", "30"]])
result = _norm_mod._compute_col_widths(["Name", "Age"], [["Alice", "30"]])
assert len(result) == 2
assert all(w >= 0.5 for w in result)
def test_compute_col_widths_jagged(self) -> None:
"""Row shorter than headers → c < len(r) False branch."""
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
_compute_col_widths,
)
result = _compute_col_widths(["A", "B", "C"], [["x"]])
result = _norm_mod._compute_col_widths(["A", "B", "C"], [["x"]])
assert len(result) == 3
def test_draw_table_auto_widths(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
create_figure,
draw_table,
)
fig, ax = create_figure()
draw_table(ax, 0, 5, "T", ["A", "B"], [["1", "2"]])
fig, ax = _norm_mod.create_figure()
_norm_mod.draw_table(ax, 0, 5, "T", ["A", "B"], [["1", "2"]])
plt.close(fig)
def test_draw_table_explicit_widths(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
create_figure,
draw_table,
)
fig, ax = create_figure()
draw_table(ax, 0, 5, "T", ["A"], [["x"]], col_widths=[1.0])
fig, ax = _norm_mod.create_figure()
_norm_mod.draw_table(ax, 0, 5, "T", ["A"], [["x"]], col_widths=[1.0])
plt.close(fig)
def test_draw_table_highlight_cols(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
create_figure,
draw_table,
)
fig, ax = create_figure()
draw_table(
fig, ax = _norm_mod.create_figure()
_norm_mod.draw_table(
ax,
0,
5,
@ -90,13 +85,8 @@ class TestNormHelpers:
plt.close(fig)
def test_draw_table_highlight_rows(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
create_figure,
draw_table,
)
fig, ax = create_figure()
draw_table(
fig, ax = _norm_mod.create_figure()
_norm_mod.draw_table(
ax,
0,
5,
@ -108,13 +98,8 @@ class TestNormHelpers:
plt.close(fig)
def test_draw_table_highlight_cells(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
create_figure,
draw_table,
)
fig, ax = create_figure()
draw_table(
fig, ax = _norm_mod.create_figure()
_norm_mod.draw_table(
ax,
0,
5,
@ -126,13 +111,8 @@ class TestNormHelpers:
plt.close(fig)
def test_draw_table_strikethrough(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
create_figure,
draw_table,
)
fig, ax = create_figure()
draw_table(
fig, ax = _norm_mod.create_figure()
_norm_mod.draw_table(
ax,
0,
5,
@ -145,13 +125,8 @@ class TestNormHelpers:
def test_draw_table_all_options(self) -> None:
"""All highlight/strikethrough at once, with matching+non-matching cells."""
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
create_figure,
draw_table,
)
fig, ax = create_figure()
w, h = draw_table(
fig, ax = _norm_mod.create_figure()
w, h = _norm_mod.draw_table(
ax,
0,
5,
@ -169,65 +144,35 @@ class TestNormHelpers:
plt.close(fig)
def test_create_figure(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
create_figure,
)
fig, ax = create_figure(10, 8)
fig, ax = _norm_mod.create_figure(10, 8)
assert fig is not None
assert ax is not None
plt.close(fig)
def test_add_arrow_with_label(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
add_arrow,
create_figure,
)
fig, ax = create_figure()
add_arrow(ax, 0, 5, 3, 5, "lbl", color="black")
fig, ax = _norm_mod.create_figure()
_norm_mod.add_arrow(ax, 0, 5, 3, 5, "lbl", color="black")
plt.close(fig)
def test_add_arrow_no_label(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
add_arrow,
create_figure,
)
fig, ax = create_figure()
add_arrow(ax, 0, 5, 3, 5)
fig, ax = _norm_mod.create_figure()
_norm_mod.add_arrow(ax, 0, 5, 3, 5)
plt.close(fig)
def test_add_label(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
add_label,
create_figure,
)
fig, ax = create_figure()
add_label(ax, 0, 5, "note", fontsize=10, color="red")
fig, ax = _norm_mod.create_figure()
_norm_mod.add_label(ax, 0, 5, "note", fontsize=10, color="red")
plt.close(fig)
def test_module_constants(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
CELL_COLOR,
DPI,
FD_ARROW_COLOR,
FIXED_COLOR,
FONT_SIZE,
HEADER_COLOR,
HIGHLIGHT_COLOR,
OUTPUT_DIR,
)
assert DPI == 300
assert isinstance(OUTPUT_DIR, str)
assert isinstance(HEADER_COLOR, str)
assert isinstance(CELL_COLOR, str)
assert isinstance(HIGHLIGHT_COLOR, str)
assert isinstance(FIXED_COLOR, str)
assert isinstance(FD_ARROW_COLOR, str)
assert isinstance(FONT_SIZE, int | float)
assert _norm_mod.DPI == 300
assert isinstance(_norm_mod.OUTPUT_DIR, str)
assert isinstance(_norm_mod.HEADER_COLOR, str)
assert isinstance(_norm_mod.CELL_COLOR, str)
assert isinstance(_norm_mod.HIGHLIGHT_COLOR, str)
assert isinstance(_norm_mod.FIXED_COLOR, str)
assert isinstance(_norm_mod.FD_ARROW_COLOR, str)
assert isinstance(_norm_mod.FONT_SIZE, int | float)
# ── _norm_basic (draw_table has positional-arg signature mismatch) ─────
@ -243,29 +188,17 @@ class TestNormBasic:
@patch(f"{_BASIC}.add_arrow")
@patch(f"{_BASIC}.draw_table")
def test_draw_0nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
from python_pkg.praca_magisterska_video.generate_images._norm_basic import (
draw_0nf,
)
def test_draw_0nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
draw_0nf()
@patch(f"{_BASIC}.add_arrow")
@patch(f"{_BASIC}.draw_table")
def test_draw_1nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
from python_pkg.praca_magisterska_video.generate_images._norm_basic import (
draw_1nf,
)
def test_draw_1nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
draw_1nf()
@patch(f"{_BASIC}.add_arrow")
@patch(f"{_BASIC}.draw_table")
def test_draw_2nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
from python_pkg.praca_magisterska_video.generate_images._norm_basic import (
draw_2nf,
)
def test_draw_2nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
draw_2nf()
@ -277,29 +210,17 @@ class TestNormAdvanced:
@patch(f"{_ADV}.add_arrow")
@patch(f"{_ADV}.draw_table")
def test_draw_3nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
from python_pkg.praca_magisterska_video.generate_images._norm_advanced import (
draw_3nf,
)
def test_draw_3nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
draw_3nf()
@patch(f"{_ADV}.add_arrow")
@patch(f"{_ADV}.draw_table")
def test_draw_bcnf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
from python_pkg.praca_magisterska_video.generate_images._norm_advanced import (
draw_bcnf,
)
def test_draw_bcnf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
draw_bcnf()
@patch(f"{_ADV}.add_arrow")
@patch(f"{_ADV}.draw_table")
def test_draw_4nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
from python_pkg.praca_magisterska_video.generate_images._norm_advanced import (
draw_4nf,
)
def test_draw_4nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
draw_4nf()
@ -311,18 +232,10 @@ class TestNormHigher:
@patch(f"{_HIGH}.add_arrow")
@patch(f"{_HIGH}.draw_table")
def test_draw_5nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
from python_pkg.praca_magisterska_video.generate_images._norm_higher import (
draw_5nf,
)
def test_draw_5nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
draw_5nf()
@patch(f"{_HIGH}.add_arrow")
@patch(f"{_HIGH}.draw_table")
def test_draw_summary_flow(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
from python_pkg.praca_magisterska_video.generate_images._norm_higher import (
draw_summary_flow,
)
def test_draw_summary_flow(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
draw_summary_flow()

View File

@ -16,6 +16,19 @@ mpl.use("Agg")
import matplotlib.pyplot as plt
import pytest
from python_pkg.praca_magisterska_video.generate_images import (
_pattern_pillars_observer as _pat_pillars,
)
from python_pkg.praca_magisterska_video.generate_images import (
_pattern_template_catalog as _pat_tmpl,
)
from python_pkg.praca_magisterska_video.generate_images import (
generate_pattern_diagrams as _pat_diags,
)
from python_pkg.praca_magisterska_video.generate_images._pattern_navigation import (
generate_pattern_language_navigation,
)
pytestmark = pytest.mark.usefixtures("_no_savefig")
_GEN = "python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams"
@ -31,74 +44,47 @@ class TestPatternConstants:
"""Constants and module-level values."""
def test_dpi(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
DPI,
)
assert DPI == 300
assert _pat_diags.DPI == 300
def test_bg(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
BG,
)
assert BG == "white"
assert _pat_diags.BG == "white"
def test_gray_constants(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
GRAY1,
GRAY2,
GRAY3,
GRAY4,
GRAY5,
assert all(
isinstance(g, str)
for g in [
_pat_diags.GRAY1,
_pat_diags.GRAY2,
_pat_diags.GRAY3,
_pat_diags.GRAY4,
_pat_diags.GRAY5,
]
)
assert all(isinstance(g, str) for g in [GRAY1, GRAY2, GRAY3, GRAY4, GRAY5])
def test_band_heights(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
_BAND_HEIGHTS,
)
assert len(_BAND_HEIGHTS) == 5
assert all(isinstance(h, float) for h in _BAND_HEIGHTS)
assert len(_pat_diags._BAND_HEIGHTS) == 5
assert all(isinstance(h, float) for h in _pat_diags._BAND_HEIGHTS)
def test_output_dir_is_str(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
OUTPUT_DIR,
)
assert isinstance(OUTPUT_DIR, str)
assert isinstance(_pat_diags.OUTPUT_DIR, str)
class TestDrawBox:
"""Test draw_box helper."""
def test_rounded(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
draw_box,
)
fig, ax = plt.subplots()
draw_box(ax, 0, 0, 1, 1, "test", rounded=True)
_pat_diags.draw_box(ax, 0, 0, 1, 1, "test", rounded=True)
plt.close(fig)
def test_not_rounded(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
draw_box,
)
fig, ax = plt.subplots()
draw_box(ax, 0, 0, 1, 1, "test", rounded=False)
_pat_diags.draw_box(ax, 0, 0, 1, 1, "test", rounded=False)
plt.close(fig)
def test_custom_style(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
draw_box,
)
fig, ax = plt.subplots()
draw_box(
_pat_diags.draw_box(
ax,
0,
0,
@ -120,21 +106,13 @@ class TestDrawArrow:
"""Test draw_arrow helper."""
def test_default(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
draw_arrow,
)
fig, ax = plt.subplots()
draw_arrow(ax, 0, 0, 1, 1)
_pat_diags.draw_arrow(ax, 0, 0, 1, 1)
plt.close(fig)
def test_custom(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
draw_arrow,
)
fig, ax = plt.subplots()
draw_arrow(ax, 0, 0, 1, 1, lw=2.5, style="<->", color="red")
_pat_diags.draw_arrow(ax, 0, 0, 1, 1, lw=2.5, style="<->", color="red")
plt.close(fig)
@ -145,22 +123,14 @@ class TestPatternTemplate:
"""Test generate_pattern_template."""
def test_runs(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._pattern_template_catalog import (
generate_pattern_template,
)
generate_pattern_template()
_pat_tmpl.generate_pattern_template()
class TestCatalogMap:
"""Test generate_catalog_map."""
def test_runs(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._pattern_template_catalog import (
generate_catalog_map,
)
generate_catalog_map()
_pat_tmpl.generate_catalog_map()
# ── _pattern_pillars_observer ──────────────────────────────────────────
@ -170,34 +140,22 @@ class TestThreePillars:
"""Test generate_three_pillars."""
def test_runs(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._pattern_pillars_observer import (
generate_three_pillars,
)
generate_three_pillars()
_pat_pillars.generate_three_pillars()
class TestObserverCard:
"""Test generate_observer_card_filled."""
def test_runs(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._pattern_pillars_observer import (
generate_observer_card_filled,
)
generate_observer_card_filled()
_pat_pillars.generate_observer_card_filled()
class TestGetObserverBandHeight:
"""Test _get_observer_band_height."""
def test_all_indices(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._pattern_pillars_observer import (
_get_observer_band_height,
)
for i in range(5):
h = _get_observer_band_height(i)
h = _pat_pillars._get_observer_band_height(i)
assert isinstance(h, float)
assert h > 0
@ -209,8 +167,4 @@ class TestPatternLanguageNavigation:
"""Test generate_pattern_language_navigation."""
def test_runs(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._pattern_navigation import (
generate_pattern_language_navigation,
)
generate_pattern_language_navigation()

View File

@ -16,6 +16,36 @@ mpl.use("Agg")
import matplotlib.pyplot as plt
import pytest
from python_pkg.praca_magisterska_video.generate_images import (
generate_process_diagrams as _proc_diags,
)
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
_draw_bpmn_elements,
_draw_bpmn_legend,
_draw_bpmn_pool_and_lanes,
_draw_uml_elements,
_draw_uml_legend,
generate_bpmn,
generate_uml_activity,
)
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
_draw_epc_branches,
_draw_epc_connector,
_draw_epc_event,
_draw_epc_flow,
_draw_epc_function,
_draw_epc_legend,
generate_epc,
)
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
_draw_fc_elements,
_draw_fc_io_shape,
_draw_fc_legend,
_draw_fc_process_box,
_draw_fc_terminal,
generate_flowchart,
)
pytestmark = pytest.mark.usefixtures("_no_savefig")
_GEN = "python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams"
@ -31,37 +61,21 @@ class TestProcessConstants:
"""Constants and module-level values."""
def test_dpi(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
DPI,
)
assert DPI == 300
assert _proc_diags.DPI == 300
def test_bg_color(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
BG_COLOR,
)
assert BG_COLOR == "white"
assert _proc_diags.BG_COLOR == "white"
def test_output_dir(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
OUTPUT_DIR,
)
assert isinstance(OUTPUT_DIR, str)
assert isinstance(_proc_diags.OUTPUT_DIR, str)
class TestProcessDrawArrow:
"""Test draw_arrow helper."""
def test_default(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
draw_arrow,
)
fig, ax = plt.subplots()
draw_arrow(ax, 0, 0, 1, 1)
_proc_diags.draw_arrow(ax, 0, 0, 1, 1)
plt.close(fig)
@ -69,12 +83,8 @@ class TestProcessDrawLine:
"""Test draw_line helper."""
def test_default(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
draw_line,
)
fig, ax = plt.subplots()
draw_line(ax, 0, 0, 5, 5)
_proc_diags.draw_line(ax, 0, 0, 5, 5)
plt.close(fig)
@ -82,21 +92,15 @@ class TestProcessDrawRoundedRect:
"""Test draw_rounded_rect helper."""
def test_default(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
draw_rounded_rect,
)
fig, ax = plt.subplots()
draw_rounded_rect(ax, 5, 5, 10, 4, "Hello")
_proc_diags.draw_rounded_rect(ax, 5, 5, 10, 4, "Hello")
plt.close(fig)
def test_custom_params(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
draw_rounded_rect,
)
fig, ax = plt.subplots()
draw_rounded_rect(ax, 0, 0, 8, 3, "styled", fill="#CCC", lw=3, fontsize=12)
_proc_diags.draw_rounded_rect(
ax, 0, 0, 8, 3, "styled", fill="#CCC", lw=3, fontsize=12
)
plt.close(fig)
@ -104,30 +108,18 @@ class TestProcessDrawDiamond:
"""Test draw_diamond helper."""
def test_with_text(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
draw_diamond,
)
fig, ax = plt.subplots()
draw_diamond(ax, 5, 5, 3, "XOR")
_proc_diags.draw_diamond(ax, 5, 5, 3, "XOR")
plt.close(fig)
def test_without_text(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
draw_diamond,
)
fig, ax = plt.subplots()
draw_diamond(ax, 5, 5, 3)
_proc_diags.draw_diamond(ax, 5, 5, 3)
plt.close(fig)
def test_custom_fill(self) -> None:
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
draw_diamond,
)
fig, ax = plt.subplots()
draw_diamond(ax, 5, 5, 3, "Y", fill="#EEE", fontsize=12)
_proc_diags.draw_diamond(ax, 5, 5, 3, "Y", fill="#EEE", fontsize=12)
plt.close(fig)
@ -138,17 +130,9 @@ class TestBPMN:
"""Test generate_bpmn and its sub-helpers."""
def test_generate_bpmn(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
generate_bpmn,
)
generate_bpmn()
def test_draw_bpmn_pool_and_lanes(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
_draw_bpmn_pool_and_lanes,
)
fig, ax = plt.subplots()
ax.set_xlim(0, 110)
ax.set_ylim(0, 75)
@ -157,10 +141,6 @@ class TestBPMN:
plt.close(fig)
def test_draw_bpmn_elements(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
_draw_bpmn_elements,
)
fig, ax = plt.subplots()
ax.set_xlim(0, 110)
ax.set_ylim(0, 75)
@ -168,10 +148,6 @@ class TestBPMN:
plt.close(fig)
def test_draw_bpmn_legend(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
_draw_bpmn_legend,
)
fig, ax = plt.subplots()
ax.set_xlim(0, 110)
ax.set_ylim(0, 75)
@ -183,17 +159,9 @@ class TestUMLActivity:
"""Test generate_uml_activity and its sub-helpers."""
def test_generate_uml_activity(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
generate_uml_activity,
)
generate_uml_activity()
def test_draw_uml_elements(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
_draw_uml_elements,
)
fig, ax = plt.subplots()
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
@ -201,10 +169,6 @@ class TestUMLActivity:
plt.close(fig)
def test_draw_uml_legend(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
_draw_uml_legend,
)
fig, ax = plt.subplots()
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
@ -219,44 +183,24 @@ class TestEPC:
"""Test generate_epc and its sub-helpers."""
def test_generate_epc(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
generate_epc,
)
generate_epc()
def test_draw_epc_event(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
_draw_epc_event,
)
fig, ax = plt.subplots()
_draw_epc_event(ax, 50, 50, "test event")
plt.close(fig)
def test_draw_epc_function(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
_draw_epc_function,
)
fig, ax = plt.subplots()
_draw_epc_function(ax, 50, 50, "test function")
plt.close(fig)
def test_draw_epc_connector(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
_draw_epc_connector,
)
fig, ax = plt.subplots()
_draw_epc_connector(ax, 50, 50, "XOR")
plt.close(fig)
def test_draw_epc_flow(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
_draw_epc_flow,
)
fig, ax = plt.subplots()
ax.set_xlim(0, 100)
ax.set_ylim(0, 120)
@ -267,10 +211,6 @@ class TestEPC:
plt.close(fig)
def test_draw_epc_branches(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
_draw_epc_branches,
)
fig, ax = plt.subplots()
ax.set_xlim(0, 100)
ax.set_ylim(0, 120)
@ -278,10 +218,6 @@ class TestEPC:
plt.close(fig)
def test_draw_epc_legend(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
_draw_epc_legend,
)
fig, ax = plt.subplots()
ax.set_xlim(0, 100)
ax.set_ylim(0, 120)
@ -296,44 +232,24 @@ class TestFlowchart:
"""Test generate_flowchart and its sub-helpers."""
def test_generate_flowchart(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
generate_flowchart,
)
generate_flowchart()
def test_draw_fc_terminal(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
_draw_fc_terminal,
)
fig, ax = plt.subplots()
_draw_fc_terminal(ax, 50, 50, "START")
plt.close(fig)
def test_draw_fc_process_box(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
_draw_fc_process_box,
)
fig, ax = plt.subplots()
_draw_fc_process_box(ax, 50, 50, "Process")
plt.close(fig)
def test_draw_fc_io_shape(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
_draw_fc_io_shape,
)
fig, ax = plt.subplots()
_draw_fc_io_shape(ax, 50, 50, "I/O")
plt.close(fig)
def test_draw_fc_elements(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
_draw_fc_elements,
)
fig, ax = plt.subplots()
ax.set_xlim(0, 100)
ax.set_ylim(0, 110)
@ -341,10 +257,6 @@ class TestFlowchart:
plt.close(fig)
def test_draw_fc_legend(self) -> None:
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
_draw_fc_legend,
)
fig, ax = plt.subplots()
ax.set_xlim(0, 100)
ax.set_ylim(0, 110)

View File

@ -48,7 +48,7 @@ class TestSplitQuestions:
source_file = FakeFile(source_content)
written_files: dict[str, FakeFile] = {}
def fake_open(self_path: Path, *args: object, **kwargs: object) -> FakeFile:
def fake_open(self_path: Path, *_args: object, **_kwargs: object) -> FakeFile:
path_str = str(self_path)
if "OBRONA_MAGISTERSKA_ODPOWIEDZI" in path_str:
return source_file
@ -59,7 +59,7 @@ class TestSplitQuestions:
with (
patch.object(Path, "open", fake_open),
patch.object(Path, "mkdir", lambda *a, **kw: None),
patch.object(Path, "mkdir", lambda *_a, **_kw: None),
):
importlib.import_module(mod_name)

View File

@ -144,7 +144,7 @@ def test_get_file_metadata(sample_file: Path) -> None:
_get_file_metadata,
)
num, subject, content = _get_file_metadata(str(sample_file))
num, subject, _ = _get_file_metadata(str(sample_file))
assert num == "01"
assert subject == "Informatyka"
@ -157,7 +157,7 @@ def test_get_file_metadata_no_match(tmp_path: Path) -> None:
p = tmp_path / "readme.txt"
p.write_text("No Przedmiot", encoding="utf-8")
num, subject, content = _get_file_metadata(str(p))
num, subject, _ = _get_file_metadata(str(p))
assert num == "00"
assert subject == "Ogólne"

View File

@ -207,7 +207,7 @@ def test_body_parts_long_para_truncation() -> None:
# --- _extract_subsection_cards: empty parts / multiple parts ---
def test_subsection_empty_answer_parts(tmp_path: Path) -> None:
def test_subsection_empty_answer_parts() -> None:
"""Subsection where _extract_body_parts returns [] (182->173)."""
from python_pkg.praca_magisterska_video.generate_images.generate_anki_final import (
_extract_subsection_cards,
@ -403,7 +403,7 @@ def test_main_function(tmp_path: Path) -> None:
call_count = 0
def fake_extract(filepath: object) -> list[dict[str, str]]:
def fake_extract(_filepath: object) -> list[dict[str, str]]:
nonlocal call_count
call_count += 1
if call_count == 1:

View File

@ -79,7 +79,7 @@ def test_get_metadata(sample_file: Path) -> None:
_get_metadata,
)
num, topic, title, main_q, content = _get_metadata(str(sample_file))
num, topic, _, main_q, content = _get_metadata(str(sample_file))
assert num == "01"
assert "test" in topic
assert "main concept" in main_q
@ -92,7 +92,7 @@ def test_get_metadata_no_match(minimal_file: Path) -> None:
_get_metadata,
)
num, topic, title, main_q, content = _get_metadata(str(minimal_file))
num, topic, _, _, _ = _get_metadata(str(minimal_file))
assert num == "00"
assert topic == "unknown"
@ -104,7 +104,7 @@ def test_extract_main_card(sample_file: Path) -> None:
_get_metadata,
)
num, topic, title, main_q, content = _get_metadata(str(sample_file))
num, topic, _, main_q, content = _get_metadata(str(sample_file))
cards = _extract_main_card(content, main_q, "Informatyka", num, topic)
assert len(cards) == 1
assert "main concept" in cards[0]["question"]

View File

@ -115,7 +115,7 @@ def test_main_card_def_outside_length(def_length_file: Path) -> None:
_get_metadata,
)
num, topic, title, main_q, content = _get_metadata(str(def_length_file))
num, topic, _, main_q, content = _get_metadata(str(def_length_file))
cards = _extract_main_card(content, main_q, "Informatyka", num, topic)
assert isinstance(cards, list)
@ -135,7 +135,7 @@ def test_sub_cards_short_body(subsection_file: Path) -> None:
assert isinstance(cards, list)
def test_sub_cards_no_answer_text(tmp_path: Path) -> None:
def test_sub_cards_no_answer_text() -> None:
"""Subsection where _extract_subsection_answer returns None (line 145)."""
from python_pkg.praca_magisterska_video.generate_images.generate_anki import (
_extract_sub_cards,
@ -221,7 +221,7 @@ def test_main_function(tmp_path: Path) -> None:
call_count = 0
def fake_extract(filepath: object) -> list[dict[str, str]]:
def fake_extract(_filepath: object) -> list[dict[str, str]]:
nonlocal call_count
call_count += 1
if call_count == 1:

View File

@ -255,7 +255,7 @@ def test_main_error_branch(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> N
return real_path(out_file)
return real_path(s)
def failing_process(filepath: str) -> list[dict[str, str]]:
def failing_process(_filepath: str) -> list[dict[str, str]]:
msg = "test error"
raise ValueError(msg)

View File

@ -113,9 +113,11 @@ Some introductory text that is ignored completely.
- **Gamma**
"""
_PLAIN_BODY = """\
This is a plain first paragraph without any structured content and it is long enough to be captured by regex.
"""
_PLAIN_BODY = (
"This is a plain first paragraph without any"
" structured content and it is long enough"
" to be captured by regex.\n"
)
_PARA_ONLY_MD = """\
# Pytanie 03: Para Only
@ -251,7 +253,7 @@ def test_read_file_metadata_matching(sample_file: Path) -> None:
_read_file_metadata,
)
content, base_tags, main_question = _read_file_metadata(sample_file)
_, base_tags, main_question = _read_file_metadata(sample_file)
assert "pyt01" in base_tags
assert "Informatyka" in base_tags
assert main_question is not None
@ -266,7 +268,7 @@ def test_read_file_metadata_no_match(tmp_path: Path) -> None:
p = tmp_path / "readme.txt"
p.write_text(_MINIMAL_MD, encoding="utf-8")
content, base_tags, main_question = _read_file_metadata(p)
_, base_tags, main_question = _read_file_metadata(p)
assert "pyt00" in base_tags
assert "Og\u00f3lne" in base_tags
assert main_question is None

View File

@ -224,7 +224,7 @@ def test_main_error_branch(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> N
return real_path(out_file)
return real_path(s)
def failing_extract(filepath: object) -> list[dict[str, str]]:
def failing_extract(_filepath: object) -> list[dict[str, str]]:
msg = "test error"
raise ValueError(msg)

View File

@ -2,16 +2,24 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
import numpy as np
if TYPE_CHECKING:
from collections.abc import Callable
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
"""VideoClip spy capturing make_frame closures."""
captured: list[tuple[object, float]] = []
def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock:
def spy(
make_frame: Callable[[float], np.ndarray] | None = None,
duration: float | None = None,
**_kw: object,
) -> MagicMock:
if callable(make_frame):
captured.append((make_frame, duration or 1.0))
clip = MagicMock()

View File

@ -2,16 +2,24 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
import numpy as np
if TYPE_CHECKING:
from collections.abc import Callable
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
"""VideoClip spy capturing make_frame closures."""
captured: list[tuple[object, float]] = []
def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock:
def spy(
make_frame: Callable[[float], np.ndarray] | None = None,
duration: float | None = None,
**_kw: object,
) -> MagicMock:
if callable(make_frame):
captured.append((make_frame, duration or 1.0))
clip = MagicMock()

View File

@ -2,16 +2,24 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
import numpy as np
if TYPE_CHECKING:
from collections.abc import Callable
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
"""VideoClip spy capturing make_frame closures."""
captured: list[tuple[object, float]] = []
def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock:
def spy(
make_frame: Callable[[float], np.ndarray] | None = None,
duration: float | None = None,
**_kw: object,
) -> MagicMock:
if callable(make_frame):
captured.append((make_frame, duration or 1.0))
clip = MagicMock()

View File

@ -2,16 +2,24 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
import numpy as np
if TYPE_CHECKING:
from collections.abc import Callable
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
"""VideoClip spy capturing make_frame closures."""
captured: list[tuple[object, float]] = []
def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock:
def spy(
make_frame: Callable[[float], np.ndarray] | None = None,
duration: float | None = None,
**_kw: object,
) -> MagicMock:
if callable(make_frame):
captured.append((make_frame, duration or 1.0))
clip = MagicMock()

View File

@ -2,16 +2,24 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
import numpy as np
if TYPE_CHECKING:
from collections.abc import Callable
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
"""VideoClip spy capturing make_frame closures."""
captured: list[tuple[object, float]] = []
def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock:
def spy(
make_frame: Callable[[float], np.ndarray] | None = None,
duration: float | None = None,
**_kw: object,
) -> MagicMock:
if callable(make_frame):
captured.append((make_frame, duration or 1.0))
clip = MagicMock()

View File

@ -2,16 +2,24 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
import numpy as np
if TYPE_CHECKING:
from collections.abc import Callable
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
"""VideoClip spy capturing make_frame closures."""
captured: list[tuple[object, float]] = []
def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock:
def spy(
make_frame: Callable[[float], np.ndarray] | None = None,
duration: float | None = None,
**_kw: object,
) -> MagicMock:
if callable(make_frame):
captured.append((make_frame, duration or 1.0))
clip = MagicMock()

View File

@ -60,7 +60,7 @@ def test_make_frame_closure_returns_ndarray() -> None:
captured: list[object] = []
def capturing_video_clip(make_frame: object = None, **kw: object) -> MagicMock:
def capturing_video_clip(make_frame: object = None, **_kw: object) -> MagicMock:
captured.append(make_frame)
clip = MagicMock()
clip.with_fps.return_value = clip

View File

@ -18,6 +18,7 @@ Usage
from __future__ import annotations
import argparse
from collections import Counter
import json
from pathlib import Path
import sys
@ -70,8 +71,6 @@ def cmd_debug(args: argparse.Namespace) -> None:
data = parse_image(args.image, threshold=args.threshold)
out = args.output or args.image.rsplit(".", 1)[0] + "_debug.png"
draw_debug(args.image, data, out)
from collections import Counter
counts = Counter(sq["type"] for sq in data["squares"])
for _t, _n in counts.most_common():
pass

View File

@ -0,0 +1,9 @@
"""Mock cv2/numpy if not installed before puzzle_solver tests."""
from __future__ import annotations
import sys
from unittest.mock import MagicMock
sys.modules.setdefault("cv2", MagicMock())
sys.modules.setdefault("numpy", MagicMock())

View File

@ -3,16 +3,11 @@
from __future__ import annotations
import json
import sys
from typing import Any
from unittest.mock import MagicMock, mock_open, patch
import pytest
# Ensure cv2 and numpy are available as mocks before importing main
sys.modules.setdefault("cv2", MagicMock())
sys.modules.setdefault("numpy", MagicMock())
from python_pkg.puzzle_solver.main import (
cmd_debug,
cmd_parse,

View File

@ -2,17 +2,10 @@
from __future__ import annotations
import sys
from unittest.mock import MagicMock, mock_open, patch
import pytest
# Install mock modules before any parse_image imports
_cv2_mock = MagicMock()
_np_mock = MagicMock()
sys.modules.setdefault("cv2", _cv2_mock)
sys.modules.setdefault("numpy", _np_mock)
from python_pkg.puzzle_solver.parse_image import (
_classify_by_fill,
_classify_interior_feature,

View File

@ -2,16 +2,11 @@
from __future__ import annotations
import sys
from typing import Any
from unittest.mock import MagicMock, patch
import numpy as np
# Install mock modules before any parse_image imports
sys.modules.setdefault("cv2", MagicMock())
sys.modules.setdefault("numpy", MagicMock())
from python_pkg.puzzle_solver.parse_image import (
_assign_teleporter_and_kl_groups,
_build_output,

View File

@ -2,14 +2,9 @@
from __future__ import annotations
import sys
from typing import Any
from unittest.mock import MagicMock, patch
# Install mock modules before any parse_image imports
sys.modules.setdefault("cv2", MagicMock())
sys.modules.setdefault("numpy", MagicMock())
from python_pkg.puzzle_solver.parse_image import (
_assign_teleporter_and_kl_groups,
draw_debug,

View File

@ -3,7 +3,6 @@
from __future__ import annotations
from pathlib import Path, PurePosixPath
from typing import Any
from unittest.mock import MagicMock, patch
from python_pkg.repo_explorer._discovery import (
@ -173,7 +172,7 @@ class TestGetDescription:
readme.exists.return_value = True
readme.read_text.return_value = "# My Project\nDetails here"
def truediv(_self: Any, name: str) -> MagicMock:
def truediv(_self: object, name: str) -> MagicMock:
if name == "README.md":
return readme
m = MagicMock(spec=Path)
@ -187,7 +186,7 @@ class TestGetDescription:
def test_readme_txt(self) -> None:
mock_path = MagicMock(spec=Path)
def truediv(_self: Any, name: str) -> MagicMock:
def truediv(_self: object, name: str) -> MagicMock:
m = MagicMock(spec=Path)
if name == "README.txt":
m.exists.return_value = True
@ -203,7 +202,7 @@ class TestGetDescription:
def test_readme_lower(self) -> None:
mock_path = MagicMock(spec=Path)
def truediv(_self: Any, name: str) -> MagicMock:
def truediv(_self: object, name: str) -> MagicMock:
m = MagicMock(spec=Path)
if name == "readme.md":
m.exists.return_value = True
@ -220,7 +219,7 @@ class TestGetDescription:
"""README exists but all lines strip to empty."""
mock_path = MagicMock(spec=Path)
def truediv(_self: Any, name: str) -> MagicMock:
def truediv(_self: object, name: str) -> MagicMock:
m = MagicMock(spec=Path)
if name == "README.md":
m.exists.return_value = True
@ -243,7 +242,7 @@ class TestGetDescription:
run_sh = MagicMock(spec=Path)
run_sh.exists.return_value = True
def truediv(_self: Any, name: str) -> MagicMock:
def truediv(_self: object, name: str) -> MagicMock:
if name == "run.sh":
return run_sh
m = MagicMock(spec=Path)
@ -261,7 +260,7 @@ class TestGetDescription:
run_sh = MagicMock(spec=Path)
run_sh.exists.return_value = True
def truediv(_self: Any, name: str) -> MagicMock:
def truediv(_self: object, name: str) -> MagicMock:
if name == "run.sh":
return run_sh
m = MagicMock(spec=Path)
@ -275,7 +274,7 @@ class TestGetDescription:
def test_no_readme_no_run_sh(self) -> None:
mock_path = MagicMock(spec=Path)
def truediv(_self: Any, _name: str) -> MagicMock:
def truediv(_self: object, _name: str) -> MagicMock:
m = MagicMock(spec=Path)
m.exists.return_value = False
return m

View File

@ -10,6 +10,7 @@ from unittest.mock import MagicMock, patch
from python_pkg.repo_explorer._execution import ExecutionMixin
if TYPE_CHECKING:
from pathlib import Path
import subprocess
# ── Protocol stub coverage ───────────────────────────────────────────
@ -45,7 +46,7 @@ class StubExecution(ExecutionMixin):
self._path: Any = None
self._after_calls: list[tuple[Any, ...]] = []
def _selected_path(self) -> Any:
def _selected_path(self) -> Path | None:
return self._path
def after(self, ms: int, *args: object) -> str:
@ -338,7 +339,11 @@ class TestReadPty:
read_calls = [0]
def fake_select(rlist: list[int], *_a: Any, **_kw: Any) -> Any:
def fake_select(
_rlist: list[int],
*_a: object,
**_kw: object,
) -> tuple[list[int], list[object], list[object]]:
read_calls[0] += 1
if read_calls[0] == 1:
# First call: return data (no newline → stays in buf)
@ -393,7 +398,11 @@ class TestReadPty:
call_count = [0]
def fake_select(rlist: list[int], *_a: Any, **_kw: Any) -> Any:
def fake_select(
_rlist: list[int],
*_a: object,
**_kw: object,
) -> tuple[list[int], list[object], list[object]]:
call_count[0] += 1
if call_count[0] == 1:
return ([10], [], [])

View File

@ -10,6 +10,7 @@ from unittest.mock import MagicMock
from python_pkg.repo_explorer._execution import ExecutionMixin
if TYPE_CHECKING:
from pathlib import Path
import subprocess
@ -31,7 +32,7 @@ class StubExecution(ExecutionMixin):
self._path: Any = None
self._after_calls: list[tuple[Any, ...]] = []
def _selected_path(self) -> Any:
def _selected_path(self) -> Path | None:
return self._path
def after(self, ms: int, *args: object) -> str:

View File

@ -4,13 +4,12 @@ from __future__ import annotations
from pathlib import Path, PurePosixPath
import tkinter as tk
from typing import Any
from unittest.mock import MagicMock, patch
# ── Helper to create a RepoExplorer without a real display ───────────
def _make_explorer(**overrides: Any) -> Any:
def _make_explorer(**overrides: object) -> object:
"""Build a RepoExplorer instance without a real Tk display.
Mocks tk.Tk.__init__ and all GUI construction so no X server is needed.
@ -108,31 +107,26 @@ class TestBuildStyle:
class TestBuildUI:
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Scrollbar")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Treeview")
@patch("python_pkg.repo_explorer.repo_explorer.font.Font")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Button")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Entry")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Separator")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Label")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Frame")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.PanedWindow")
@patch("python_pkg.repo_explorer.repo_explorer.tk.Text")
@patch("python_pkg.repo_explorer.repo_explorer.tk.StringVar")
def test_build_ui_with_terminal(
self,
mock_stringvar: MagicMock,
mock_text: MagicMock,
mock_paned: MagicMock,
mock_frame: MagicMock,
mock_label: MagicMock,
mock_sep: MagicMock,
mock_entry: MagicMock,
mock_button: MagicMock,
mock_font: MagicMock,
mock_treeview: MagicMock,
mock_scrollbar: MagicMock,
) -> None:
def test_build_ui_with_terminal(self) -> None:
with (
patch(
"python_pkg.repo_explorer.repo_explorer.tk.StringVar"
) as mock_stringvar,
patch("python_pkg.repo_explorer.repo_explorer.tk.Text") as mock_text,
patch(
"python_pkg.repo_explorer.repo_explorer.ttk.PanedWindow"
) as mock_paned,
patch("python_pkg.repo_explorer.repo_explorer.ttk.Frame"),
patch("python_pkg.repo_explorer.repo_explorer.ttk.Label"),
patch("python_pkg.repo_explorer.repo_explorer.ttk.Separator"),
patch("python_pkg.repo_explorer.repo_explorer.ttk.Entry"),
patch("python_pkg.repo_explorer.repo_explorer.ttk.Button"),
patch("python_pkg.repo_explorer.repo_explorer.font.Font"),
patch(
"python_pkg.repo_explorer.repo_explorer.ttk.Treeview"
) as mock_treeview,
patch("python_pkg.repo_explorer.repo_explorer.ttk.Scrollbar"),
):
app = _make_explorer()
mock_sv = MagicMock()
mock_stringvar.return_value = mock_sv
@ -147,31 +141,26 @@ class TestBuildUI:
app.pack = MagicMock()
app._build_ui()
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Scrollbar")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Treeview")
@patch("python_pkg.repo_explorer.repo_explorer.font.Font")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Button")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Entry")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Separator")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Label")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Frame")
@patch("python_pkg.repo_explorer.repo_explorer.ttk.PanedWindow")
@patch("python_pkg.repo_explorer.repo_explorer.tk.Text")
@patch("python_pkg.repo_explorer.repo_explorer.tk.StringVar")
def test_build_ui_no_terminal(
self,
mock_stringvar: MagicMock,
mock_text: MagicMock,
mock_paned: MagicMock,
mock_frame: MagicMock,
mock_label: MagicMock,
mock_sep: MagicMock,
mock_entry: MagicMock,
mock_button: MagicMock,
mock_font: MagicMock,
mock_treeview: MagicMock,
mock_scrollbar: MagicMock,
) -> None:
def test_build_ui_no_terminal(self) -> None:
with (
patch(
"python_pkg.repo_explorer.repo_explorer.tk.StringVar"
) as mock_stringvar,
patch("python_pkg.repo_explorer.repo_explorer.tk.Text") as mock_text,
patch(
"python_pkg.repo_explorer.repo_explorer.ttk.PanedWindow"
) as mock_paned,
patch("python_pkg.repo_explorer.repo_explorer.ttk.Frame"),
patch("python_pkg.repo_explorer.repo_explorer.ttk.Label"),
patch("python_pkg.repo_explorer.repo_explorer.ttk.Separator"),
patch("python_pkg.repo_explorer.repo_explorer.ttk.Entry"),
patch("python_pkg.repo_explorer.repo_explorer.ttk.Button"),
patch("python_pkg.repo_explorer.repo_explorer.font.Font"),
patch(
"python_pkg.repo_explorer.repo_explorer.ttk.Treeview"
) as mock_treeview,
patch("python_pkg.repo_explorer.repo_explorer.ttk.Scrollbar"),
):
app = _make_explorer(terminal_args=[])
mock_sv = MagicMock()
mock_stringvar.return_value = mock_sv

View File

@ -196,7 +196,7 @@ def main(argv: Sequence[str] | None = None) -> int:
parser = _build_parser()
args = parser.parse_args(argv)
if not _trans._check_argos():
if not _trans.check_argos():
sys.stderr.write(
"Error: argostranslate is not installed.\n"
"Install it with: pip install argostranslate\n",

View File

@ -67,7 +67,7 @@ class ArgosAvailableMock:
translator, "_ensure_language_pair", lambda _f, _t: None
)
self._check_argos_patcher = patch.object(
translator, "_check_argos", return_value=True
translator, "check_argos", return_value=True
)
self._sys_modules_patcher.start()

View File

@ -15,9 +15,9 @@ from python_pkg.word_frequency import translator
@pytest.fixture
def _mock_argos_unavailable() -> Generator[None, None, None]:
def mock_argos_unavailable() -> Generator[None, None, None]:
"""Mock argostranslate being unavailable (for legacy tests)."""
with patch.object(translator, "_check_argos", return_value=False):
with patch.object(translator, "check_argos", return_value=False):
yield

View File

@ -28,8 +28,10 @@ class TestGetCacheDir:
"""Tests for get_cache_dir."""
def test_returns_default(self, tmp_path: Path) -> None:
with patch("python_pkg.word_frequency.cache.DEFAULT_CACHE_DIR", tmp_path):
with patch.dict("os.environ", {}, clear=False):
with (
patch("python_pkg.word_frequency.cache.DEFAULT_CACHE_DIR", tmp_path),
patch.dict("os.environ", {}, clear=False),
):
d = get_cache_dir()
assert d == tmp_path

View File

@ -142,14 +142,14 @@ class TestAnkiDeckCache:
cache = AnkiDeckCache(cache_dir=tmp_path)
fp = tmp_path / "text.txt"
fp.write_text("hello", encoding="utf-8")
dk = AnkiDeckKey(fp, 10, "es", False, True)
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
assert cache.get(dk) is None
def test_get_hash_mismatch(self, tmp_path: Path) -> None:
cache = AnkiDeckCache(cache_dir=tmp_path)
fp = tmp_path / "text.txt"
fp.write_text("hello", encoding="utf-8")
dk = AnkiDeckKey(fp, 10, "es", False, True)
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
cache.set(dk, "content", "hello", 1, 1)
# Modify file to change hash
fp.write_text("changed content", encoding="utf-8")
@ -160,7 +160,7 @@ class TestAnkiDeckCache:
cache = AnkiDeckCache(cache_dir=tmp_path)
fp = tmp_path / "text.txt"
fp.write_text("hello", encoding="utf-8")
dk = AnkiDeckKey(fp, 10, "es", False, True)
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
cache.set(dk, "content", "hello", 1, 1)
# Tamper with stored hash in metadata
m = cache._load_metadata()
@ -174,7 +174,7 @@ class TestAnkiDeckCache:
cache = AnkiDeckCache(cache_dir=tmp_path)
fp = tmp_path / "text.txt"
fp.write_text("hello", encoding="utf-8")
dk = AnkiDeckKey(fp, 10, "es", False, True)
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
cache.set(dk, "content", "hello", 1, 1)
# Remove all .txt files in cache dir
for f in cache.cache_dir.glob("*.txt"):
@ -185,7 +185,7 @@ class TestAnkiDeckCache:
cache = AnkiDeckCache(cache_dir=tmp_path)
fp = tmp_path / "text.txt"
fp.write_text("hello", encoding="utf-8")
dk = AnkiDeckKey(fp, 10, "es", False, True)
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
cache.set(dk, "content", "hello", 1, 1)
# Mock read_text to raise OSError
with patch("pathlib.Path.read_text", side_effect=OSError("read error")):
@ -212,7 +212,7 @@ class TestAnkiDeckCache:
cache = AnkiDeckCache(cache_dir=tmp_path)
fp = tmp_path / "text.txt"
fp.write_text("hello", encoding="utf-8")
dk = AnkiDeckKey(fp, 10, "es", False, True)
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
cache.set(dk, "content", "hello", 1, 1)
cache.clear()
assert cache.get(dk) is None
@ -226,7 +226,7 @@ class TestAnkiDeckCache:
cache = AnkiDeckCache(cache_dir=tmp_path)
fp = tmp_path / "text.txt"
fp.write_text("hello", encoding="utf-8")
dk = AnkiDeckKey(fp, 10, "es", False, True)
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
cache.set(dk, "content", "hello", 1, 1)
stats = cache.stats()
assert stats["total_entries"] == 1

View File

@ -118,19 +118,19 @@ class TestCaching:
mock.return_value.set.assert_called_once()
def test_get_cached_deck_force(self) -> None:
key = AnkiDeckKey(Path("x"), 10, "es", False, True)
key = AnkiDeckKey(Path("x"), 10, "es", include_context=False, all_vocab=True)
result = get_cached_deck(key, force=True)
assert result is None
def test_get_cached_deck_delegates(self) -> None:
key = AnkiDeckKey(Path("x"), 10, "es", False, True)
key = AnkiDeckKey(Path("x"), 10, "es", include_context=False, all_vocab=True)
with patch("python_pkg.word_frequency._generation.get_anki_deck_cache") as mock:
mock.return_value.get.return_value = ("c", "e", 2, 5)
result = get_cached_deck(key)
assert result == ("c", "e", 2, 5)
def test_cache_deck_delegates(self) -> None:
key = AnkiDeckKey(Path("x"), 10, "es", False, True)
key = AnkiDeckKey(Path("x"), 10, "es", include_context=False, all_vocab=True)
with patch("python_pkg.word_frequency._generation.get_anki_deck_cache") as mock:
cache_deck(key, "content", "excerpt", 2, 5)
mock.return_value.set.assert_called_once()
@ -215,7 +215,7 @@ VOCAB_DUMP_END
"python_pkg.word_frequency._generation.get_anki_deck_cache"
) as mock_cache,
):
content, excerpt, num_words, max_rank = generate_flashcards(
content, excerpt, _, _ = generate_flashcards(
fp,
5,
FlashcardOptions(source_lang="en"),
@ -331,7 +331,7 @@ VOCAB_DUMP_END
),
patch("python_pkg.word_frequency._generation.get_anki_deck_cache"),
):
content, excerpt, num_words, max_rank = generate_flashcards(
content, _, _, _ = generate_flashcards(
fp, 5, FlashcardOptions(source_lang=None, no_translate=True)
)
assert content == "deck"

View File

@ -3,7 +3,6 @@
from __future__ import annotations
from collections import Counter
from typing import Any
from unittest.mock import patch
from python_pkg.word_frequency._learning_batch import (
@ -106,8 +105,8 @@ class TestGenerateBatchSectionWithTranslation:
def fake_batch(
words: list[str],
from_lang: Any,
to_lang: Any,
_from_lang: str | None,
_to_lang: str | None,
) -> list[TranslationResult]:
return [
TranslationResult(

View File

@ -29,7 +29,7 @@ if TYPE_CHECKING:
@pytest.fixture
def _mock_translation() -> Generator[MagicMock, None, None]:
def mock_translation() -> Generator[MagicMock, None, None]:
"""Mock translation to avoid requiring argostranslate."""
def fake_batch_translate(
@ -262,7 +262,7 @@ class TestMain:
"""Tests for main CLI function."""
def test_basic_text_input(
self, caplog: pytest.LogCaptureFixture, _mock_translation: None
self, caplog: pytest.LogCaptureFixture, mock_translation: None
) -> None:
"""Test with text input."""
with caplog.at_level(logging.INFO):
@ -280,7 +280,7 @@ class TestMain:
assert "LANGUAGE LEARNING LESSON" in caplog.text
def test_file_input(
self, tmp_path: Path, caplog: pytest.LogCaptureFixture, _mock_translation: None
self, tmp_path: Path, caplog: pytest.LogCaptureFixture, mock_translation: None
) -> None:
"""Test with file input."""
test_file = tmp_path / "test.txt"
@ -300,7 +300,7 @@ class TestMain:
assert exit_code == 0
assert "hello" in caplog.text.lower()
def test_output_to_file(self, tmp_path: Path, _mock_translation: None) -> None:
def test_output_to_file(self, tmp_path: Path, mock_translation: None) -> None:
"""Test outputting to file."""
output_file = tmp_path / "lesson.txt"
@ -319,7 +319,7 @@ class TestMain:
content = output_file.read_text(encoding="utf-8")
assert "LANGUAGE LEARNING LESSON" in content
def test_custom_stopwords(self, tmp_path: Path, _mock_translation: None) -> None:
def test_custom_stopwords(self, tmp_path: Path, mock_translation: None) -> None:
"""Test with custom stopwords file."""
stopwords_file = tmp_path / "stop.txt"
stopwords_file.write_text("hello\n", encoding="utf-8")
@ -340,7 +340,7 @@ class TestMain:
# "hello" should be filtered by custom stopwords
def test_multiple_batches_option(
self, caplog: pytest.LogCaptureFixture, _mock_translation: None
self, caplog: pytest.LogCaptureFixture, mock_translation: None
) -> None:
"""Test --batches option."""
text = " ".join(f"word{i}" * (50 - i) for i in range(30))
@ -385,7 +385,7 @@ class TestMain:
assert exit_code == 1
def test_output_to_file_branch(
self, tmp_path: Path, _mock_translation: None
self, tmp_path: Path, mock_translation: None
) -> None:
"""Test --output to verify the file writing path."""
out = tmp_path / "out.txt"

View File

@ -2,7 +2,6 @@
from __future__ import annotations
from typing import Any
from unittest.mock import patch
from python_pkg.word_frequency._learning_constants import LessonConfig
@ -19,8 +18,8 @@ class TestDoTranslateBranch:
def fake_batch(
words: list[str],
from_lang: Any,
to_lang: Any,
_from_lang: str | None,
_to_lang: str | None,
) -> list[TranslationResult]:
return [
TranslationResult(
@ -56,8 +55,8 @@ class TestDoTranslateBranch:
def fake_batch(
words: list[str],
from_lang: Any,
to_lang: Any,
from_lang: str | None,
to_lang: str | None,
) -> list[TranslationResult]:
return [
TranslationResult(

View File

@ -109,7 +109,7 @@ VOCAB_DUMP_END
Excerpt:
"hello world foo"
"""
excerpt, length, max_rank, vocab = parse_inverse_mode_output(output)
_, length, max_rank, _ = parse_inverse_mode_output(output)
assert length == 3
assert max_rank == 0
@ -122,17 +122,17 @@ Excerpt:
def test_short_longest_excerpt_line(self) -> None:
output = "LONGEST EXCERPT: 0"
excerpt, length, max_rank, vocab = parse_inverse_mode_output(output)
_, length, _, _ = parse_inverse_mode_output(output)
assert length == 0
def test_too_few_parts_in_longest_excerpt(self) -> None:
output = "LONGEST EXCERPT:"
excerpt, length, max_rank, vocab = parse_inverse_mode_output(output)
_, length, _, _ = parse_inverse_mode_output(output)
assert length == 0
def test_rarest_word_without_hash_number(self) -> None:
output = "Rarest word used: unknown"
excerpt, length, max_rank, vocab = parse_inverse_mode_output(output)
_, _, max_rank, _ = parse_inverse_mode_output(output)
assert max_rank == 0
@ -165,7 +165,7 @@ class TestParseTargetLengthBlock:
"[Length 3] Vocab needed: 2",
" Words: hello(#1)",
]
excerpt, words = _parse_target_length_block(lines, 3)
excerpt, _ = _parse_target_length_block(lines, 3)
assert excerpt == ""
def test_no_words_line(self) -> None:

View File

@ -195,7 +195,7 @@ class TestTranslateWordsBatch:
mock_parent.package = mock_package_module
with (
patch.object(translator, "_check_argos", return_value=True),
patch.object(translator, "check_argos", return_value=True),
patch.object(translator, "argostranslate", mock_parent, create=True),
patch.dict(
"sys.modules",

View File

@ -158,7 +158,7 @@ class TestHandleTranslation:
_cli._trans,
"translate_words_batch",
return_value=[
TranslationResult("hello", "hola", "en", "es", True),
TranslationResult("hello", "hola", "en", "es", success=True),
],
),
patch.object(
@ -195,7 +195,7 @@ class TestHandleTranslation:
_cli._trans,
"translate_words_batch",
return_value=[
TranslationResult("hello", "hola", "en", "es", True),
TranslationResult("hello", "hola", "en", "es", success=True),
],
),
patch.object(
@ -219,8 +219,15 @@ class TestHandleTranslation:
_cli._trans,
"translate_words_batch",
return_value=[
TranslationResult("hello", "hola", "en", "es", True),
TranslationResult("xyz", "", "en", "es", False, "error"),
TranslationResult("hello", "hola", "en", "es", success=True),
TranslationResult(
"xyz",
"",
"en",
"es",
success=False,
error="error",
),
],
),
patch.object(
@ -237,7 +244,7 @@ class TestMain:
"""Tests for main entry point."""
def test_argos_not_available(self, capsys: pytest.CaptureFixture[str]) -> None:
with patch.object(_cli._trans, "_check_argos", return_value=False):
with patch.object(_cli._trans, "check_argos", return_value=False):
result = main(["--text", "hello", "--from", "en", "--to", "es"])
assert result == 1
captured = capsys.readouterr()
@ -245,7 +252,7 @@ class TestMain:
def test_list_languages(self) -> None:
with (
patch.object(_cli._trans, "_check_argos", return_value=True),
patch.object(_cli._trans, "check_argos", return_value=True),
patch.object(
_cli._trans,
"get_installed_languages",
@ -257,7 +264,7 @@ class TestMain:
def test_list_available(self) -> None:
with (
patch.object(_cli._trans, "_check_argos", return_value=True),
patch.object(_cli._trans, "check_argos", return_value=True),
patch.object(_cli._trans, "get_available_packages", return_value=[]),
):
result = main(["--list-available"])
@ -265,7 +272,7 @@ class TestMain:
def test_download(self, capsys: pytest.CaptureFixture[str]) -> None:
with (
patch.object(_cli._trans, "_check_argos", return_value=True),
patch.object(_cli._trans, "check_argos", return_value=True),
patch.object(
_cli._trans,
"download_languages",
@ -276,7 +283,7 @@ class TestMain:
assert result == 0
def test_no_input_shows_help(self) -> None:
with patch.object(_cli._trans, "_check_argos", return_value=True):
with patch.object(_cli._trans, "check_argos", return_value=True):
result = main([])
assert result == 1
@ -284,7 +291,7 @@ class TestMain:
self, capsys: pytest.CaptureFixture[str]
) -> None:
with (
patch.object(_cli._trans, "_check_argos", return_value=True),
patch.object(_cli._trans, "check_argos", return_value=True),
patch.object(
_cli._trans,
"read_file",

View File

@ -3,6 +3,7 @@
from __future__ import annotations
import importlib
import tempfile
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
@ -235,7 +236,7 @@ class TestEnsureLanguagePair:
mock_pkg = MagicMock()
mock_pkg.from_code = "en"
mock_pkg.to_code = "es"
mock_pkg.download.return_value = "/tmp/pkg.argosmodel"
mock_pkg.download.return_value = tempfile.gettempdir() + "/pkg.argosmodel"
mock_argos = MagicMock()
mock_argos.translate.get_installed_languages.return_value = [
mock_from,
@ -262,7 +263,7 @@ class TestEnsureLanguagePair:
mock_pkg = MagicMock()
mock_pkg.from_code = "en"
mock_pkg.to_code = "es"
mock_pkg.download.return_value = "/tmp/pkg"
mock_pkg.download.return_value = tempfile.gettempdir() + "/pkg"
mock_argos = MagicMock()
mock_argos.translate.get_installed_languages.return_value = [mock_to]
mock_argos.package.get_available_packages.return_value = [mock_pkg]
@ -275,14 +276,14 @@ class TestFormatTranslations:
def test_failed_with_no_error(self) -> None:
results = [
TranslationResult("xyz", "", "en", "es", False),
TranslationResult("xyz", "", "en", "es", success=False),
]
output = format_translations(results)
assert "[Failed]" in output
def test_all_failed_max_trans(self) -> None:
results = [
TranslationResult("xyz", "", "en", "es", False, "err"),
TranslationResult("xyz", "", "en", "es", success=False, error="err"),
]
output = format_translations(results)
assert "Translation" in output

View File

@ -2,6 +2,7 @@
from __future__ import annotations
import tempfile
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
@ -30,7 +31,7 @@ if TYPE_CHECKING:
class TestGetInstalledLanguages:
"""Tests for get_installed_languages function."""
def test_argos_unavailable(self, _mock_argos_unavailable: None) -> None:
def test_argos_unavailable(self, mock_argos_unavailable: None) -> None:
"""Test when argos is unavailable."""
result = get_installed_languages()
assert result == []
@ -56,7 +57,7 @@ class TestGetInstalledLanguages:
mock_parent.package = mock_package_module
with (
patch.object(translator, "_check_argos", return_value=True),
patch.object(translator, "check_argos", return_value=True),
patch.object(translator, "argostranslate", mock_parent, create=True),
patch.dict(
"sys.modules",
@ -79,7 +80,7 @@ class TestGetInstalledLanguages:
class TestGetAvailablePackages:
"""Tests for get_available_packages function."""
def test_argos_unavailable(self, _mock_argos_unavailable: None) -> None:
def test_argos_unavailable(self, mock_argos_unavailable: None) -> None:
"""Test when argos is unavailable."""
result = get_available_packages()
assert result == []
@ -91,7 +92,7 @@ class TestGetAvailablePackages:
class TestDownloadLanguages:
"""Tests for download_languages function."""
def test_argos_unavailable(self, _mock_argos_unavailable: None) -> None:
def test_argos_unavailable(self, mock_argos_unavailable: None) -> None:
"""Test when argos is unavailable."""
result = download_languages(["en", "es"])
assert result == {}
@ -124,7 +125,7 @@ class TestReadFile:
class TestMain:
"""Tests for main CLI function."""
def test_argos_unavailable_error(self, _mock_argos_unavailable: None) -> None:
def test_argos_unavailable_error(self, mock_argos_unavailable: None) -> None:
"""Test error when argos not installed."""
result = main(["--text", "hello", "--from", "en", "--to", "es"])
assert result == 1
@ -139,7 +140,7 @@ class TestMain:
mock_parent.package = mock_package_module
with (
patch.object(translator, "_check_argos", return_value=True),
patch.object(translator, "check_argos", return_value=True),
patch.object(translator, "argostranslate", mock_parent, create=True),
patch.dict(
"sys.modules",
@ -172,7 +173,7 @@ class TestMain:
mock_parent.package = mock_package_module
with (
patch.object(translator, "_check_argos", return_value=True),
patch.object(translator, "check_argos", return_value=True),
patch.object(translator, "argostranslate", mock_parent, create=True),
patch.dict(
"sys.modules",
@ -326,7 +327,7 @@ class TestGetAvailablePackagesWithArgos:
mock_parent.translate = mock_translate
with (
patch.object(translator, "_check_argos", return_value=True),
patch.object(translator, "check_argos", return_value=True),
patch.object(translator, "argostranslate", mock_parent, create=True),
patch.dict(
"sys.modules",
@ -348,7 +349,7 @@ class TestDownloadLanguagesFull:
pkg = MagicMock()
pkg.from_code = "en"
pkg.to_code = "es"
pkg.download.return_value = "/tmp/fake.argosmodel"
pkg.download.return_value = tempfile.gettempdir() + "/fake.argosmodel"
mock_package = MagicMock()
mock_package.update_package_index.return_value = None
@ -359,7 +360,7 @@ class TestDownloadLanguagesFull:
mock_parent.translate = mock_translate
with (
patch.object(translator, "_check_argos", return_value=True),
patch.object(translator, "check_argos", return_value=True),
patch.object(translator, "argostranslate", mock_parent, create=True),
patch.dict(
"sys.modules",
@ -384,7 +385,7 @@ class TestDownloadLanguagesFull:
mock_parent.translate = mock_translate
with (
patch.object(translator, "_check_argos", return_value=True),
patch.object(translator, "check_argos", return_value=True),
patch.object(translator, "argostranslate", mock_parent, create=True),
patch.dict(
"sys.modules",
@ -414,7 +415,7 @@ class TestDownloadLanguagesFull:
mock_parent.translate = mock_translate
with (
patch.object(translator, "_check_argos", return_value=True),
patch.object(translator, "check_argos", return_value=True),
patch.object(translator, "argostranslate", mock_parent, create=True),
patch.dict(
"sys.modules",

View File

@ -65,7 +65,7 @@ logger = logging.getLogger(__name__)
_BATCH_SIZE = 100
def _check_argos() -> bool:
def check_argos() -> bool:
"""Check if argostranslate is available."""
return argostranslate is not None
@ -76,7 +76,7 @@ def get_installed_languages() -> list[tuple[str, str]]:
Returns:
List of (code, name) tuples for installed languages.
"""
if not _check_argos():
if not check_argos():
return []
languages = argostranslate.translate.get_installed_languages()
@ -89,7 +89,7 @@ def get_available_packages() -> list[tuple[str, str, str, str]]:
Returns:
List of (from_code, from_name, to_code, to_name) tuples.
"""
if not _check_argos():
if not check_argos():
return []
argostranslate.package.update_package_index()
@ -111,7 +111,7 @@ def download_languages(lang_codes: Sequence[str]) -> dict[str, bool]:
Returns:
Dict mapping "from->to" to success boolean.
"""
if not _check_argos():
if not check_argos():
return {}
results: dict[str, bool] = {}