Core Services¶
Surface-agnostic business logic from musicseed-core (musicseed.services.*). These services
return result models and raise typed exceptions; surfaces (CLI, API handlers) map them.
Library¶
musicseed.services.library
¶
Library service — surface-agnostic entry points for import, DB, and status operations.
EnrichmentCoverage
¶
Bases: BaseModel
Per-source enrichment coverage counts over the local track library.
LibraryStatus
¶
Bases: BaseModel
Point-in-time statistics for the local library and its configuration.
ImportResult
¶
Bases: BaseModel
Counts of rows written by one Plex import run.
CountCompare
¶
Bases: BaseModel
Plex vs local row counts for one entity type.
missing
property
¶
How many Plex rows are not yet imported locally (never negative).
ImportCoverage
¶
Bases: BaseModel
Comparison of Plex library counts against the imported local counts.
initialize_database()
¶
Create the SQLite database file and tables. Idempotent.
optimize_database()
¶
Create performance indexes.
Returns:
| Type | Description |
|---|---|
list[IndexResult]
|
Per-index results describing success or failure for each index. |
import_library(plex_db_path=None, library_name=None, full_import=False, progress_callback=None, should_cancel=None)
¶
Import metadata from the Plex database into the local library.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plex_db_path
|
Path | None
|
path to the Plex SQLite database; defaults to the
configured |
None
|
library_name
|
str | None
|
Plex library to import; defaults to the configured
|
None
|
full_import
|
bool
|
re-import everything instead of an incremental import. |
False
|
progress_callback
|
Callable[[int, int, str], None] | None
|
optional |
None
|
should_cancel
|
Callable[[], bool] | None
|
optional callable polled by the importer; the import stops early when it returns True. |
None
|
Returns:
| Type | Description |
|---|---|
ImportResult
|
Counts of imported artists, albums, tracks, and play history rows. |
Raises:
| Type | Description |
|---|---|
NotFoundError
|
if the Plex database file does not exist. |
has_succeeded_import()
¶
Return True when any import job has ever reached succeeded.
Returns False (rather than raising) when the database cannot be read.
get_import_coverage()
¶
Compare MusicSeed artist/album/track counts to the configured Plex library.
Returns:
| Type | Description |
|---|---|
ImportCoverage | None
|
The coverage comparison, or None when the Plex database cannot be |
ImportCoverage | None
|
read. Does not raise. |
get_status()
¶
Return library statistics and enrichment coverage.
Returns:
| Type | Description |
|---|---|
LibraryStatus
|
Entity counts (artists, albums, tracks, plays, tags), per-source |
LibraryStatus
|
enrichment coverage, import coverage against the configured Plex |
LibraryStatus
|
library, and the resolved configuration paths. |
Recommend¶
musicseed.services.recommend
¶
Recommendation service — surface-agnostic entry points for the recommendation flow.
RecommendationResult
¶
Bases: BaseModel
Result of a recommendation request.
PlaylistCreateResult
¶
Bases: BaseModel
Result of a playlist creation request.
get_recommendations(*, seed_texts=None, seed_ids=None, limit=50, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None)
¶
Return seed tracks and scored recommendations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed_texts
|
list[str] | None
|
seed tracks as |
None
|
seed_ids
|
list[int] | None
|
seed tracks by local database id. |
None
|
limit
|
int
|
maximum number of recommendations to return. |
50
|
weights
|
Weights | None
|
signal weights; defaults to |
None
|
year_min
|
int | None
|
only recommend tracks released in this year or later. |
None
|
year_max
|
int | None
|
only recommend tracks released in this year or earlier. |
None
|
max_tracks_per_artist
|
int
|
artist diversity cap applied during selection. |
3
|
min_score
|
float | None
|
drop recommendations with a total score below this value. |
None
|
Returns:
| Type | Description |
|---|---|
RecommendationResult
|
The resolved seed tracks, the selected recommendations, and the |
RecommendationResult
|
sonic coverage of the candidate pool. |
Raises:
| Type | Description |
|---|---|
NotFoundError
|
if one or more seed tracks cannot be resolved. |
create_playlist(name, *, seed_texts=None, seed_ids=None, limit=50, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None)
¶
Generate recommendations and create a Plex playlist.
The created playlist contains the resolved seed tracks followed by the
recommendations. Accepts the same recommendation arguments as
get_recommendations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
title of the Plex playlist to create. |
required |
seed_texts
|
list[str] | None
|
seed tracks as |
None
|
seed_ids
|
list[int] | None
|
seed tracks by local database id. |
None
|
limit
|
int
|
maximum number of recommendations to include. |
50
|
weights
|
Weights | None
|
signal weights; defaults to |
None
|
year_min
|
int | None
|
only recommend tracks released in this year or later. |
None
|
year_max
|
int | None
|
only recommend tracks released in this year or earlier. |
None
|
max_tracks_per_artist
|
int
|
artist diversity cap applied during selection. |
3
|
min_score
|
float | None
|
drop recommendations with a total score below this value. |
None
|
Returns:
| Type | Description |
|---|---|
PlaylistCreateResult
|
The resolved seed tracks, the recommendations, and the created Plex |
PlaylistCreateResult
|
playlist. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if seed tracks cannot be resolved. |
PlexAPIError
|
if the Plex API call fails. |
Enrichment¶
musicseed.services.enrichment
¶
Enrichment service — surface-agnostic entry points for metadata enrichment.
enrich_tracks(source='listenbrainz', batch_size=50, limit=None, artist=None, album=None, resume=False, concurrency=5, progress_callback=None, should_cancel=None)
¶
Enrich tracks with external metadata from Spotify or ListenBrainz.
Runs the async enrichment pipeline via asyncio.run() internally —
never call this from inside a running event loop; offload to a thread
instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str
|
enrichment source, |
'listenbrainz'
|
batch_size
|
int
|
tracks processed per batch. |
50
|
limit
|
int | None
|
maximum number of tracks to enrich (None for all). |
None
|
artist
|
str | None
|
only enrich tracks whose artist name matches this pattern. |
None
|
album
|
str | None
|
only enrich tracks whose album title matches this pattern. |
None
|
resume
|
bool
|
skip tracks that were already attempted. |
False
|
concurrency
|
int
|
maximum concurrent requests inside the async pipeline. |
5
|
progress_callback
|
Callable[[int, int, str], None] | None
|
optional |
None
|
should_cancel
|
Callable[[], bool] | None
|
optional callable polled by the pipeline; enrichment stops early when it returns True. |
None
|
Returns:
| Type | Description |
|---|---|
EnrichmentStats
|
Aggregate enrichment statistics (processed, matched, unmatched, |
EnrichmentStats
|
errors). |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if Spotify credentials are missing when source='spotify', or the ListenBrainz user token is missing when source='listenbrainz'. |
Discovery¶
musicseed.services.discovery
¶
Local environment discovery: Plex server, databases, and config state.
Surface-agnostic and strictly read-only: probes the filesystem and the Plex
HTTP API (GET requests only) but never starts imports, sonic analysis,
enrichment, or any Plex mutation. Expected failures are returned as
structured data (reason codes) so surfaces can render actionable fixes
instead of parsing exceptions. Plex tokens are never included in results.
Reason
¶
Bases: StrEnum
Machine-readable discovery outcome codes.
PathCandidate
¶
Bases: BaseModel
One probed filesystem candidate for a required local file.
FileDiscovery
¶
Bases: BaseModel
Discovery result for a required local file (e.g. a Plex database).
DatabasePathDiscovery
¶
Bases: BaseModel
Discovery result for MusicSeed's own SQLite database location.
PlexServerDiscovery
¶
Bases: BaseModel
Discovery result for the Plex HTTP API.
SpotifyCredentialsCheck
¶
Bases: BaseModel
Presence of Spotify enrichment credentials (values never exposed).
ListenBrainzTokenCheck
¶
Bases: BaseModel
Presence of the ListenBrainz user token (value never exposed).
EnrichmentDiscovery
¶
Bases: BaseModel
Enrichment-provider readiness. Either provider's credentials suffice.
FirstRunStatus
¶
Bases: BaseModel
Derived first-run state and the reasons it is considered a first run.
DiscoveryResult
¶
Bases: BaseModel
Complete, read-only picture of the local MusicSeed environment.
read_plex_token(preferences_path=None, local_admin_token_path=None)
¶
Read a usable Plex token from the local server's data directory.
Prefers the account token (PlexOnlineToken in Preferences.xml),
then falls back to .LocalAdminToken (which only works from localhost).
When no explicit paths are given, probes macOS and Linux Plex data dirs.
Returns None when neither is readable. Read-only — the token value is
never logged or returned in discovery results.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
preferences_path
|
str | None
|
explicit path to |
None
|
local_admin_token_path
|
str | None
|
explicit path to |
None
|
Returns:
| Type | Description |
|---|---|
str | None
|
The token string, or None when no readable token is found. |
discover(*, musicseed_db_path=None, plex_db_path=None, plex_url=None, plex_token=None, plex_library=None, check_server=True, timeout=5.0, config=None)
¶
Probe the local MusicSeed/Plex environment (read-only).
Never raises on expected failures — they are reported via reason
codes instead.
Overrides apply to this call only and never mutate global configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
musicseed_db_path
|
str | None
|
override for the MusicSeed database path. |
None
|
plex_db_path
|
str | None
|
override for the Plex library database path. |
None
|
plex_url
|
str | None
|
override for the Plex server URL. |
None
|
plex_token
|
str | None
|
override for the Plex token. |
None
|
plex_library
|
str | None
|
override for the Plex library name. |
None
|
check_server
|
bool
|
whether to probe the Plex HTTP API (False skips the
network call and reports the server check as |
True
|
timeout
|
float
|
seconds before the Plex HTTP probe gives up. |
5.0
|
config
|
Config | None
|
explicit config to probe against; defaults to the global
|
None
|
Returns:
| Type | Description |
|---|---|
DiscoveryResult
|
The complete discovery result, including per-check |
DiscoveryResult
|
enrichment readiness, missing inputs, and the derived first-run |
DiscoveryResult
|
status. The Plex token is never included. |
Plex Discovery¶
musicseed.services.plex_discovery
¶
Plex server discovery — local network (GDM + SSDP) and account (plex.tv).
Two independent, read-only discovery paths feed one result list:
- Local network — GDM ("Good Day Mate") multicast on
239.0.0.250:32414with an SSDP fallback on239.255.255.250:1900. Both use stdlibsocketonly and find servers on the same subnet (multicast never crosses a router). GDM replies carry the friendly name, port, product, version, and machine identifier. - Account —
plex.tv/api/resourceslists every server linked to the user's Plex account, including servers on other subnets that multicast cannot reach. Requires a Plex token and internet access.
Both paths are strictly read-only and never store or return a token.
This is a deliberately separate, opt-in probe — it is not folded into
services.discovery.discover(), which runs on frequent dashboard polls and
must stay cheap. The first-run wizard calls it once.
DiscoveredPlexServer
¶
Bases: BaseModel
One Plex server discovered on the local network or the Plex account.
url
property
¶
The server's base URL (scheme://host:port).
discover_plex_account_servers(token, timeout=5.0)
¶
Discover servers linked to the Plex account via plex.tv/api/resources.
Requires a Plex token and internet access. Returns an empty list (never raises) when the token is missing or the call fails — e.g. offline, invalid token, or no servers on the account.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Plex account token used to authenticate against plex.tv. |
required |
timeout
|
float
|
HTTP timeout in seconds for the plex.tv request. |
5.0
|
Returns:
| Type | Description |
|---|---|
list[DiscoveredPlexServer]
|
One entry per best-ranked connection of each account server. |
discover_plex_servers(timeout=3.0, token=None)
¶
Discover Plex servers — local subnet via GDM/SSDP, plus the account.
With token set, also queries plex.tv/api/resources so servers on
other subnets (invisible to multicast) are included. Results are deduplicated
by address. Returns an empty list when nothing responds; never raises.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float
|
seconds to listen for local multicast replies; also the
plex.tv request timeout when |
3.0
|
token
|
str | None
|
optional Plex account token enabling cross-subnet discovery. |
None
|
Returns:
| Type | Description |
|---|---|
list[DiscoveredPlexServer]
|
Discovered servers sorted by |
Populate¶
musicseed.services.populate
¶
Populate service — fill an existing Plex playlist with complementary recommendations.
PopulateResult
¶
Bases: BaseModel
Result of a populate preview request.
PopulateApplyResult
¶
list_plex_playlists()
¶
Return every audio playlist currently on the Plex server.
Returns:
| Type | Description |
|---|---|
list[Playlist]
|
All audio playlists on the server. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
PlexAPIError
|
if the Plex API call fails. |
get_populate_recommendations(playlist_id, *, method='average', limit=10, per_seed_limit=30, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None)
¶
Preview complementary recommendations for an existing Plex playlist.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
playlist_id
|
str
|
Plex rating key of the playlist to populate. |
required |
method
|
PopulateMethod
|
populate strategy (see |
'average'
|
limit
|
int
|
maximum number of recommendations to return. |
10
|
per_seed_limit
|
int
|
candidates gathered per playlist track ("frequency" method only). |
30
|
weights
|
Weights | None
|
signal weights; defaults to |
None
|
year_min
|
int | None
|
only recommend tracks released in this year or later. |
None
|
year_max
|
int | None
|
only recommend tracks released in this year or earlier. |
None
|
max_tracks_per_artist
|
int
|
artist diversity cap applied during selection. |
3
|
min_score
|
float | None
|
drop recommendations with a total score below this value. |
None
|
Returns:
| Type | Description |
|---|---|
PopulateResult
|
The playlist identity, how many of its tracks matched the local |
PopulateResult
|
library, and the previewed recommendations. Nothing is written to |
PopulateResult
|
Plex. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if the playlist doesn't exist or none of its tracks are in the local library. |
PlexAPIError
|
if the Plex API call fails. |
populate_playlist(playlist_id, *, method='average', limit=10, per_seed_limit=30, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None, track_ids=None)
¶
Generate recommendations and add them to an existing Plex playlist.
When track_ids is provided, only those local track ids are added (the
recommendation step is skipped). This supports surfaces that let a user
prune a preview before confirming.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
playlist_id
|
str
|
Plex rating key of the playlist to populate. |
required |
method
|
PopulateMethod
|
populate strategy (see |
'average'
|
limit
|
int
|
maximum number of recommendations to add. |
10
|
per_seed_limit
|
int
|
candidates gathered per playlist track ("frequency" method only). |
30
|
weights
|
Weights | None
|
signal weights; defaults to |
None
|
year_min
|
int | None
|
only recommend tracks released in this year or later. |
None
|
year_max
|
int | None
|
only recommend tracks released in this year or earlier. |
None
|
max_tracks_per_artist
|
int
|
artist diversity cap applied during selection. |
3
|
min_score
|
float | None
|
drop recommendations with a total score below this value. |
None
|
track_ids
|
list[int] | None
|
explicit local track ids to add instead of recommending. |
None
|
Returns:
| Type | Description |
|---|---|
PopulateApplyResult
|
The playlist identity, match counts, the recommendations (empty when |
PopulateApplyResult
|
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if the playlist or its local tracks cannot be resolved. |
PlexAPIError
|
if the Plex API call fails. |
Plex Analysis¶
musicseed.services.plex_analysis
¶
Plex sonic analysis service — inspect and trigger Plex's own sonic analysis.
Keeps the Plex sonic vectors that MusicSeed reads at query time (see
musicseed.sonic) up to date:
- Which tracks has Plex already analyzed sonically (
musicAnalysisVersion)? - Trigger the
MusicAnalysisButler task on demand (POST /butler/…) and watch a date-scoped window of recently added music until it is analyzed.
Note: the Butler task always processes Plex's entire pending backlog; the date window scopes what we target, watch, and report — not what Plex runs.
UnanalyzedAlbum
¶
Bases: BaseModel
An album that has at least one track without Plex sonic analysis.
SonicStatusResult
¶
SonicTriggerProbeResult
¶
Bases: BaseModel
Outcome of triggering Plex analysis on one album and re-checking it.
sonic_triggered
property
¶
True when the analyzed-track count increased after the trigger.
SonicRefreshResult
¶
get_sonic_status(library_name=None, *, recent_days=7)
¶
Report Plex sonic analysis coverage for a music library.
Fetches every track in the library over the HTTP API, so it can be slow on
large libraries. recent_days defines the "recent additions" window
(based on Plex's addedAt).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
library_name
|
str | None
|
Plex music library to inspect; defaults to the
configured |
None
|
recent_days
|
int
|
size of the "recent additions" window in days. |
7
|
Returns:
| Type | Description |
|---|---|
SonicStatusResult
|
Coverage counts for the whole library and the recent window, plus the |
SonicStatusResult
|
albums that still have unanalyzed tracks. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if the library name doesn't match a music section. |
PlexAPIError
|
if the Plex API call fails. |
refresh_album(album_rating_key)
¶
Ask Plex to refresh one album's metadata (re-reads its files from disk).
This recreates the album's media items, which clears the failed-analysis state that prevents sonic analysis from being queued.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
album_rating_key
|
str
|
Plex rating key of the album to refresh. |
required |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
PlexAPIError
|
if the Plex API call fails. |
probe_sonic_trigger(album_rating_key=None, library_name=None, *, wait_seconds=120.0, poll_interval=5.0)
¶
Trigger Plex analysis on one album and check if sonic analysis follows.
If album_rating_key is None, the most recently added album with
unanalyzed tracks is picked automatically. Polls the album's tracks until
the number of sonically analyzed tracks increases or wait_seconds
elapses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
album_rating_key
|
str | None
|
Plex rating key of the album to analyze and watch; auto-picked when None. |
None
|
library_name
|
str | None
|
library used for the auto-pick; defaults to the
configured |
None
|
wait_seconds
|
float
|
maximum time to watch for analysis. |
120.0
|
poll_interval
|
float
|
seconds between polls. |
5.0
|
Returns:
| Type | Description |
|---|---|
SonicTriggerProbeResult
|
The probe outcome, including analyzed counts before/after and the |
SonicTriggerProbeResult
|
Plex activities observed while waiting. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if no unanalyzed album can be found. |
PlexAPIError
|
if the Plex API call fails. |
probe_butler_trigger(album_rating_key=None, library_name=None, *, butler_task='MusicAnalysis', wait_seconds=120.0, poll_interval=5.0)
¶
Trigger a Plex Butler task and check if sonic analysis follows.
Unlike per-item analyze, the MusicAnalysis Butler task works
through every album pending sonic analysis on the server, which can be
CPU-heavy and long-running — it keeps going after this probe returns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
album_rating_key
|
str | None
|
Plex rating key of the album to watch; auto-picked when None. |
None
|
library_name
|
str | None
|
library used for the auto-pick; defaults to the
configured |
None
|
butler_task
|
str
|
name of the Butler task to run. |
'MusicAnalysis'
|
wait_seconds
|
float
|
maximum time to watch for analysis. |
120.0
|
poll_interval
|
float
|
seconds between polls. |
5.0
|
Returns:
| Type | Description |
|---|---|
SonicTriggerProbeResult
|
The probe outcome for the watched album, including analyzed counts |
SonicTriggerProbeResult
|
before/after and the Plex activities observed while waiting. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if no unanalyzed album can be found. |
PlexAPIError
|
if the Plex API call fails. |
refresh_sonic_analysis(library_name=None, *, days=7, wait_seconds=900.0, poll_interval=15.0, stall_after=4, on_poll=None)
¶
Refresh sonic analysis for music added in the last days days.
Triggers the Plex MusicAnalysis Butler task and watches the window's
pending track count until it reaches zero, stalls (no progress across
stall_after consecutive polls), or wait_seconds elapses. The Butler
task keeps running on the server after this function returns.
on_poll(pending_now, pending_before) is called after each poll so a
surface can render progress.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
library_name
|
str | None
|
Plex music library to refresh; defaults to the
configured |
None
|
days
|
int
|
size of the "recent additions" window in days. |
7
|
wait_seconds
|
float
|
maximum time to watch the refresh. |
900.0
|
poll_interval
|
float
|
seconds between polls. |
15.0
|
stall_after
|
int
|
consecutive polls without progress before giving up. |
4
|
on_poll
|
Callable[[int, int], None] | None
|
optional |
None
|
Returns:
| Type | Description |
|---|---|
SonicRefreshResult
|
The refresh outcome, including pending counts before/after, whether a |
SonicRefreshResult
|
stall was detected, and the albums still pending in the window. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if the library name doesn't match a music section. |
PlexAPIError
|
if the Plex API call fails. |
Dashboard¶
musicseed.services.dashboard
¶
Dashboard aggregation service — combines discovery, library stats, and job status into one surface-agnostic snapshot.
DashboardSnapshot
¶
Bases: BaseModel
Combined dashboard view: discovery, library stats, and job state.
ready_for_recommendations
property
¶
True when at least one track has been imported locally.
get_dashboard(check_server=False)
¶
Aggregate a dashboard snapshot.
check_server gates the live Plex HTTP probe inside discovery. It
defaults to False so frequent dashboard polls never touch Plex; the
caller (a web surface) fetches the full probe separately and less often.
When the library status cannot be read (e.g. no database yet), an empty
LibraryStatus is used instead of failing the whole snapshot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
check_server
|
bool
|
whether to probe the Plex server over HTTP as part of the embedded discovery result. |
False
|
Returns:
| Type | Description |
|---|---|
DashboardSnapshot
|
A snapshot with discovery, library status, active and recent jobs, |
DashboardSnapshot
|
and the last successful import ( |
DashboardSnapshot
|
has succeeded yet). |
Jobs¶
musicseed.services.jobs
¶
Persistent, in-process job abstraction for long-running MusicSeed work.
Every operation (create, start, update progress, complete, fail,
cancel-request) writes through a dedicated SQLAlchemy session. The
JobManager singleton runs workers in daemon threads — no external
queue, no Redis, no containers. On first access it reconciles any jobs
left in a running state from a prior process into interrupted.
JobKind
¶
Bases: StrEnum
The kinds of long-running work the job system tracks.
JobState
¶
Bases: StrEnum
Lifecycle states of a job row.
JobManager
¶
In-process runner with a bounded concurrency pool.
Workers are daemon threads. Job state lives in the jobs table (shared
across processes); the in-memory bookkeeping only tracks this process's
threads and concurrency. Cancel is cooperative (should_cancel reads the
DB — workers poll it at safe checkpoints).
__init__(max_concurrent=2)
¶
Create a manager that runs at most max_concurrent jobs at once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_concurrent
|
int
|
maximum number of worker threads allowed to be active simultaneously; further submissions are rejected. |
2
|
submit(kind, target, *args, **kwargs)
¶
Create a job and run target for it in a daemon thread.
The target is called as target(job_id, *args, **kwargs) — the job
id is always the first positional argument.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
job kind (see |
required |
target
|
Callable[..., None]
|
blocking callable to run in the worker thread. |
required |
*args
|
Any
|
extra positional arguments forwarded to |
()
|
**kwargs
|
Any
|
keyword arguments forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
int
|
The id of the newly created job row. |
Raises:
| Type | Description |
|---|---|
JobConflictError
|
if a job of the same kind is already active, or the concurrency pool is full. |
request_cancel(job_id)
¶
Ask a job to stop (cooperative; see request_cancel).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job to cancel. |
required |
should_cancel(job_id)
¶
Return True when cancellation has been requested for a job.
Workers poll this at safe checkpoints and then wind down on their own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the job exists and its state is |
shutdown()
¶
Request cancellation of every active job (threads are daemons).
create_job(kind)
¶
Insert a new pending job and return its id.
start_job(job_id)
¶
Mark a job running and stamp its start time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to update. Unknown ids are ignored. |
required |
update_progress(job_id, current, total=0, checkpoint='', phases=None)
¶
Record progress for a running job.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to update. Unknown ids are ignored. |
required |
current
|
int
|
units of work completed so far. |
required |
total
|
int
|
total units of work expected (0 when unknown). |
0
|
checkpoint
|
str
|
human-readable status line; only stored when non-empty. |
''
|
phases
|
dict | None
|
per-phase |
None
|
complete_job(job_id, result_summary='')
¶
Mark a job succeeded and stamp its completion time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to update. Unknown ids are ignored. |
required |
result_summary
|
str
|
optional JSON-serialized outcome summary; only stored when non-empty. |
''
|
fail_job(job_id, error_summary)
¶
Mark a job failed and stamp its completion time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to update. Unknown ids are ignored. |
required |
error_summary
|
str
|
failure description, truncated to 500 characters. |
required |
request_cancel(job_id)
¶
Set a job's state to cancel_requested (cooperative cancellation).
The worker still has to observe the request (via
JobManager.should_cancel) and wind itself down; nothing is interrupted
forcibly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to update. Unknown ids are ignored. |
required |
cancel_job(job_id)
¶
Mark a job canceled and stamp its completion time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to update. Unknown ids are ignored. |
required |
get_job(job_id)
¶
Return a snapshot of one job, or None when it does not exist.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to read. |
required |
Returns:
| Type | Description |
|---|---|
dict | None
|
The job's fields as a plain dict, or None for an unknown id. |
delete_job(job_id)
¶
Delete a job row by id. Returns True if a row was deleted.
list_jobs(limit=20)
¶
Return the most recent jobs, newest first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
limit
|
int
|
maximum number of jobs to return. |
20
|
Returns:
| Type | Description |
|---|---|
list[dict]
|
Job snapshots as plain dicts, ordered by creation time descending. |
get_latest_job(kind)
¶
Return the most recent job of a given kind, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
job kind to filter on (see |
required |
Returns:
| Type | Description |
|---|---|
dict | None
|
The newest matching job snapshot as a plain dict, or None when no |
dict | None
|
job of that kind exists. |
get_active_jobs()
¶
Return all jobs in a non-terminal state (running or pending).
Returns:
| Type | Description |
|---|---|
list[dict]
|
Job snapshots as plain dicts. |
reconcile_running_jobs()
¶
Mark running jobs from dead processes as interrupted.
A job is only interrupted when its recorded owner pid is no longer alive, so starting one process while another genuinely runs a job leaves that job untouched.
get_manager()
¶
Return the module-level JobManager singleton, creating it lazily.
On first access, jobs left running by dead processes are reconciled
to interrupted before the manager is returned.
Returns:
| Type | Description |
|---|---|
JobManager
|
The shared |
Typeahead¶
musicseed.services.typeahead
¶
Track typeahead search service — the reusable lookup behind autocomplete.
TypeaheadTrack
¶
Bases: BaseModel
A minimal, JSON-safe view of a track for autocomplete results.
search_tracks(query, exclude_ids=None, limit=10)
¶
Search tracks by title or artist name for autocomplete.
Returns an empty list when the query is shorter than 2 characters. Results
exclude exclude_ids, are ordered by title, and capped at limit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
substring matched (case-insensitively) against track titles and artist names. |
required |
exclude_ids
|
list[int] | None
|
local track ids to leave out of the results. |
None
|
limit
|
int
|
maximum number of matches to return. |
10
|
Returns:
| Type | Description |
|---|---|
list[TypeaheadTrack]
|
Matching tracks as minimal JSON-safe views, ordered by title. |