station-studio 1.5.0__py3-none-any.whl
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.
- api/__init__.py +0 -0
- api/deps.py +117 -0
- api/job_runner.py +674 -0
- api/main.py +333 -0
- api/routers/__init__.py +0 -0
- api/routers/editor.py +1633 -0
- api/routers/generate.py +691 -0
- api/routers/jobs.py +616 -0
- api/routers/meta.py +41 -0
- api/routers/rules.py +164 -0
- api/routers/sources.py +184 -0
- api/routers/stations.py +540 -0
- api/schemas.py +921 -0
- api/timefmt.py +20 -0
- engine/__init__.py +7 -0
- engine/artist_match.py +203 -0
- engine/database.py +33 -0
- engine/divergence.py +336 -0
- engine/editor_metadata.py +1280 -0
- engine/exporter.py +718 -0
- engine/generator.py +1161 -0
- engine/importer.py +217 -0
- engine/models.py +451 -0
- engine/persistence.py +988 -0
- engine/prior_seed.py +166 -0
- engine/rules.py +529 -0
- engine/seeding.py +526 -0
- engine/sidecar_types.py +406 -0
- engine/strategies/__init__.py +26 -0
- engine/strategies/archive_ref.py +63 -0
- engine/strategies/jingle.py +51 -0
- engine/strategies/mix.py +79 -0
- engine/strategies/pick.py +147 -0
- engine/strategies/prev_ref.py +94 -0
- engine/strategies/weighted.py +458 -0
- engine/strategies/window.py +100 -0
- engine/template.py +1009 -0
- engine/template_importer.py +347 -0
- engine/trace.py +80 -0
- engine/trace_diff.py +129 -0
- station_studio/__init__.py +9 -0
- station_studio/_bundled/alembic.ini +150 -0
- station_studio/_bundled/migrations/README +1 -0
- station_studio/_bundled/migrations/env.py +100 -0
- station_studio/_bundled/migrations/script.py.mako +28 -0
- station_studio/_bundled/migrations/versions/08c84df31980_add_station_sidecar_error_column_16_2_5_t7.py +54 -0
- station_studio/_bundled/migrations/versions/0f76cfb45e8d_baseline.py +177 -0
- station_studio/_bundled/migrations/versions/18b360df9e02_add_playlist_version_table_16_4.py +162 -0
- station_studio/_bundled/migrations/versions/1cafcc971152_slot_type.py +44 -0
- station_studio/_bundled/migrations/versions/54570900ed97_rule.py +59 -0
- station_studio/_bundled/migrations/versions/6db55f2a13af_artist_alias.py +45 -0
- station_studio/_bundled/migrations/versions/8353f29ac95e_mix_usage.py +66 -0
- station_studio/_bundled/migrations/versions/be9b9713c5a4_add_playlist_version_tags_column_16_4_t5.py +49 -0
- station_studio/_bundled/migrations/versions/c7bb27708788_add_source_playlist_role_column_14_35.py +50 -0
- station_studio/_bundled/migrations/versions/e695658879de_add_station_sidecar_column_16_2_5.py +59 -0
- station_studio/_bundled/stations/README.md +997 -0
- station_studio/_bundled/stations/rebirth-classics.editor.json +17 -0
- station_studio/_bundled/stations/rebirth-classics.json +24 -0
- station_studio/_bundled/stations/rebirth-radio.editor.json +654 -0
- station_studio/_bundled/stations/rebirth-radio.json +12284 -0
- station_studio/_bundled/stations/rebirth-radio.pool-manifest.json +12 -0
- station_studio/_bundled/stations/starter-content.itunes.xml +1189 -0
- station_studio/_bundled/stations/starter-jingles.itunes.xml +154 -0
- station_studio/_bundled/stations/starter-reference.itunes.xml +315 -0
- station_studio/_bundled/stations/starter.editor.json +395 -0
- station_studio/_bundled/stations/starter.json +194 -0
- station_studio/_bundled/stations/stateful-starter-content.itunes.xml +7767 -0
- station_studio/_bundled/stations/stateful-starter.editor.json +545 -0
- station_studio/_bundled/stations/stateful-starter.json +2071 -0
- station_studio/_bundled/web/_app/env.js +1 -0
- station_studio/_bundled/web/_app/immutable/assets/0.CHcXy38b.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/10.D59qnJwu.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/11.DJRzRnJ2.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/2.BxRsQkmK.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/3.DRUlMdK3.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/4.52O9OOIP.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/5.Csn0hZD_.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/6.DxvG4bd-.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/7.CfcTqcRr.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/8.Bwsy3jBn.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/9.B31kdeU4.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/ChipMultiInput.DULmv1MR.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/ErrorBanner.B9DMjHa5.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/PoolImportPanel.B6YfNFeB.css +1 -0
- station_studio/_bundled/web/_app/immutable/assets/StationForm.DZPAT17X.css +1 -0
- station_studio/_bundled/web/_app/immutable/chunks/B4EyEyxP.js +2 -0
- station_studio/_bundled/web/_app/immutable/chunks/BU8rCeKM.js +1 -0
- station_studio/_bundled/web/_app/immutable/chunks/BUAoT0-3.js +1 -0
- station_studio/_bundled/web/_app/immutable/chunks/Be1Id7BW.js +1 -0
- station_studio/_bundled/web/_app/immutable/chunks/Bpisc4ul.js +1 -0
- station_studio/_bundled/web/_app/immutable/chunks/DK2tf20W.js +1 -0
- station_studio/_bundled/web/_app/immutable/chunks/QXSCjADz.js +1 -0
- station_studio/_bundled/web/_app/immutable/chunks/SHg1fOfW.js +1 -0
- station_studio/_bundled/web/_app/immutable/chunks/juDB7fyi.js +9 -0
- station_studio/_bundled/web/_app/immutable/chunks/uXzXLYFb.js +1 -0
- station_studio/_bundled/web/_app/immutable/chunks/y_vJ_g9l.js +13 -0
- station_studio/_bundled/web/_app/immutable/entry/app.CFeZQhV_.js +2 -0
- station_studio/_bundled/web/_app/immutable/entry/start.CrH9QAQm.js +1 -0
- station_studio/_bundled/web/_app/immutable/nodes/0.CxFOD7tO.js +1 -0
- station_studio/_bundled/web/_app/immutable/nodes/1.DtZs9QID.js +1 -0
- station_studio/_bundled/web/_app/immutable/nodes/10.BjcAQg9i.js +42 -0
- station_studio/_bundled/web/_app/immutable/nodes/11.h8B3q9So.js +2 -0
- station_studio/_bundled/web/_app/immutable/nodes/2.C-gVionp.js +3 -0
- station_studio/_bundled/web/_app/immutable/nodes/3.CsEYWMXp.js +25 -0
- station_studio/_bundled/web/_app/immutable/nodes/4.BZtHkw90.js +25 -0
- station_studio/_bundled/web/_app/immutable/nodes/5.Crod0gUe.js +3 -0
- station_studio/_bundled/web/_app/immutable/nodes/6.CFblxVlM.js +12 -0
- station_studio/_bundled/web/_app/immutable/nodes/7.BHhoSAi2.js +27 -0
- station_studio/_bundled/web/_app/immutable/nodes/8.BqTnHUU6.js +16 -0
- station_studio/_bundled/web/_app/immutable/nodes/9.DtY1oETd.js +4 -0
- station_studio/_bundled/web/_app/version.json +1 -0
- station_studio/_bundled/web/index.html +38 -0
- station_studio/bootstrap.py +448 -0
- station_studio/cli.py +575 -0
- station_studio/home.py +72 -0
- station_studio-1.5.0.dist-info/METADATA +359 -0
- station_studio-1.5.0.dist-info/RECORD +120 -0
- station_studio-1.5.0.dist-info/WHEEL +4 -0
- station_studio-1.5.0.dist-info/entry_points.txt +2 -0
- station_studio-1.5.0.dist-info/licenses/LICENSE +21 -0
api/__init__.py
ADDED
|
File without changes
|
api/deps.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""FastAPI dependency providers."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from engine.database import make_engine, make_sessionmaker
|
|
8
|
+
from engine.models import Base
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_engine = make_engine()
|
|
12
|
+
_SessionLocal = make_sessionmaker(_engine)
|
|
13
|
+
|
|
14
|
+
# Ensure schema exists (development default; real migrations later if needed)
|
|
15
|
+
Base.metadata.create_all(_engine)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# §16.2.2 c1: sidecar files live at `stations/{slug}.editor.json` next
|
|
19
|
+
# to the canonical `stations/{slug}.json`. Same directory the seeder +
|
|
20
|
+
# bootstrap scripts read from. Path is resolved relative to the repo
|
|
21
|
+
# root (api/deps.py → api/ → repo root) so it works both from `make
|
|
22
|
+
# dev` (cwd = repo root) and from arbitrary cwds.
|
|
23
|
+
_REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
24
|
+
_STATIONS_DIR = _REPO_ROOT / "stations"
|
|
25
|
+
|
|
26
|
+
# §16.2.2.t4 (post-c5 sweep) — fail-fast against silent brittleness.
|
|
27
|
+
# If api/deps.py ever moves deeper into the package, `parent.parent`
|
|
28
|
+
# would resolve to the wrong directory. The sibling `engine/` dir is
|
|
29
|
+
# a load-bearing presence at the repo root — the whole codebase
|
|
30
|
+
# imports from it — so verifying it lives next to `_STATIONS_DIR`
|
|
31
|
+
# catches the misconfiguration at import time, with a clear stack
|
|
32
|
+
# trace, instead of failing later with "stations directory not
|
|
33
|
+
# found" once the editor endpoint is hit.
|
|
34
|
+
#
|
|
35
|
+
# Repo-name-agnostic on purpose: someone forking the repo into
|
|
36
|
+
# `my-station-fork/` should still pass. The checks verify
|
|
37
|
+
# STRUCTURE (engine/ sibling, stations/ exists), not naming.
|
|
38
|
+
#
|
|
39
|
+
# `if ... raise` (not `assert`) because `python -O` strips `assert`
|
|
40
|
+
# — and the whole point of the guard is to fail loudly when the
|
|
41
|
+
# runtime layout is wrong; `-O` would silently disable it.
|
|
42
|
+
|
|
43
|
+
def _verify_repo_layout(repo_root: Path, stations_dir: Path) -> None:
|
|
44
|
+
"""Raise RuntimeError if the path-derivation produced a layout
|
|
45
|
+
that won't work. Extracted so tests can drive failure cases
|
|
46
|
+
deterministically (tests/test_deps_layout.py).
|
|
47
|
+
|
|
48
|
+
§16.13 c4: when `STATION_STUDIO_STATIONS_DIR` is set (the
|
|
49
|
+
installed-wheel path — `station_studio.bootstrap` populates it
|
|
50
|
+
before uvicorn imports this module), the stations-dir check is
|
|
51
|
+
SKIPPED. The env-override is the authoritative source of truth in
|
|
52
|
+
that mode, and the wheel's site-packages layout has no `stations/`
|
|
53
|
+
sibling. The engine-sibling check stays — it's about Python module
|
|
54
|
+
resolution, which IS load-bearing in both modes.
|
|
55
|
+
"""
|
|
56
|
+
if not (repo_root / "engine").is_dir():
|
|
57
|
+
raise RuntimeError(
|
|
58
|
+
f"api/deps.py: repo_root={repo_root} doesn't contain an "
|
|
59
|
+
f"`engine/` sibling. Did api/deps.py move deeper into the package?"
|
|
60
|
+
)
|
|
61
|
+
# If the env override is set, defer the stations check to
|
|
62
|
+
# `get_stations_dir()` (which `mkdir(parents=True, exist_ok=True)`s
|
|
63
|
+
# the override path at call time anyway). No need to assert the
|
|
64
|
+
# repo-relative stations/ exists when the operator told us to use
|
|
65
|
+
# a different dir.
|
|
66
|
+
if os.environ.get("STATION_STUDIO_STATIONS_DIR", "").strip():
|
|
67
|
+
return
|
|
68
|
+
if not stations_dir.is_dir():
|
|
69
|
+
raise RuntimeError(
|
|
70
|
+
f"api/deps.py: stations_dir={stations_dir} doesn't exist. "
|
|
71
|
+
f"The `stations/` directory should live at the repo root."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
_verify_repo_layout(_REPO_ROOT, _STATIONS_DIR)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_db():
|
|
79
|
+
db = _SessionLocal()
|
|
80
|
+
try:
|
|
81
|
+
yield db
|
|
82
|
+
finally:
|
|
83
|
+
db.close()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_session_factory():
|
|
87
|
+
"""Return the SessionLocal factory. Endpoints that spawn background
|
|
88
|
+
work (§16.11 c3 job runner) need to open fresh sessions INSIDE the
|
|
89
|
+
worker thread — the request's `db` from `get_db()` is closed when
|
|
90
|
+
the endpoint returns. Tests override this dep alongside `get_db` so
|
|
91
|
+
the worker uses the throwaway-tmp test DB, not the dev DB.
|
|
92
|
+
"""
|
|
93
|
+
return _SessionLocal
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_stations_dir() -> Path:
|
|
97
|
+
"""Return the directory holding the committed station JSON +
|
|
98
|
+
sidecar files (`stations/{slug}.json`, `stations/{slug}.editor.json`).
|
|
99
|
+
|
|
100
|
+
§16.2.2 c1: the editor endpoint reads sidecars from this directory.
|
|
101
|
+
Pytest overrides this dep to a tmp_path so fixtures drop in
|
|
102
|
+
isolation without polluting the repo's committed sidecars.
|
|
103
|
+
|
|
104
|
+
§16.2.3 c3: `STATION_STUDIO_STATIONS_DIR` env override. The export
|
|
105
|
+
endpoint WRITES `stations/{slug}.json` here — in a real server
|
|
106
|
+
process (Playwright e2e) there's no dependency-override, so without
|
|
107
|
+
this the e2e would write stray files into the repo's `stations/`.
|
|
108
|
+
The Playwright webServer points it at a throwaway dir. Read at call
|
|
109
|
+
time (not import) so the env applies per-process; the dir is
|
|
110
|
+
created if missing so the first export doesn't 500.
|
|
111
|
+
"""
|
|
112
|
+
override = os.environ.get("STATION_STUDIO_STATIONS_DIR", "").strip()
|
|
113
|
+
if override:
|
|
114
|
+
d = Path(override)
|
|
115
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
116
|
+
return d
|
|
117
|
+
return _STATIONS_DIR
|