fix: address 3 quality review findings — record_frigate_files_batch cache mutation, tracker_ok flag, LIMIT guard

- record_frigate_files_batch: copy-before-mutate so a write failure
  doesn't leave cache ahead of disk (same fix as remove_frigate_files_batch)
- executor: replace tracker_ok boolean with try/else
- jobs: collapse duplicate custom_limit is not None checks into one guard

Bump version to 0.6.3.
This commit is contained in:
2026-06-16 18:13:36 +00:00
parent e8cb390fe4
commit 14f759e960
5 changed files with 19 additions and 9 deletions
+10
View File
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.6.3] - 2026-06-16
### Fixed
- **`record_frigate_files_batch` no longer mutates the tracker cache before write**: the function shared the same cache-corruption-on-write-failure bug that was fixed in `remove_frigate_files_batch` in v0.6.1 — `data.setdefault("by_person", {})` mutated the cached dict in-place, so a disk-full or permission error left the in-memory cache ahead of the on-disk file. Now uses the same copy-before-mutate pattern (shallow copies of the top-level dict and `by_person` sub-dict) so a failed write leaves cache and disk in sync.
- **`tracker_ok` boolean flag replaced with try/else**: the intermediate boolean was a misleading placeholder — the `True` initial value suggested success before the operation ran. The control flow is now expressed directly with a try/except/else block.
- **`LIMIT` env var guard simplified**: the two adjacent `if custom_limit is not None` checks in `_resolve_strategy` are collapsed into a single `if custom_limit is not None:` with nested branches, removing redundant evaluation.
## [0.6.2] - 2026-06-16 ## [0.6.2] - 2026-06-16
### Changed ### Changed
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "winnow" name = "winnow"
version = "0.6.2" version = "0.6.3"
description = "Selects diverse, high-quality photos from Immich as training data for Frigate face recognition." description = "Selects diverse, high-quality photos from Immich as training data for Frigate face recognition."
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
requires-python = ">=3.13" requires-python = ">=3.13"
+1 -3
View File
@@ -510,7 +510,6 @@ def upload_to_frigate(jobs: list[dict]) -> None:
asset_id = asset_map.get(fname) asset_id = asset_map.get(fname)
if asset_id: if asset_id:
tracker_ok = True
try: try:
mark_uploaded( mark_uploaded(
asset_id, asset_id,
@@ -520,7 +519,6 @@ def upload_to_frigate(jobs: list[dict]) -> None:
frigate_score=pre_fscore, frigate_score=pre_fscore,
) )
except Exception as tracker_exc: except Exception as tracker_exc:
tracker_ok = False
# Upload to Frigate succeeded — don't retry on tracker # Upload to Frigate succeeded — don't retry on tracker
# failure or we'd upload a duplicate to Frigate. # failure or we'd upload a duplicate to Frigate.
logger.error( logger.error(
@@ -528,7 +526,7 @@ def upload_to_frigate(jobs: list[dict]) -> None:
" but asset may be re-selected next run: %s", " but asset may be re-selected next run: %s",
fname, tracker_exc, fname, tracker_exc,
) )
if tracker_ok: else:
if pre_fscore is not None: if pre_fscore is not None:
person_has_fscores = True person_has_fscores = True
actually_uploaded.append((fname, asset_id)) actually_uploaded.append((fname, asset_id))
+2 -2
View File
@@ -69,9 +69,9 @@ def _resolve_strategy(strategy: str, has_embedding: bool) -> tuple[int | str, st
return _getenv_int("LIMIT", 30), "time" return _getenv_int("LIMIT", 30), "time"
custom_limit = _getenv_optional_int("LIMIT") custom_limit = _getenv_optional_int("LIMIT")
if custom_limit is not None and custom_limit > 0: if custom_limit is not None:
if custom_limit > 0:
return custom_limit, "smart" return custom_limit, "smart"
if custom_limit is not None and custom_limit <= 0:
logger.warning("LIMIT=%s is invalid — ignoring and using auto strategy", custom_limit) logger.warning("LIMIT=%s is invalid — ignoring and using auto strategy", custom_limit)
strategy_map = { strategy_map = {
+4 -2
View File
@@ -223,11 +223,13 @@ def record_frigate_files_batch(person_name: str, mappings: dict[str, str]) -> No
"""Record multiple Frigate filename → asset_id mappings in a single load/save.""" """Record multiple Frigate filename → asset_id mappings in a single load/save."""
if not mappings: if not mappings:
return return
data = _load(UPLOAD_TRACKER_FILE) src = _load(UPLOAD_TRACKER_FILE)
by_person = data.setdefault("by_person", {}) by_person = dict(src.get("by_person", {}))
entry = _migrate_entry(by_person.get(person_name, {})) entry = _migrate_entry(by_person.get(person_name, {}))
entry["frigate_files"].update(mappings) entry["frigate_files"].update(mappings)
by_person[person_name] = entry by_person[person_name] = entry
data = dict(src)
data["by_person"] = by_person
_save(UPLOAD_TRACKER_FILE, data) _save(UPLOAD_TRACKER_FILE, data)
logger.debug(f"Batch-mapped {len(mappings)} Frigate file(s) for {person_name}") logger.debug(f"Batch-mapped {len(mappings)} Frigate file(s) for {person_name}")