Skip to content

Core Services

Surface-agnostic business logic from musicseed-core (musicseed.services.*). These services return result models and raise typed exceptions; CLI, API handlers, and MCP adapters map them. Database-backed operations accept an optional MusicSeedContext; see runtime and models for configuration, sessions, vector caching, and DTOs.

Recommendation results contain scalar ServiceTrack / ServiceRecommendation models, so they remain JSON-serializable after the service closes its session. For a preview/approval workflow, write the approved IDs with playlist_tracks.create_playlist_from_tracks or populate.populate_playlist(track_ids=...). recommend.create_playlist and populate without track_ids are separate generate-and-write paths; they must not replace an approved selection.

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.

complete property

True when the source completed and counts match the current snapshot.

setup_incomplete property

True when no import ever succeeded and coverage is not complete.

Surfaces use this to detect an interrupted first-time import that should be resumed.

initialize_database(context=None)

Create the SQLite database file and tables. Idempotent.

Parameters:

Name Type Description Default
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None

optimize_database(context=None)

Create performance indexes.

Parameters:

Name Type Description Default
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None

Returns:

Type Description
list[IndexResult]

Per-index results describing success or failure for each index.

import_library(plex_db_path=None, plex_db_ssh=None, library_name=None, full_import=False, progress_callback=None, should_cancel=None, context=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; overrides the configured source when given.

None
plex_db_ssh str | None

scp-style SSH target of a remote Plex database directory; overrides plex.db_ssh_target when given.

None
library_name str | None

Plex library to import; defaults to the configured plex.library.

None
full_import bool

re-import everything instead of an incremental import.

False
progress_callback Callable[[int, int, str], None] | None

optional (current, total, phase) callback invoked as the import progresses.

None
should_cancel Callable[[], bool] | None

optional callable polled by the importer; the import stops early when it returns True.

None
context MusicSeedContext | None

runtime context to use; defaults to the default context.

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(context=None)

Whether this source/library completed an import, independent of job history.

get_import_coverage(context=None)

Compare MusicSeed artist/album/track counts to the configured Plex library.

Parameters:

Name Type Description Default
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None

Returns:

Type Description
ImportCoverage | None

The coverage comparison, or None when the Plex database cannot be

ImportCoverage | None

read. Does not raise.

get_status(context=None)

Return library statistics and enrichment coverage.

Parameters:

Name Type Description Default
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None

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, method='average', per_seed_limit=30, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None, context=None)

Return seed tracks and scored recommendations.

Parameters:

Name Type Description Default
seed_texts list[str] | None

seed tracks as "Artist - Title" (or bare title) strings; at least one text or id seed is required.

None
seed_ids list[int] | None

seed tracks by local database id.

None
limit int

maximum number of recommendations to return.

50
method RecommendMethod

"average" (score against the seeds' combined profile) or "frequency" (per-seed votes ranked by average score).

'average'
per_seed_limit int

candidates gathered per seed ("frequency" method only).

30
weights Weights | None

signal weights; defaults to Weights() (the "balanced" preset).

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
context MusicSeedContext | None

runtime context to use; defaults to the default context.

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, context=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 "Artist - Title" (or bare title) strings; at least one text or id seed is required.

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 Weights().

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
context MusicSeedContext | None

runtime context to use; defaults to the default context.

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.

Approved playlist selections

musicseed.services.playlist_tracks

Write an approved ordered track selection, without recomputing recommendations.

PlaylistTracksResult

Bases: BaseModel

The created playlist and exact selected tracks, with no invented scores.

resolve_track_selection(session, track_ids)

Resolve every selected local ID in order; reject stale/unmapped selections.

Duplicates are removed in first-occurrence order. Missing tracks fail the entire selection before any Plex write rather than silently writing a subset.

create_playlist_from_tracks(name, track_ids, *, context=None)

Create a Plex playlist from exactly the approved local IDs, in order.

No recommendations are generated, and no score/filter policy is reapplied. Callers should include approved seed IDs in the selection when desired.

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, context=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, "spotify" or "listenbrainz".

'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 (current, total, message) callback.

None
should_cancel Callable[[], bool] | None

optional callable polled by the pipeline; enrichment stops early when it returns True.

None
context MusicSeedContext | None

runtime context to use; defaults to the default context.

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.

SonicVectorsDiscovery

Bases: BaseModel

Locally persisted Plex sonic vectors (imported via import_plex_sonic).

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 Preferences.xml; when omitted, the default Plex data dir candidates are probed.

None
local_admin_token_path str | None

explicit path to .LocalAdminToken; same default behavior as preferences_path.

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_db_ssh=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_db_ssh str | None

override for the scp-style SSH target (remote Plex).

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 skipped).

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 get_config().

None

Returns:

Type Description
DiscoveryResult

The complete discovery result, including per-check reason codes,

DiscoveryResult

enrichment readiness, missing inputs, the count of locally stored

DiscoveryResult

sonic vectors, and the derived first-run status. The Plex blobs

DiscoveryResult

database is reported but no longer blocks ready (it is only needed

DiscoveryResult

to import sonic vectors). 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:32414 with an SSDP fallback on 239.255.255.250:1900. Both use stdlib socket only 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/resources lists 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 token is set.

3.0
token str | None

optional Plex account token enabling cross-subnet discovery.

None

Returns:

Type Description
list[DiscoveredPlexServer]

Discovered servers sorted by (host, port, name).

Populate

musicseed.services.populate

Populate service — fill an existing Plex playlist with complementary recommendations.

PopulateResult

Bases: BaseModel

Result of a populate preview request.

PopulateApplyResult

Bases: PopulateResult

Result of a populate request that was written to Plex.

list_plex_playlists(context=None)

Return every audio playlist currently on the Plex server.

Parameters:

Name Type Description Default
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None

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, context=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 PopulateMethod).

'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 Weights().

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
context MusicSeedContext | None

runtime context to use; defaults to the default context.

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, context=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 PopulateMethod).

'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 Weights().

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
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None

Returns:

Type Description
PopulateApplyResult

The playlist identity, match counts, the recommendations (empty when

PopulateApplyResult

track_ids was given), and how many tracks were actually added.

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 Plex's own sonic analysis up to date so MusicSeed can import fresh vectors into its local store (see musicseed.sonic):

  1. Which tracks has Plex already analyzed sonically (musicAnalysisVersion)?
  2. Trigger the MusicAnalysis Butler 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

Bases: BaseModel

Sonic analysis coverage of a Plex music library.

unanalyzed_tracks property

Tracks in the library without Plex sonic analysis.

recent_unanalyzed_tracks property

Recently added tracks still without Plex sonic analysis.

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

Bases: BaseModel

Outcome of a date-scoped sonic analysis refresh.

analyzed_delta property

Tracks that gained sonic analysis during the refresh.

completed property

True when no tracks in the window remain unanalyzed.

get_sonic_status(library_name=None, *, recent_days=7, context=None)

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 plex.library.

None
recent_days int

size of the "recent additions" window in days.

7
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None

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, context=None)

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
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None

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, context=None)

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 plex.library.

None
wait_seconds float

maximum time to watch for analysis.

120.0
poll_interval float

seconds between polls.

5.0
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None

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, context=None)

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 plex.library.

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
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None

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, context=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 plex.library.

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 (pending_now, pending_before) progress callback.

None
context MusicSeedContext | None

runtime context to use; defaults to the default context.

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.

Local sonic vector import

musicseed.services.sonic_vectors

Sonic vector persistence — import Plex vectors into the local database.

MusicSeed reads Plex sonic-analysis vectors from Plex's blobs database and persists them into the local track_vectors table so recommendations no longer depend on that file at query time. The blobs database is only read by import_plex_sonic; scoring reads the local store via the context.

SonicVectorImportResult

Bases: BaseModel

Counts from one sonic-vector import run.

import_plex_sonic(context=None, progress_callback=None, should_cancel=None, *, batch_size=500)

Import Plex sonic-analysis vectors into the local database.

Reads the Plex blobs database once (via sonic.load_sonic_vectors) and upserts every vector into track_vectors keyed by plex_id. The operation is idempotent: existing rows are refreshed in place and new rows are inserted, so re-running after Plex analyzes more tracks is safe. After writing, the context's cached vectors are dropped so the next scoring run reads the fresh store.

Parameters:

Name Type Description Default
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None
progress_callback Callable[[int, int, str], None] | None

optional callback after each committed batch.

None
should_cancel Callable[[], bool] | None

polled before source reads and between committed batches. Source backup/decoding must finish before cancellation is checked again.

None
batch_size int

maximum vector upserts per committed transaction.

500

Returns:

Type Description
SonicVectorImportResult

The total number of vectors found in Plex and how many rows were newly

SonicVectorImportResult

inserted versus updated in place.

Raises:

Type Description
NotFoundError

if the Plex blobs database is unavailable.

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, context=None)

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
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None

Returns:

Type Description
DashboardSnapshot

A snapshot with discovery, library status, active and recent jobs,

DashboardSnapshot

and the last successful import (last_sync, None when no 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 jobs left in any nonterminal state by a dead 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=1)

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.

1

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 JobKind); only one active job of any kind is allowed across all processes sharing the database.

required
target Callable[..., None]

blocking callable to run in the worker thread.

required
*args Any

extra positional arguments forwarded to target.

()
**kwargs Any

keyword arguments forwarded to target.

{}

Returns:

Type Description
int

The id of the newly created job row.

Raises:

Type Description
JobConflictError

if a job is already active for this database, 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 cancel_requested.

shutdown()

Request cancellation of every active job (threads are daemons).

configuration_change()

Serialize config replacement with submissions; never initialize a database.

current_job_id()

The operation-bound job ID, if called from a claimed writer.

exclusive_writer(kind)

Use the same persisted writer claim for synchronous CLI/service imports.

A service called inside a managed worker reuses its claim. There is no separate queue or scheduler; the SQLite job row is the reservation.

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 {"current", "total"} snapshot for multi-phase jobs; only stored when not None.

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. Managed targets defer completion until they return.

''

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 canceled, retaining a managed worker's claim until its target exits.

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 JobKind).

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 pending/running/cancel-requested jobs; all still reserve the writer.

Returns:

Type Description
list[dict]

Job snapshots as plain dicts.

reconcile_running_jobs()

Reconcile every nonterminal state owned by a dead process.

Pending jobs and cancellation requests can also survive a crash. Live owners are left alone, including other API/CLI processes.

get_manager()

Return the module-level JobManager singleton, creating it lazily.

On first access, nonterminal jobs owned by dead processes are reconciled to interrupted before the manager is returned.

Returns:

Type Description
JobManager

The shared JobManager instance.

Import provenance

musicseed.services.import_state

Source-specific durable import checkpoints and read-only status queries.

source_key(context)

Identity excludes credentials and includes library, source and Plex server.

snapshot_id(path)

Identify the observed input generation, including local file changes.

read_import_state(context)

Read without initializing/migrating; unknown is not a successful import.

checkpoint(context, state, *, snapshot=None, expected=None, phase=None, processed=None)

Write a checkpoint outside the importer's write transaction.

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, context=None)

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
context MusicSeedContext | None

runtime context to use; defaults to the default context.

None

Returns:

Type Description
list[TypeaheadTrack]

Matching tracks as minimal JSON-safe views, ordered by title.

Offline evaluation

musicseed.services.evaluation

Deterministic offline recommendation evaluation using synthetic temporary libraries.

This is a developer diagnostic, not a listening-quality benchmark. It never loads the owner's config, database, Plex server, or enrichment credentials.

FixtureTrack

Bases: BaseModel

Public synthetic facts only; no private library exports or file paths.

EvaluationRequest

Bases: BaseModel

Scoring, eligibility and selection controls recorded alongside the metrics.

EvaluationCase

Bases: EvaluationRequest

Synthetic library facts and the complete recommendation request to evaluate.

StrategyMetrics

Bases: BaseModel

Constraint, fill, overlap and evidence metrics under one strategy's objective.

CaseEvaluation

Bases: BaseModel

Three strategy results and a bounded-retrieval diagnostic for one fixture.

EvaluationReport

Bases: BaseModel

Reproducible synthetic results; intentionally not a musical-preference verdict.

evaluation_cases(seed=7)

Nine versioned synthetic cases, reproducible from a nonnegative RNG seed.

evaluate_recommendations(*, seed=7, case_names=None)

Evaluate disposable synthetic libraries and return deterministic JSON-safe metrics.

Timestamps, elapsed times and temporary paths are intentionally absent. Retrieval gaps are observations, not safety-invariant failures. Native scores use different objectives across strategies and are not preference ratings. No owner library/context argument is accepted by this diagnostic.