release: v0.5.14 — graceful fallback for invalid numeric env vars
YEARS_FILTER, MIN_FACE_WIDTH, MIN_FACE_COUNT, MAX_AUTO_IMAGES, BLUR_THRESHOLD, MIN_CONFIDENCE, and FACE_MARGIN used bare int()/float() calls with no error handler. A typo (trailing space, non-numeric value) raised ValueError inside __getattr__, producing a cryptic traceback on the first config access rather than at the validate() step. Values are now parsed by _getenv_int/_getenv_float helpers that warn and fall back to the documented default, matching the existing FRIGATE_SCORE_CEILING pattern.
This commit is contained in:
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.5.14] - 2026-06-15
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Invalid env var values for numeric config now warn and use defaults**: `YEARS_FILTER`, `MIN_FACE_WIDTH`, `MIN_FACE_COUNT`, `MAX_AUTO_IMAGES`, `BLUR_THRESHOLD`, `MIN_CONFIDENCE`, and `FACE_MARGIN` all used bare `int()`/`float()` with no error handler. A typo such as `YEARS_FILTER=10 ` (trailing space) or `MIN_FACE_WIDTH=auto` raised `ValueError` from inside `__getattr__`, surfacing as a cryptic traceback on the first config access rather than at the config-validation step where a helpful error is expected. The values are now parsed with module-level `_getenv_int` / `_getenv_float` helpers that log a `WARNING` and fall back to the documented default on parse failure, matching the existing pattern already used for `FRIGATE_SCORE_CEILING`.
|
||||
|
||||
## [0.5.13] - 2026-06-15
|
||||
|
||||
### Fixed
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "winnow"
|
||||
version = "0.5.13"
|
||||
version = "0.5.14"
|
||||
description = "Selects diverse, high-quality photos from Immich as training data for Frigate face recognition."
|
||||
license = "AGPL-3.0-or-later"
|
||||
requires-python = ">=3.13"
|
||||
|
||||
+25
-7
@@ -12,6 +12,24 @@ from rich.prompt import Prompt
|
||||
_LEGACY_CONFIG_FILE = Path(".immich_config.json") # pre-v0.6: lived in process CWD, not on a volume
|
||||
|
||||
|
||||
def _getenv_int(name: str, default: int) -> int:
|
||||
val = os.getenv(name, str(default))
|
||||
try:
|
||||
return int(val)
|
||||
except ValueError:
|
||||
logging.warning("%s=%r is not a valid integer — using default %s", name, val, default)
|
||||
return default
|
||||
|
||||
|
||||
def _getenv_float(name: str, default: float) -> float:
|
||||
val = os.getenv(name, str(default))
|
||||
try:
|
||||
return float(val)
|
||||
except ValueError:
|
||||
logging.warning("%s=%r is not a valid float — using default %s", name, val, default)
|
||||
return default
|
||||
|
||||
|
||||
class _Config:
|
||||
"""Singleton configuration with lazy loading via __getattr__.
|
||||
|
||||
@@ -84,13 +102,13 @@ class _Config:
|
||||
self.IMMICH_URL = os.getenv("IMMICH_URL")
|
||||
self.API_KEY = os.getenv("API_KEY")
|
||||
self.OUTPUT_DIR = os.getenv("OUTPUT_DIR", "./frigate_train")
|
||||
self.YEARS_FILTER = int(os.getenv("YEARS_FILTER", "10"))
|
||||
self.MIN_FACE_WIDTH = int(os.getenv("MIN_FACE_WIDTH", "90"))
|
||||
self.MIN_FACE_COUNT = int(os.getenv("MIN_FACE_COUNT", "3"))
|
||||
self.YEARS_FILTER = _getenv_int("YEARS_FILTER", 10)
|
||||
self.MIN_FACE_WIDTH = _getenv_int("MIN_FACE_WIDTH", 90)
|
||||
self.MIN_FACE_COUNT = _getenv_int("MIN_FACE_COUNT", 3)
|
||||
self.MERGE_DUPLICATE_PEOPLE = os.getenv("MERGE_DUPLICATE_PEOPLE", "false").lower() in ("true", "1", "yes")
|
||||
self.BLUR_THRESHOLD = float(os.getenv("BLUR_THRESHOLD", "120.0"))
|
||||
self.MIN_CONFIDENCE = float(os.getenv("MIN_CONFIDENCE", "0.7"))
|
||||
self.MAX_AUTO_IMAGES = int(os.getenv("MAX_AUTO_IMAGES", "20"))
|
||||
self.BLUR_THRESHOLD = _getenv_float("BLUR_THRESHOLD", 120.0)
|
||||
self.MIN_CONFIDENCE = _getenv_float("MIN_CONFIDENCE", 0.7)
|
||||
self.MAX_AUTO_IMAGES = _getenv_int("MAX_AUTO_IMAGES", 20)
|
||||
self.QUALITY_REPLACEMENT = os.getenv("QUALITY_REPLACEMENT", "true").lower() in ("true", "1", "yes")
|
||||
_ceiling_env = os.getenv("FRIGATE_SCORE_CEILING", "").strip()
|
||||
if _ceiling_env:
|
||||
@@ -102,7 +120,7 @@ class _Config:
|
||||
else:
|
||||
self.FRIGATE_SCORE_CEILING = None
|
||||
self.ENABLE_FRIGATE_SCORES = os.getenv("ENABLE_FRIGATE_SCORES", "true").lower() in ("true", "1", "yes")
|
||||
self.FACE_MARGIN = float(os.getenv("FACE_MARGIN", "0.15"))
|
||||
self.FACE_MARGIN = _getenv_float("FACE_MARGIN", 0.15)
|
||||
self.USE_FULL_RESOLUTION = os.getenv("USE_FULL_RESOLUTION", "true").lower() in ("true", "1", "yes")
|
||||
self.ENABLE_FACE_ALIGNMENT = os.getenv("ENABLE_FACE_ALIGNMENT", "true").lower() in ("true", "1", "yes")
|
||||
self.ENABLE_CACHE = os.getenv("ENABLE_CACHE", "true").lower() in ("true", "1", "yes")
|
||||
|
||||
Reference in New Issue
Block a user