feat(library): lazy materialization foundation for remote tracks (§Phase1)
Adds nullable storage fields + availability column on tracks, remote source/source_id identity on albums/artists, TrackRepository.materialize() and get_or_create_remote() repos — groundwork for on-demand YTM library (placeholders saved without audio, materialized in-place on first play). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,5 +13,7 @@ class Album:
|
||||
year: int | None
|
||||
cover_path: str | None
|
||||
musicbrainz_id: str | None
|
||||
source: str | None
|
||||
source_id: str | None
|
||||
created_at: dt.datetime
|
||||
updated_at: dt.datetime
|
||||
|
||||
@@ -9,6 +9,8 @@ from dataclasses import dataclass
|
||||
class Artist:
|
||||
id: uuid.UUID
|
||||
name: str
|
||||
source: str | None
|
||||
source_id: str | None
|
||||
created_at: dt.datetime
|
||||
updated_at: dt.datetime
|
||||
|
||||
@@ -19,9 +21,9 @@ class Track:
|
||||
title: str
|
||||
artist_id: uuid.UUID
|
||||
album_id: uuid.UUID | None
|
||||
storage_uri: str
|
||||
file_format: str
|
||||
file_size: int
|
||||
storage_uri: str | None
|
||||
file_format: str | None
|
||||
file_size: int | None
|
||||
source: str
|
||||
source_id: str
|
||||
duration_seconds: int | None
|
||||
@@ -31,5 +33,6 @@ class Track:
|
||||
metadata_status: str
|
||||
metadata_error: str | None
|
||||
enriched_at: dt.datetime | None
|
||||
availability: str
|
||||
created_at: dt.datetime
|
||||
updated_at: dt.datetime
|
||||
|
||||
+36
-3
@@ -114,6 +114,11 @@ class FileStorage(Protocol):
|
||||
|
||||
class ArtistRepository(Protocol):
|
||||
async def get_or_create(self, name: str) -> Artist: ...
|
||||
async def get_or_create_remote(self, *, name: str, source: str, source_id: str) -> Artist:
|
||||
"""Resolve/create an artist bound to a remote ``(source, source_id)``
|
||||
(lazy materialization save-to-library)."""
|
||||
...
|
||||
|
||||
async def get_by_id(self, artist_id: uuid.UUID) -> Artist | None: ...
|
||||
async def get_many(self, ids: list[uuid.UUID]) -> list[Artist]: ...
|
||||
async def list(self, *, q: str | None, limit: int, offset: int) -> list[Artist]: ...
|
||||
@@ -131,14 +136,28 @@ class TrackRepository(Protocol):
|
||||
id: uuid.UUID,
|
||||
title: str,
|
||||
artist_id: uuid.UUID,
|
||||
storage_uri: str,
|
||||
file_format: str,
|
||||
file_size: int,
|
||||
storage_uri: str | None,
|
||||
file_format: str | None,
|
||||
file_size: int | None,
|
||||
source: str,
|
||||
source_id: str,
|
||||
metadata_status: str,
|
||||
added_by: uuid.UUID | None,
|
||||
availability: str = ...,
|
||||
) -> Track: ...
|
||||
async def materialize(
|
||||
self,
|
||||
track_id: uuid.UUID,
|
||||
*,
|
||||
storage_uri: str,
|
||||
file_format: str,
|
||||
file_size: int,
|
||||
bitrate: int | None,
|
||||
) -> Track:
|
||||
"""Fill in a remote placeholder's audio fields after a download
|
||||
(lazy materialization), flipping ``availability`` to ``local``."""
|
||||
...
|
||||
|
||||
async def delete(self, track_id: uuid.UUID) -> None: ...
|
||||
# genres / library_stats must come before ``list`` — the method named
|
||||
# ``list`` shadows the builtin in later annotations (same pattern as
|
||||
@@ -212,6 +231,20 @@ class AlbumRepository(Protocol):
|
||||
year: int | None,
|
||||
musicbrainz_id: str | None,
|
||||
) -> Album: ...
|
||||
async def get_or_create_remote(
|
||||
self,
|
||||
*,
|
||||
title: str,
|
||||
artist_id: uuid.UUID,
|
||||
year: int | None,
|
||||
musicbrainz_id: str | None,
|
||||
source: str,
|
||||
source_id: str,
|
||||
) -> Album:
|
||||
"""Resolve/create an album bound to a remote ``(source, source_id)``
|
||||
(lazy materialization save-to-library)."""
|
||||
...
|
||||
|
||||
async def set_cover_path(self, album_id: uuid.UUID, cover_path: str) -> None: ...
|
||||
async def get_by_id(self, album_id: uuid.UUID) -> Album | None: ...
|
||||
async def get_many(self, ids: list[uuid.UUID]) -> list[Album]: ...
|
||||
|
||||
Reference in New Issue
Block a user