PR 1 (commit 6ad85e27) shipped the ``next_run_at`` pure function as
foundation plumbing. PR 2 wires the engine through it and adds
``monthly_time`` as a real registered trigger type. After this PR
``core/automation_engine.py`` no longer has its own datetime
arithmetic for daily / weekly schedules — every next-run computation
flows through one function with one set of defensive fallbacks.
Net user-visible change: zero (no UI surface for monthly_time yet —
that's PR 3). New ``monthly_time`` trigger is reachable only via
direct API for now.
**Engine refactor:**
- ``_finish_run`` — collapsed three inline branches (daily_time
arithmetic, weekly_time arithmetic, fallback schedule arithmetic)
into a single ``next_run_at(...)`` call with ``_dt_to_db_str``
normalising the aware-UTC result to the engine's naive-UTC string
convention. Retry-delay short-circuit preserved. Exception
swallowing preserved (logged at debug, writes None next_run).
- ``_setup_daily_time_trigger`` + ``_setup_weekly_time_trigger`` +
new ``_setup_monthly_time_trigger`` — three near-identical methods
collapsed into one ``_setup_timed_trigger`` skeleton. Each public
method is now a one-line dispatch passing trigger_type to the
shared helper with a human-readable label for the debug log.
- Existing ``_next_weekly_occurrence`` deleted — its logic now lives
in ``core/automation/schedule.py:_next_weekly`` (lifted in PR 1).
- New ``_dt_to_db_str(dt)`` module-level helper normalises aware-UTC
→ naive-UTC string. Centralised so a tz mistake here surfaces in
one place. Aware non-UTC datetimes converted to UTC first
(defensive against a future bug that passes the wrong tz).
- New ``_resolve_system_default_tz()`` reads the server's local IANA
tz via ``tzlocal``. Cached at module import (the host's tz doesn't
change while the process runs). Falls back to UTC when ``tzlocal``
is missing — defensive for minimal Docker images.
- New ``self._default_tz`` engine attribute reads from
``automation.default_timezone`` config first, falls back to the
system-detected IANA name. Override path lets users on weird
setups pin a specific tz without touching env vars.
**Convergence fix (intentional behaviour change):**
Old ``_setup_daily_time_trigger`` / ``_setup_weekly_time_trigger``
didn't check the DB for an existing future ``next_run`` — they'd
recompute from scratch on every engine startup, overwriting manual
edits or pending retries. The interval path (``_setup_schedule_trigger``)
already had this check. The new shared ``_setup_timed_trigger``
brings daily / weekly in line: existing-future next_run wins over
freshly-computed delay. Treat this as a correctness fix, not a
breaking change — the old behaviour was an inconsistency, not a
deliberate choice.
**Backward-compat:**
- Existing ``schedule`` / ``daily_time`` / ``weekly_time`` rows
continue to work unchanged. The ``_trigger_handlers`` registry
keeps every historic key.
- Existing rows without an explicit ``tz`` field use
``self._default_tz`` (server-local IANA via ``tzlocal``) —
preserves "every Monday 09:00 server-local" behaviour on
non-UTC servers. Pre-fix the engine used naive
``datetime.now()`` which is also server-local; net effect is
identical wall-clock time, just routed through a tz-aware
pipeline that handles DST correctly (the May 2026 "next in 8h"
bug fix class).
- Engine boots even when ``tzlocal`` is missing — the resolver
falls back to UTC silently. Existing tests would catch a hard
dependency on tzlocal here.
**``tzlocal>=5.0`` added to requirements.txt** alongside
``tzdata>=2024.1`` from PR 1. Both libraries are small and stable;
``tzlocal`` returns a clean IANA name across Windows / Linux /
Docker, sidestepping the platform-specific tz detection mess.
**Tests:** 20 new in ``tests/automation/test_engine_schedule_integration.py``:
- ``_dt_to_db_str`` x3 (aware UTC, aware non-UTC converted to UTC,
naive assumed UTC)
- ``_resolve_system_default_tz`` x2 (returns IANA string, falls back
to UTC without tzlocal)
- ``_finish_run`` dispatch through next_run_at for each trigger type
(schedule, daily_time, weekly_time, monthly_time)
- Retry-delay short-circuits next_run_at
- next_run_at returns None → DB next_run cleared
- next_run_at raises → engine swallows + writes None
- Event triggers skipped (no scheduled next-run)
- ``self._default_tz`` passed through to next_run_at
- monthly_time registered in _trigger_handlers
- All historic trigger types kept registered
- ``_setup_monthly_time_trigger`` arms timer + writes DB
- ``_setup_timed_trigger`` honours existing future DB next_run
- Skip-with-log when next_run_at returns None
- End-to-end no-mock smoke for monthly_time
260 automation suite tests pass; the 240 from PR 1's branch plus 20
new integration tests. Ruff clean.
No WHATS_NEW entry — UI doesn't expose monthly_time yet (PR 3),
and the backward-compat path preserves existing daily/weekly
schedule timing.
67 lines
1.8 KiB
Text
67 lines
1.8 KiB
Text
# SoulSync requirements
|
|
# Web application dependencies only
|
|
# All dependencies pinned for reproducible builds.
|
|
|
|
# Core web framework
|
|
Flask==3.1.3
|
|
Flask-Limiter==4.1.1
|
|
|
|
# Music service APIs
|
|
spotipy==2.26.0
|
|
PlexAPI==4.18.1
|
|
|
|
# HTTP and async support
|
|
requests==2.33.1
|
|
aiohttp==3.13.5
|
|
|
|
# Security and encryption
|
|
cryptography==48.0.0
|
|
|
|
# Media metadata handling
|
|
mutagen==1.47.0
|
|
Pillow==12.2.0
|
|
|
|
# Text processing
|
|
Unidecode==1.4.0
|
|
beautifulsoup4==4.14.3
|
|
|
|
# System monitoring
|
|
psutil==7.2.2
|
|
|
|
# IANA timezone data — required by ``zoneinfo`` on Windows hosts and
|
|
# minimal Docker base images that ship without the system tz database.
|
|
# Consumed by ``core/automation/schedule.py`` for daily / weekly /
|
|
# monthly schedule next-run computation in the user's local timezone.
|
|
# Loose-pinned because IANA tz data changes a few times a year for
|
|
# real-world DST policy updates; pinning to one snapshot would freeze
|
|
# the app's tz knowledge to the build date.
|
|
tzdata>=2024.1
|
|
|
|
# Cross-platform IANA timezone detection — returns the server's local
|
|
# tz as a string ('America/Los_Angeles', not 'PDT'). Consumed by the
|
|
# automation engine to preserve historic behaviour for daily / weekly
|
|
# trigger rows that don't carry an explicit ``tz`` field: the old
|
|
# engine computed delays from naive ``datetime.now()``, which is
|
|
# implicitly the server's local tz, so falling back to the same tz
|
|
# keeps existing schedules running at the same wall-clock time.
|
|
tzlocal>=5.0
|
|
|
|
# YouTube support -- unpinned; yt-dlp must track upstream releases to stay functional
|
|
yt-dlp>=2026.3.17
|
|
|
|
# Lyrics support
|
|
lrclibapi==0.3.1
|
|
|
|
# Audio fingerprinting for download verification
|
|
pyacoustid==1.3.1
|
|
|
|
# WebSocket client for Hydrabase connection
|
|
websocket-client==1.9.0
|
|
|
|
# Tidal download support
|
|
tidalapi==0.8.11
|
|
|
|
# WebSocket server for real-time UI updates
|
|
flask-socketio==5.6.1
|
|
gunicorn==26.0.0
|
|
simple-websocket==1.1.0
|