kryten-webqueue 0.34.0__tar.gz → 0.34.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/CHANGELOG.md +24 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/PKG-INFO +1 -1
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/catalog/db/_catalog.py +3 -5
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/jobs/job_scheduler.py +44 -49
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/jobs/manager.py +0 -17
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/admin_job_schedules.py +23 -19
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/static/js/admin.js +0 -13
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/pyproject.toml +1 -1
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/.github/workflows/gitleaks.yml +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/.github/workflows/python-publish.yml +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/.github/workflows/release.yml +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/.gitignore +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/.gitleaks.toml +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/.pre-commit-config.yaml +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/AGENT-WORKFLOW-GUIDE.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/README.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/config.example.json +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/deploy/kryten-webqueue.service +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/deploy/nginx-queue.conf +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/IMPLEMENTATION_SPEC.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/IMPL_API_GATE.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/IMPL_ECONOMY.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/IMPL_KRYTEN_PY.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/IMPL_ROBOT.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/Kryten-Economy Service Quality Survey Report at v0.13.0.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/Kryten-Webqueue Service Quality Survey Report at v0.27.1.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/Log for incorrect promo insertion problem.txt +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/PLAN_PRESENCE_AND_PROMOS.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/PRE_PLAN_GAPS.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/PRODUCT_PLAN.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/SPEC_EMOTE_REHOST.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/UX_POLISH_PLAN.md +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/__main__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/api_gate/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/api_gate/client.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/app.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/auth/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/auth/otp.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/auth/rate_limit.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/auth/session.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/catalog/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/catalog/db/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/catalog/db/_connection.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/catalog/db/_feedback.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/catalog/db/_playlists.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/catalog/db/_queue.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/catalog/images.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/catalog/mediacms.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/catalog/sync.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/config.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/cmsutils/fetchurls.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/cmsutils/motdposters.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/cmsutils/playedmovies.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/ytpipe/downloader.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/jobs/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/jobs/fetchurls_auth.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/jobs/rehost_emotes.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/jobs/tasks.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/logging_config.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/playlists/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/playlists/bulk_add.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/playlists/fire.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/playlists/importer.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/playlists/ordering.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/playlists/scheduler.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/promos/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/promos/director.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/queue/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/queue/completion.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/queue/ordering.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/queue/poller.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/queue/presence.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/queue/race_poller.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/queue/shadow.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/admin_catalog.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/admin_feedback.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/admin_jobs.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/admin_moderation.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/admin_playlists.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/admin_promos.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/admin_queue.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/admin_schedules.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/auth.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/catalog.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/feedback.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/pages.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/queue.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/user.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/static/css/main.css +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/static/js/admin-playlists.js +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/static/js/admin-promos.js +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/static/js/admin-queue-mgmt.js +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/static/js/admin-schedules.js +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/static/js/main.js +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/admin/index.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/admin/playlists.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/admin/promos.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/admin/schedules.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/auth/login.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/base.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/catalog/browse.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/feedback/index.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/queue/index.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/race.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/user/dashboard.html +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/ws/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/ws/handler.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/ws/manager.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/__init__.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_config_persistence.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_feedback.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_fetchurls_sharepoint.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_phase1.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_phase2_jobs.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_phase3_jobs.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_phase4_live_fixes.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_playlist_import.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_presence_refund.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_promo_director.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_promo_pool_exclusion.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_queue_announce.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_race_poller.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_recently_played_hide.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_save_results_to_playlist.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_schedule_lock.py +0 -0
- {kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/tests/test_search_facets.py +0 -0
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.34.2] - 2026-08-12
|
|
4
|
+
|
|
5
|
+
### Removed
|
|
6
|
+
|
|
7
|
+
- Job chaining: removed the "Run next" chain feature entirely.
|
|
8
|
+
Removed `_on_job_complete()` from `JobScheduler`, `set_post_run_hook()` and
|
|
9
|
+
`_post_run_hook` from `JobManager`, `run_next_job` from `upsert_job_schedule()`,
|
|
10
|
+
the `run_next_job` field from POST/PUT API bodies, and the "Run next"
|
|
11
|
+
dropdown from the schedule edit modal. The `run_next_job` DB column is
|
|
12
|
+
preserved (unused) for schema compatibility.
|
|
13
|
+
|
|
14
|
+
## [0.34.1] - 2026-08-12
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- `JobScheduler._register()`: bad cron expressions in the DB (e.g. hour=27)
|
|
19
|
+
are now logged at WARNING and skipped rather than raising `ValueError` during
|
|
20
|
+
startup and preventing the service from starting.
|
|
21
|
+
- `JobScheduler.upsert()`: validates the cron expression via APScheduler before
|
|
22
|
+
persisting; raises `ValueError` with a clear message so the API route can
|
|
23
|
+
return HTTP 400 instead of saving an unrunnable schedule.
|
|
24
|
+
- `/admin/job-schedules` POST and PUT: APScheduler validation errors now surface
|
|
25
|
+
as HTTP 400 with the rejection reason.
|
|
26
|
+
|
|
3
27
|
## [0.34.0] - 2026-08-11
|
|
4
28
|
|
|
5
29
|
### Added
|
|
@@ -926,23 +926,21 @@ class _CatalogMixin:
|
|
|
926
926
|
label: str | None = None,
|
|
927
927
|
params_json: str | None = None,
|
|
928
928
|
is_active: bool = True,
|
|
929
|
-
run_next_job: str | None = None,
|
|
930
929
|
created_by: str | None = None,
|
|
931
930
|
) -> None:
|
|
932
931
|
await self._db.execute(
|
|
933
932
|
"""
|
|
934
933
|
INSERT INTO job_schedules
|
|
935
|
-
(job_name, label, cron_expression, params_json, is_active,
|
|
936
|
-
VALUES (?, ?, ?, ?, ?,
|
|
934
|
+
(job_name, label, cron_expression, params_json, is_active, created_by)
|
|
935
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
937
936
|
ON CONFLICT(job_name) DO UPDATE SET
|
|
938
937
|
label = excluded.label,
|
|
939
938
|
cron_expression = excluded.cron_expression,
|
|
940
939
|
params_json = excluded.params_json,
|
|
941
940
|
is_active = excluded.is_active,
|
|
942
|
-
run_next_job = excluded.run_next_job,
|
|
943
941
|
updated_at = datetime('now')
|
|
944
942
|
""",
|
|
945
|
-
[job_name, label, cron_expression, params_json, int(is_active),
|
|
943
|
+
[job_name, label, cron_expression, params_json, int(is_active), created_by],
|
|
946
944
|
)
|
|
947
945
|
|
|
948
946
|
async def delete_job_schedule(self, job_name: str) -> None:
|
|
@@ -3,10 +3,6 @@
|
|
|
3
3
|
Persists schedules to the ``job_schedules`` table and fires them via
|
|
4
4
|
APScheduler's ``CronTrigger``. Completely separate from ``PlaylistScheduler``,
|
|
5
5
|
which fires playlists onto the live queue.
|
|
6
|
-
|
|
7
|
-
Chain support: when a job finishes with status ``completed`` and was triggered
|
|
8
|
-
by the scheduler, the scheduler checks ``run_next_job`` on that schedule and
|
|
9
|
-
immediately launches the next job using its own saved params.
|
|
10
6
|
"""
|
|
11
7
|
|
|
12
8
|
from __future__ import annotations
|
|
@@ -30,7 +26,6 @@ class JobScheduler:
|
|
|
30
26
|
|
|
31
27
|
async def start(self) -> None:
|
|
32
28
|
self._aps.start()
|
|
33
|
-
self._jm.set_post_run_hook(self._on_job_complete)
|
|
34
29
|
await self._reload()
|
|
35
30
|
logger.info("JobScheduler started")
|
|
36
31
|
|
|
@@ -57,21 +52,30 @@ class JobScheduler:
|
|
|
57
52
|
)
|
|
58
53
|
return False
|
|
59
54
|
minute, hour, day, month, day_of_week = parts
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
self.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
55
|
+
try:
|
|
56
|
+
trigger = CronTrigger(
|
|
57
|
+
minute=minute,
|
|
58
|
+
hour=hour,
|
|
59
|
+
day=day,
|
|
60
|
+
month=month,
|
|
61
|
+
day_of_week=day_of_week,
|
|
62
|
+
timezone="UTC",
|
|
63
|
+
)
|
|
64
|
+
self._aps.add_job(
|
|
65
|
+
self._fire,
|
|
66
|
+
trigger=trigger,
|
|
67
|
+
args=[job_name],
|
|
68
|
+
id=f"jsch_{job_name}",
|
|
69
|
+
replace_existing=True,
|
|
70
|
+
)
|
|
71
|
+
except (ValueError, Exception) as exc: # noqa: BLE001 - bad stored cron must not crash startup
|
|
72
|
+
logger.warning(
|
|
73
|
+
"JobScheduler: skipping invalid cron %r for %r: %s",
|
|
74
|
+
cron_expression,
|
|
75
|
+
job_name,
|
|
76
|
+
exc,
|
|
77
|
+
)
|
|
78
|
+
return False
|
|
75
79
|
logger.info("JobScheduler: registered %r cron=%r", job_name, cron_expression)
|
|
76
80
|
return True
|
|
77
81
|
|
|
@@ -95,32 +99,6 @@ class JobScheduler:
|
|
|
95
99
|
except ValueError as exc:
|
|
96
100
|
logger.warning("JobScheduler: %r invalid params: %s", job_name, exc)
|
|
97
101
|
|
|
98
|
-
async def _on_job_complete(
|
|
99
|
-
self, job_name: str, status: str, triggered_by: str | None
|
|
100
|
-
) -> None:
|
|
101
|
-
"""Post-run hook: chain to run_next_job if the scheduler triggered this run."""
|
|
102
|
-
if triggered_by != "scheduler" or status != "completed":
|
|
103
|
-
return
|
|
104
|
-
sched = await self._db.get_job_schedule(job_name)
|
|
105
|
-
if not sched or not sched.get("run_next_job"):
|
|
106
|
-
return
|
|
107
|
-
next_job: str = sched["run_next_job"]
|
|
108
|
-
next_sched = await self._db.get_job_schedule(next_job)
|
|
109
|
-
params = (
|
|
110
|
-
json.loads(next_sched["params_json"])
|
|
111
|
-
if next_sched and next_sched.get("params_json")
|
|
112
|
-
else None
|
|
113
|
-
)
|
|
114
|
-
try:
|
|
115
|
-
await self._jm.run(next_job, triggered_by="scheduler", params=params)
|
|
116
|
-
logger.info("JobScheduler: chained %r → %r", job_name, next_job)
|
|
117
|
-
except KeyError:
|
|
118
|
-
logger.warning("JobScheduler: chain target %r is not registered", next_job)
|
|
119
|
-
except ValueError as exc:
|
|
120
|
-
logger.warning(
|
|
121
|
-
"JobScheduler: chain %r → %r invalid params: %s", job_name, next_job, exc
|
|
122
|
-
)
|
|
123
|
-
|
|
124
102
|
# ── Public API ─────────────────────────────────────────────────────────────
|
|
125
103
|
|
|
126
104
|
async def upsert(
|
|
@@ -130,11 +108,29 @@ class JobScheduler:
|
|
|
130
108
|
*,
|
|
131
109
|
params: dict | None = None,
|
|
132
110
|
label: str | None = None,
|
|
133
|
-
run_next_job: str | None = None,
|
|
134
111
|
is_active: bool = True,
|
|
135
112
|
created_by: str | None = None,
|
|
136
113
|
) -> None:
|
|
137
|
-
"""Persist a schedule and (re)register it with APScheduler.
|
|
114
|
+
"""Persist a schedule and (re)register it with APScheduler.
|
|
115
|
+
|
|
116
|
+
Raises ValueError if cron_expression is rejected by APScheduler.
|
|
117
|
+
"""
|
|
118
|
+
# Validate before persisting so a bad expression is rejected at save time.
|
|
119
|
+
if is_active:
|
|
120
|
+
parts = (cron_expression or "").strip().split()
|
|
121
|
+
if len(parts) != 5:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"cron_expression must have exactly 5 fields (min hour dom mon dow), got: {cron_expression!r}"
|
|
124
|
+
)
|
|
125
|
+
minute, hour, day, month, day_of_week = parts
|
|
126
|
+
try:
|
|
127
|
+
CronTrigger(
|
|
128
|
+
minute=minute, hour=hour, day=day,
|
|
129
|
+
month=month, day_of_week=day_of_week, timezone="UTC",
|
|
130
|
+
)
|
|
131
|
+
except Exception as exc:
|
|
132
|
+
raise ValueError(f"Invalid cron expression {cron_expression!r}: {exc}") from exc
|
|
133
|
+
|
|
138
134
|
params_json = json.dumps(params) if params is not None else None
|
|
139
135
|
await self._db.upsert_job_schedule(
|
|
140
136
|
job_name,
|
|
@@ -142,7 +138,6 @@ class JobScheduler:
|
|
|
142
138
|
label=label,
|
|
143
139
|
params_json=params_json,
|
|
144
140
|
is_active=is_active,
|
|
145
|
-
run_next_job=run_next_job,
|
|
146
141
|
created_by=created_by,
|
|
147
142
|
)
|
|
148
143
|
self._unregister(job_name)
|
|
@@ -139,14 +139,6 @@ class JobManager:
|
|
|
139
139
|
self._config = config
|
|
140
140
|
self._jobs: dict[str, dict] = {}
|
|
141
141
|
self._running: dict[str, asyncio.Task] = {}
|
|
142
|
-
self._post_run_hook: Callable | None = None
|
|
143
|
-
|
|
144
|
-
def set_post_run_hook(self, hook: "Callable[[str, str, str | None], Any]") -> None:
|
|
145
|
-
"""Register a coroutine called after each job finishes.
|
|
146
|
-
|
|
147
|
-
Signature: async hook(job_name, status, triggered_by) -> None
|
|
148
|
-
"""
|
|
149
|
-
self._post_run_hook = hook
|
|
150
142
|
|
|
151
143
|
def register(
|
|
152
144
|
self,
|
|
@@ -232,14 +224,11 @@ class JobManager:
|
|
|
232
224
|
run_id=run_id,
|
|
233
225
|
triggered_by=triggered_by,
|
|
234
226
|
)
|
|
235
|
-
_final_status = "failed"
|
|
236
227
|
try:
|
|
237
228
|
result = await func(params, ctx)
|
|
238
229
|
detail = json.dumps(result) if result is not None else None
|
|
239
230
|
await self._db.finish_job_run(run_id, "completed", detail)
|
|
240
|
-
_final_status = "completed"
|
|
241
231
|
except asyncio.CancelledError:
|
|
242
|
-
_final_status = "cancelled"
|
|
243
232
|
try:
|
|
244
233
|
await self._db.finish_job_run(run_id, "cancelled", None)
|
|
245
234
|
except Exception: # noqa: BLE001
|
|
@@ -276,9 +265,3 @@ class JobManager:
|
|
|
276
265
|
)
|
|
277
266
|
finally:
|
|
278
267
|
self._running.pop(name, None)
|
|
279
|
-
hook = self._post_run_hook
|
|
280
|
-
if hook:
|
|
281
|
-
try:
|
|
282
|
-
await hook(name, _final_status, triggered_by)
|
|
283
|
-
except Exception: # noqa: BLE001
|
|
284
|
-
logger.debug("post_run_hook failed for '%s'", name, exc_info=True)
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/admin_job_schedules.py
RENAMED
|
@@ -26,7 +26,7 @@ async def get_job_schedule(
|
|
|
26
26
|
async def upsert_job_schedule(request: Request, user: dict = Depends(require_admin)):
|
|
27
27
|
"""Create or update a job schedule.
|
|
28
28
|
|
|
29
|
-
Body: {job_name, cron_expression, params?, label?,
|
|
29
|
+
Body: {job_name, cron_expression, params?, label?, is_active?}
|
|
30
30
|
cron_expression must be a standard 5-field cron string (min hour dom mon dow).
|
|
31
31
|
"""
|
|
32
32
|
body = await request.json()
|
|
@@ -40,15 +40,17 @@ async def upsert_job_schedule(request: Request, user: dict = Depends(require_adm
|
|
|
40
40
|
raise HTTPException(400, "cron_expression must have exactly 5 fields (min hour dom mon dow)")
|
|
41
41
|
|
|
42
42
|
job_scheduler = request.app.state.job_scheduler
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
try:
|
|
44
|
+
await job_scheduler.upsert(
|
|
45
|
+
job_name,
|
|
46
|
+
cron_expression,
|
|
47
|
+
params=body.get("params") or None,
|
|
48
|
+
label=body.get("label") or None,
|
|
49
|
+
is_active=bool(body.get("is_active", True)),
|
|
50
|
+
created_by=user["username"],
|
|
51
|
+
)
|
|
52
|
+
except ValueError as exc:
|
|
53
|
+
raise HTTPException(400, str(exc))
|
|
52
54
|
return {"success": True}
|
|
53
55
|
|
|
54
56
|
|
|
@@ -65,15 +67,17 @@ async def update_job_schedule(
|
|
|
65
67
|
raise HTTPException(400, "cron_expression must have exactly 5 fields (min hour dom mon dow)")
|
|
66
68
|
|
|
67
69
|
job_scheduler = request.app.state.job_scheduler
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
try:
|
|
71
|
+
await job_scheduler.upsert(
|
|
72
|
+
job_name,
|
|
73
|
+
cron_expression,
|
|
74
|
+
params=body.get("params") or None,
|
|
75
|
+
label=body.get("label") or None,
|
|
76
|
+
is_active=bool(body.get("is_active", True)),
|
|
77
|
+
created_by=user["username"],
|
|
78
|
+
)
|
|
79
|
+
except ValueError as exc:
|
|
80
|
+
raise HTTPException(400, str(exc))
|
|
77
81
|
return {"success": True}
|
|
78
82
|
|
|
79
83
|
|
|
@@ -145,19 +145,12 @@ async function openScheduleModal(jobName) {
|
|
|
145
145
|
|
|
146
146
|
const cronVal = existing ? escapeHtml(existing.cron_expression || '') : '';
|
|
147
147
|
const isActive = existing ? !!existing.is_active : true;
|
|
148
|
-
const runNextVal = existing ? (existing.run_next_job || '') : '';
|
|
149
148
|
|
|
150
149
|
const paramFields = job.schema.map(f => {
|
|
151
150
|
const savedVal = existingParams[f.name] !== undefined ? existingParams[f.name] : f.default;
|
|
152
151
|
return _jobFieldHtml({...f, default: savedVal}, 'schedparam-');
|
|
153
152
|
}).join('');
|
|
154
153
|
|
|
155
|
-
const otherJobs = JOBS_CACHE.filter(j => j.name !== jobName);
|
|
156
|
-
const nextOpts = '<option value="">\u2014 none (no chain) \u2014</option>' +
|
|
157
|
-
otherJobs.map(j =>
|
|
158
|
-
`<option value="${escapeHtml(j.name)}" ${j.name === runNextVal ? 'selected' : ''}>${escapeHtml(j.label)}</option>`
|
|
159
|
-
).join('');
|
|
160
|
-
|
|
161
154
|
const overlay = document.createElement('div');
|
|
162
155
|
overlay.id = 'schedule-modal';
|
|
163
156
|
overlay.className = 'modal-overlay';
|
|
@@ -173,10 +166,6 @@ async function openScheduleModal(jobName) {
|
|
|
173
166
|
<span>Active</span>
|
|
174
167
|
</label>
|
|
175
168
|
${paramFields ? `<details ${existing ? 'open' : ''}><summary style="cursor:pointer;margin:.5rem 0">Job parameters</summary><div class="job-params">${paramFields}</div></details>` : ''}
|
|
176
|
-
<label class="field">
|
|
177
|
-
<span>Run next (chain)</span>
|
|
178
|
-
<select id="sched-run-next">${nextOpts}</select>
|
|
179
|
-
</label>
|
|
180
169
|
<div class="modal-actions">
|
|
181
170
|
<button class="btn btn-secondary" data-action="cancel">Cancel</button>
|
|
182
171
|
<button class="btn btn-primary" data-action="save">Save</button>
|
|
@@ -196,7 +185,6 @@ async function _submitScheduleModal(jobName, job, overlay) {
|
|
|
196
185
|
const cron = (document.getElementById('sched-cron').value || '').trim();
|
|
197
186
|
if (!cron) { showToast('Cron expression is required', 'error'); return; }
|
|
198
187
|
const isActive = document.getElementById('sched-active').checked;
|
|
199
|
-
const runNext = document.getElementById('sched-run-next').value || null;
|
|
200
188
|
|
|
201
189
|
const params = {};
|
|
202
190
|
for (const f of job.schema) {
|
|
@@ -216,7 +204,6 @@ async function _submitScheduleModal(jobName, job, overlay) {
|
|
|
216
204
|
job_name: jobName,
|
|
217
205
|
cron_expression: cron,
|
|
218
206
|
is_active: isActive,
|
|
219
|
-
run_next_job: runNext,
|
|
220
207
|
params: Object.keys(params).length ? params : null,
|
|
221
208
|
label: job.label,
|
|
222
209
|
}),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/docs/Log for incorrect promo insertion problem.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/cmsutils/__init__.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/cmsutils/_common.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/cmsutils/enrichtv.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/cmsutils/fetchurls.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/ytpipe/__init__.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/integrations/ytpipe/downloader.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/routes/admin_moderation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/static/js/admin-playlists.js
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/static/js/admin-queue-mgmt.js
RENAMED
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/static/js/admin-schedules.js
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/admin/index.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/admin/playlists.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/admin/promos.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/admin/queue_mgmt.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/admin/schedules.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/catalog/browse.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/catalog/item_detail.html
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/feedback/index.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/queue/index.html
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.34.0 → kryten_webqueue-0.34.2}/kryten_webqueue/templates/user/dashboard.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|