harry0703/MoneyPrinterTurbo · error · ValueError

max_age_days must be a positive integer or None

Error message

max_age_days must be a positive integer or None

What it means

ValueError from _validate_max_age_days in the cache manager: max_age_days must be None or a positive int, but the caller passed a non-int, a bool, zero, or a negative number. The explicit isinstance(max_age_days, bool) check rejects True/False even though bool subclasses int. Validation runs even when the cache directory is empty, so bad parameters fail deterministically instead of silently passing.

Solutions

  1. Pass None for 'all caches' and a positive int (e.g. 30) for age-based stats/cleanup.
  2. Coerce user input at the boundary: days = int(str(days)) and validate days > 0, or map falsy to None.
  3. Never rely on True/False — booleans are deliberately rejected.

Example fix

// before
get_video_cache_stats(max_age_days=request.args.get('days', type=float))  # 7.0 -> ValueError

// after
days = request.args.get('days', type=int)
get_video_cache_stats(max_age_days=days if days and days > 0 else None)
Defensive patterns

Strategy: type-guard

Validate before calling

def normalize_max_age_days(value):
    if value is None:
        return None
    days = int(value)
    if isinstance(value, bool) or days <= 0:
        raise ValueError('max_age_days must be a positive integer or None')
    return days

Type guard

def is_valid_max_age_days(value) -> bool:
    """True only for None or positive non-bool ints."""
    if value is None:
        return True
    return not isinstance(value, bool) and isinstance(value, int) and value > 0

Try / catch

try:
    get_video_cache_stats(max_age_days=days)
except ValueError as e:
    if 'max_age_days' in str(e):
        days = None  # fall back to full stats
        get_video_cache_stats(max_age_days=days)

Prevention

When it happens

Trigger: Calling get_video_cache_stats(max_age_days=...), cleanup, or any API that funnels into _validate_max_age_days with 0, -1, 7.0, '7', or True. WebUI form values parsed as strings/floats are the classic source.

Common situations: Form/query params arriving as strings ('30') and being passed through unconverted; floats from division (days*1.0); using True as a truthy 'purge everything' flag; 0 intended to mean 'all' instead of None.

Related errors


AI-assisted analysis of harry0703/MoneyPrinterTurbo@1f9f19c202 (2026-08-14). Data as JSON: /api/errors/a456ec9c12a1d87e. Report an issue: GitHub.

Appendix: source

Thrown at app/services/cache_manager.py:120

    entry: _VideoCacheEntry,
    max_age_days: int | None,
    now: float,
) -> bool:
    if max_age_days is None:
        return True
    return entry.mtime < now - max_age_days * _SECONDS_PER_DAY


def _validate_max_age_days(max_age_days: int | None) -> None:
    """即使缓存目录为空,也应稳定拒绝无效清理参数。"""
    if max_age_days is None:
        return
    if (
        isinstance(max_age_days, bool)
        or not isinstance(max_age_days, int)
        or max_age_days <= 0
    ):
        raise ValueError("max_age_days must be a positive integer or None")


def get_video_cache_stats(max_age_days: int | None = None) -> VideoCacheStats:
    """
    统计全部缓存,或预览修改时间早于指定天数的可清理缓存。

    ``max_age_days=None`` 表示全部缓存。统计过程只读取目录项的大小和修改时间,
    不读取视频内容,因此即使缓存总容量很大也不会产生与容量成比例的 I/O。
    """

    _validate_max_age_days(max_age_days)
    now = time.time()
    file_count = 0
    total_size = 0
    oldest_mtime = None
    newest_mtime = None

    for entry in _iter_video_cache_entries():

View on GitHub (pinned to 1f9f19c202)