fix: resolve all linting errors, add tests, bump base image

Linting (ruff):
- cli.py: sort relative imports (I001)
- jobs.py: remove unused get_people import (F401), wrap long line (E501)
- executor.py: remove unused success variable (F841), wrap 4 long lines (E501)

Tests (24 passing):
- tests/test_config.py: config singleton defaults + env var overrides
- tests/test_upload_tracker.py: mark/filter/reset/summary logic
- tests/test_immich_api.py: filter_recent_assets date boundary cases
- tests/test_jobs.py: _resolve_strategy with LIMIT env var and fallbacks
- pyproject.toml: add [tool.pytest.ini_options] testpaths=["tests"] so
  pytest doesn't scan .venv in CI

Security:
- Dockerfile: bump CUDA base from 12.6.3 to 12.9.2 to pick up patched
  Ubuntu packages (fixes Dependabot low-severity alert)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 00:42:38 +00:00
co-authored by Claude Sonnet 4.6
parent c861d56a53
commit eedbd3180a
9 changed files with 303 additions and 12 deletions
+2 -2
View File
@@ -7,11 +7,11 @@ from rich import print as rprint
from rich.prompt import Confirm
from .config import Config, ConfigManager
from .executor import execute_jobs, upload_to_frigate
from .immich_api import get_people
from .jobs import _show_preview, auto_configure, interactive_configure
from .logging import console, setup_logging
from .upload_tracker import get_person_summary, reset_person
from .executor import execute_jobs, upload_to_frigate
from .jobs import auto_configure, interactive_configure, _show_preview
logger = logging.getLogger(__name__)
+17 -7
View File
@@ -242,8 +242,6 @@ def upload_to_frigate(jobs: list[dict]) -> None:
for fname in person_files:
fpath = os.path.join(person_dir, fname)
success = False
for attempt in range(1, max_retries + 1):
try:
with open(fpath, "rb") as f:
@@ -255,7 +253,6 @@ def upload_to_frigate(jobs: list[dict]) -> None:
if resp.status_code == 200:
uploaded += 1
person_uploaded += 1
success = True
# Mark this asset as uploaded so it's skipped on future runs
asset_id = asset_map.get(fname)
@@ -265,7 +262,10 @@ def upload_to_frigate(jobs: list[dict]) -> None:
break
else:
if attempt < max_retries:
logger.warning(f"Upload attempt {attempt}/{max_retries} for {fname}: HTTP {resp.status_code}, retrying...")
logger.warning(
f"Upload attempt {attempt}/{max_retries} for {fname}:"
f" HTTP {resp.status_code}, retrying..."
)
continue
failed += 1
person_failed += 1
@@ -284,17 +284,27 @@ def upload_to_frigate(jobs: list[dict]) -> None:
mark_rejected(asset_id, person_name=name)
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as exc:
if attempt < max_retries:
logger.warning(f"Upload attempt {attempt}/{max_retries} for {fname}: {type(exc).__name__}, retrying...")
logger.warning(
f"Upload attempt {attempt}/{max_retries} for {fname}:"
f" {type(exc).__name__}, retrying..."
)
continue
failed += 1
person_failed += 1
label = "Connection refused" if isinstance(exc, requests.exceptions.ConnectionError) else "Request timed out (30s)"
label = (
"Connection refused"
if isinstance(exc, requests.exceptions.ConnectionError)
else "Request timed out (30s)"
)
progress.console.print(
f" [red]✗ {fname}: {label} (after {max_retries} attempts)[/red]"
)
except Exception as e:
if attempt < max_retries:
logger.warning(f"Upload attempt {attempt}/{max_retries} for {fname}: {type(e).__name__}, retrying...")
logger.warning(
f"Upload attempt {attempt}/{max_retries} for {fname}:"
f" {type(e).__name__}, retrying..."
)
continue
failed += 1
person_failed += 1
+5 -2
View File
@@ -11,7 +11,7 @@ from rich.table import Table
from .config import Config
from .diversity import select_diverse_assets
from .embeddings import is_embedding_available, load_embedding_model
from .immich_api import fetch_all_assets, filter_recent_assets, get_people
from .immich_api import fetch_all_assets, filter_recent_assets
from .logging import console
from .upload_tracker import filter_already_uploaded
@@ -231,7 +231,10 @@ def auto_configure(people: list[dict]) -> list[dict]:
if min_face_count > 0:
valid_people = [p for p in valid_people if p.get("assetCount", 0) >= min_face_count]
if valid_people:
rprint(f" Filtered to {len(valid_people)} people with ≥{min_face_count} assets (MIN_FACE_COUNT={min_face_count})")
rprint(
f" Filtered to {len(valid_people)} people with"
f" ≥{min_face_count} assets (MIN_FACE_COUNT={min_face_count})"
)
jobs = []
for person in valid_people: