kryten-webqueue 0.9.8__tar.gz → 0.9.9__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.9.8 → kryten_webqueue-0.9.9}/CHANGELOG.md +9 -1
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/PKG-INFO +1 -1
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/cmsutils/fetchurls.py +6 -2
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/jobs/manager.py +16 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/jobs/tasks.py +19 -8
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/admin/index.html +23 -1
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/pyproject.toml +1 -1
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/tests/test_fetchurls_sharepoint.py +15 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/tests/test_phase2_jobs.py +30 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/tests/test_phase3_jobs.py +3 -1
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/.github/workflows/python-publish.yml +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/.github/workflows/release.yml +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/.gitignore +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/README.md +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/config.example.json +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/deploy/kryten-webqueue.service +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/deploy/nginx-queue.conf +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/docs/IMPLEMENTATION_SPEC.md +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/docs/IMPL_API_GATE.md +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/docs/IMPL_ECONOMY.md +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/docs/IMPL_KRYTEN_PY.md +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/docs/IMPL_ROBOT.md +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/docs/PRE_PLAN_GAPS.md +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/docs/PRODUCT_PLAN.md +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/__main__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/api_gate/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/api_gate/client.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/app.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/auth/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/auth/otp.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/auth/rate_limit.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/auth/session.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/catalog/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/catalog/db.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/catalog/images.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/catalog/mediacms.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/catalog/sync.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/config.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/ytpipe/downloader.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/jobs/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/jobs/fetchurls_auth.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/playlists/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/playlists/fire.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/playlists/importer.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/playlists/scheduler.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/queue/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/queue/ordering.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/queue/poller.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/queue/shadow.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/routes/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/routes/admin_catalog.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/routes/admin_jobs.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/routes/admin_playlists.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/routes/admin_queue.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/routes/admin_schedules.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/routes/auth.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/routes/catalog.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/routes/pages.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/routes/queue.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/routes/user.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/static/css/main.css +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/static/js/main.js +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/admin/playlists.html +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/admin/schedules.html +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/auth/login.html +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/base.html +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/catalog/browse.html +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/queue/index.html +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/user/dashboard.html +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/ws/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/ws/handler.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/ws/manager.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/tests/__init__.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/tests/test_phase1.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/tests/test_phase4_live_fixes.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/tests/test_playlist_import.py +0 -0
- {kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/tests/test_queue_announce.py +0 -0
|
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.9] — 2026-06-12
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **Graceful handling of expected job failures.** Misconfiguration and bad-input errors (e.g. `fetchurls` not finding this weekend's worksheet, a missing/unauthenticated workbook, or a missing optional dependency) are now recorded as a clean, actionable message in the job-run history and logged at WARNING — no stack trace. A new internal `JobError` distinguishes these expected failures from unexpected bugs (which still log a full traceback and keep their exception-type prefix).
|
|
14
|
+
- The `fetchurls` "sheet not found" message now reads as guidance ("This weekend's worksheet 'M.D-M.D' was not found…") and lists only the date-format weekend sheets instead of every tab in the workbook.
|
|
15
|
+
- The admin Jobs history table now shows a **Detail** column — the failure message for failed runs, or a compact summary (sheet, imported playlists, counts) for successful ones.
|
|
16
|
+
|
|
9
17
|
## [0.9.8] — 2026-06-12
|
|
10
18
|
|
|
11
19
|
### Added
|
|
@@ -33,7 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
33
41
|
### Added
|
|
34
42
|
|
|
35
43
|
- **Richer Bulk Text Import on the admin Playlists editor.** The text import now accepts, one entry per line:
|
|
36
|
-
- **dropsugar.co
|
|
44
|
+
- **dropsugar.co links** (watch `?m=TOKEN` or manifest `/api/v1/media/cytube/TOKEN.json`) — resolved against the catalog for title/duration, falling back to a constructed manifest URL when the token isn't catalogued yet.
|
|
37
45
|
- **YouTube / youtu.be links** — playlist (`list=`), start-time (`t`/`start`) and all other arguments are stripped, leaving a clean `yt:VIDEOID` item.
|
|
38
46
|
- Legacy `cm:token`, `yt:id`, and bare catalog tokens (unchanged).
|
|
39
47
|
- Trailing free text after a URL (e.g. `URL - My Title`) is used as a title hint.
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/cmsutils/fetchurls.py
RENAMED
|
@@ -1379,9 +1379,13 @@ def run(params: dict, *, config, progress=None) -> dict:
|
|
|
1379
1379
|
all_sheets = wb_peek.sheetnames
|
|
1380
1380
|
wb_peek.close()
|
|
1381
1381
|
if sheet_name not in all_sheets:
|
|
1382
|
+
# Suggest only date-format weekend sheets (ignore Sheet1/Played Movies/etc).
|
|
1383
|
+
weekend_sheets = [s for s in all_sheets if _SHEET_DATE_RE.match(s.strip())]
|
|
1384
|
+
available = ", ".join(weekend_sheets) if weekend_sheets else ", ".join(all_sheets)
|
|
1382
1385
|
raise RuntimeError(
|
|
1383
|
-
f"
|
|
1384
|
-
f"
|
|
1386
|
+
f"This weekend's worksheet '{sheet_name}' was not found in the workbook. "
|
|
1387
|
+
f"Add a sheet named '{sheet_name}' (Friday.date-Saturday.date), or check "
|
|
1388
|
+
f"the sheet name matches. Available weekend sheets: {available}"
|
|
1385
1389
|
)
|
|
1386
1390
|
|
|
1387
1391
|
_emit({"phase": "parsing", "sheet": sheet_name})
|
|
@@ -24,6 +24,15 @@ logger = logging.getLogger(__name__)
|
|
|
24
24
|
JobFunc = Callable[[dict, "JobContext"], Awaitable[dict | None]]
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
class JobError(Exception):
|
|
28
|
+
"""An expected, user-facing job failure (bad input / config, not a bug).
|
|
29
|
+
|
|
30
|
+
Raising this from a job records a clean ``failed`` run with the message and
|
|
31
|
+
logs it at WARNING without a stack trace, so misconfiguration (e.g. a
|
|
32
|
+
missing workbook sheet) reads as actionable guidance rather than a crash.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
|
|
27
36
|
def _option_values(field: dict) -> list:
|
|
28
37
|
"""Return the allowed values for an enum field's ``options``.
|
|
29
38
|
|
|
@@ -184,6 +193,13 @@ class JobManager:
|
|
|
184
193
|
except asyncio.CancelledError:
|
|
185
194
|
await self._db.finish_job_run(run_id, "cancelled", None)
|
|
186
195
|
raise
|
|
196
|
+
except JobError as exc:
|
|
197
|
+
# Expected, user-facing failure (bad input/config): record a clean
|
|
198
|
+
# message and log without a stack trace.
|
|
199
|
+
logger.warning("Job '%s' failed: %s", name, exc)
|
|
200
|
+
await self._db.finish_job_run(
|
|
201
|
+
run_id, "failed", json.dumps({"error": str(exc)})
|
|
202
|
+
)
|
|
187
203
|
except Exception as exc: # noqa: BLE001 - record any failure
|
|
188
204
|
logger.exception("Job '%s' failed", name)
|
|
189
205
|
await self._db.finish_job_run(
|
|
@@ -15,6 +15,8 @@ import functools
|
|
|
15
15
|
import importlib
|
|
16
16
|
import logging
|
|
17
17
|
|
|
18
|
+
from .manager import JobError
|
|
19
|
+
|
|
18
20
|
logger = logging.getLogger(__name__)
|
|
19
21
|
|
|
20
22
|
|
|
@@ -44,14 +46,23 @@ def _thread_safe_progress(ctx, loop):
|
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
async def _run_vendored(module_path: str, params: dict, ctx, *, deps: list[str]):
|
|
47
|
-
"""Import a vendored module, verify deps, and run its ``run()`` off-loop.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
"""Import a vendored module, verify deps, and run its ``run()`` off-loop.
|
|
50
|
+
|
|
51
|
+
The vendored tools raise ``RuntimeError`` for expected, user-facing failures
|
|
52
|
+
(missing/unauthenticated workbook, a sheet that isn't present, a missing
|
|
53
|
+
optional dependency). Surface those as :class:`JobError` so the run history
|
|
54
|
+
shows a clean, actionable message instead of a stack trace.
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
for dep in deps:
|
|
58
|
+
_require(dep)
|
|
59
|
+
module = importlib.import_module(module_path)
|
|
60
|
+
loop = asyncio.get_running_loop()
|
|
61
|
+
progress = _thread_safe_progress(ctx, loop)
|
|
62
|
+
fn = functools.partial(module.run, params, config=ctx.config, progress=progress)
|
|
63
|
+
return await asyncio.to_thread(fn)
|
|
64
|
+
except RuntimeError as exc:
|
|
65
|
+
raise JobError(str(exc)) from exc
|
|
55
66
|
|
|
56
67
|
|
|
57
68
|
# ── Enrich jobs ────────────────────────────────────────────────────────────────
|
|
@@ -212,13 +212,14 @@ async function loadJobs() {
|
|
|
212
212
|
const el = document.getElementById('job-runs');
|
|
213
213
|
if (runs.length > 0) {
|
|
214
214
|
el.innerHTML = `<table class="admin-table">
|
|
215
|
-
<tr><th>Job</th><th>Started</th><th>Ended</th><th>Status</th></tr>
|
|
215
|
+
<tr><th>Job</th><th>Started</th><th>Ended</th><th>Status</th><th>Detail</th></tr>
|
|
216
216
|
${runs.map(r => `
|
|
217
217
|
<tr>
|
|
218
218
|
<td>${escapeHtml(r.job_name || '')}</td>
|
|
219
219
|
<td>${formatLocalDateTime(r.started_at)}</td>
|
|
220
220
|
<td>${r.ended_at ? formatLocalDateTime(r.ended_at) : '—'}</td>
|
|
221
221
|
<td><span class="job-status job-status-${escapeHtml(r.status || '')}">${escapeHtml(r.status || '')}</span></td>
|
|
222
|
+
<td class="job-detail">${escapeHtml(summarizeRunDetail(r.detail))}</td>
|
|
222
223
|
</tr>
|
|
223
224
|
`).join('')}
|
|
224
225
|
</table>`;
|
|
@@ -269,6 +270,27 @@ function escapeHtml(str) {
|
|
|
269
270
|
return div.innerHTML;
|
|
270
271
|
}
|
|
271
272
|
|
|
273
|
+
// Summarize a job_runs.detail JSON blob for the history table. Prefers a
|
|
274
|
+
// failure message, otherwise a compact success summary.
|
|
275
|
+
function summarizeRunDetail(detail) {
|
|
276
|
+
if (!detail) return '';
|
|
277
|
+
let d;
|
|
278
|
+
try { d = typeof detail === 'string' ? JSON.parse(detail) : detail; }
|
|
279
|
+
catch { return String(detail).slice(0, 160); }
|
|
280
|
+
if (d && d.error) return d.error;
|
|
281
|
+
if (d && typeof d === 'object') {
|
|
282
|
+
const parts = [];
|
|
283
|
+
if (d.sheet) parts.push(d.sheet);
|
|
284
|
+
if (d.imported_playlists && d.imported_playlists.length) parts.push(`imported: ${d.imported_playlists.join(', ')}`);
|
|
285
|
+
if (typeof d.resolved === 'number') parts.push(`resolved ${d.resolved}`);
|
|
286
|
+
if (typeof d.failures === 'number' && d.failures) parts.push(`${d.failures} failed`);
|
|
287
|
+
if (typeof d.committed === 'number') parts.push(`committed ${d.committed}`);
|
|
288
|
+
if (typeof d.added_to_playlist !== 'undefined' && d.added_to_playlist) parts.push(`→ playlist ${d.added_to_playlist}`);
|
|
289
|
+
if (parts.length) return parts.join(' · ');
|
|
290
|
+
}
|
|
291
|
+
return '';
|
|
292
|
+
}
|
|
293
|
+
|
|
272
294
|
loadAdminData();
|
|
273
295
|
loadJobs();
|
|
274
296
|
</script>
|
|
@@ -71,6 +71,21 @@ def test_run_no_source_raises(tmp_path):
|
|
|
71
71
|
fetchurls.run({}, config=_config(), progress=None)
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
# --- vendored RuntimeError → JobError translation ---
|
|
75
|
+
|
|
76
|
+
async def test_run_vendored_translates_runtimeerror_to_joberror(db, monkeypatch):
|
|
77
|
+
from kryten_webqueue.jobs.manager import JobError
|
|
78
|
+
|
|
79
|
+
def boom(params, *, config, progress):
|
|
80
|
+
raise RuntimeError("This weekend's worksheet was not found.")
|
|
81
|
+
|
|
82
|
+
fake_mod = SimpleNamespace(run=boom)
|
|
83
|
+
monkeypatch.setattr(tasks.importlib, "import_module", lambda name, *a, **k: fake_mod)
|
|
84
|
+
|
|
85
|
+
with pytest.raises(JobError, match="worksheet was not found"):
|
|
86
|
+
await tasks._run_vendored("whatever", {}, _ctx(db), deps=[])
|
|
87
|
+
|
|
88
|
+
|
|
74
89
|
# --- fixed section-label playlist names + immutable preserve ---
|
|
75
90
|
|
|
76
91
|
async def _add_catalog(db, token, title="T"):
|
|
@@ -135,6 +135,36 @@ async def test_unknown_job_raises_keyerror(db):
|
|
|
135
135
|
await jm.run("nope")
|
|
136
136
|
|
|
137
137
|
|
|
138
|
+
async def test_job_error_records_clean_message(db):
|
|
139
|
+
from kryten_webqueue.jobs.manager import JobError
|
|
140
|
+
|
|
141
|
+
async def job(params, ctx):
|
|
142
|
+
raise JobError("This weekend's worksheet '6.12-6.13' was not found.")
|
|
143
|
+
|
|
144
|
+
jm = JobManager(db)
|
|
145
|
+
jm.register("fetchurls", job)
|
|
146
|
+
await jm.run("fetchurls")
|
|
147
|
+
run = await _wait_terminal(db, "fetchurls")
|
|
148
|
+
assert run["status"] == "failed"
|
|
149
|
+
# Clean message, no "RuntimeError:"/"JobError:" type prefix or traceback.
|
|
150
|
+
assert json.loads(run["detail"]) == {
|
|
151
|
+
"error": "This weekend's worksheet '6.12-6.13' was not found."
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
async def test_unexpected_error_keeps_type_prefix(db):
|
|
156
|
+
async def job(params, ctx):
|
|
157
|
+
raise ValueError("boom")
|
|
158
|
+
|
|
159
|
+
jm = JobManager(db)
|
|
160
|
+
jm.register("crashy", job)
|
|
161
|
+
await jm.run("crashy")
|
|
162
|
+
run = await _wait_terminal(db, "crashy")
|
|
163
|
+
assert run["status"] == "failed"
|
|
164
|
+
# Unexpected (bug) failures retain the exception type for debugging.
|
|
165
|
+
assert json.loads(run["detail"]) == {"error": "ValueError: boom"}
|
|
166
|
+
|
|
167
|
+
|
|
138
168
|
async def test_already_running_guard(db):
|
|
139
169
|
started = asyncio.Event()
|
|
140
170
|
release = asyncio.Event()
|
|
@@ -69,6 +69,7 @@ def test_extract_manifest_token():
|
|
|
69
69
|
async def test_fetch_job_missing_ytdlp_fails_fast(db, monkeypatch):
|
|
70
70
|
# Simulate yt_dlp absent regardless of the host environment.
|
|
71
71
|
import importlib
|
|
72
|
+
from kryten_webqueue.jobs.manager import JobError
|
|
72
73
|
real_import = importlib.import_module
|
|
73
74
|
|
|
74
75
|
def fake_import(name, *a, **k):
|
|
@@ -77,7 +78,8 @@ async def test_fetch_job_missing_ytdlp_fails_fast(db, monkeypatch):
|
|
|
77
78
|
return real_import(name, *a, **k)
|
|
78
79
|
|
|
79
80
|
monkeypatch.setattr(tasks.importlib, "import_module", fake_import)
|
|
80
|
-
|
|
81
|
+
# A missing optional dependency is a clean, user-facing JobError.
|
|
82
|
+
with pytest.raises(JobError, match="yt_dlp"):
|
|
81
83
|
await tasks.fetch_job({"url": "http://x"}, _ctx(db))
|
|
82
84
|
|
|
83
85
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/cmsutils/__init__.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/cmsutils/_common.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/cmsutils/enrichmeta.py
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/cmsutils/enrichtv.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/integrations/ytpipe/__init__.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/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
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/admin/playlists.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/admin/queue_mgmt.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/admin/schedules.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/catalog/browse.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/kryten_webqueue/templates/catalog/item_detail.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.9.8 → kryten_webqueue-0.9.9}/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
|