Skip to content

Commit 98377ab

Browse files
committed
Split library file into different files inside library folder.
1 parent 3d364e4 commit 98377ab

File tree

5 files changed

+1035
-1020
lines changed

5 files changed

+1035
-1020
lines changed

beets/library/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from .exceptions import FileOperationError, ReadError, WriteError
2+
from .library import Library
3+
from .models import Album, Item, LibModel
4+
from .queries import parse_query_parts, parse_query_string
5+
6+
__all__ = [
7+
"Library",
8+
"LibModel",
9+
"Album",
10+
"Item",
11+
"parse_query_parts",
12+
"parse_query_string",
13+
"FileOperationError",
14+
"ReadError",
15+
"WriteError",
16+
]

beets/library/exceptions.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from beets import util
2+
3+
4+
class FileOperationError(Exception):
5+
"""Indicate an error when interacting with a file on disk.
6+
7+
Possibilities include an unsupported media type, a permissions
8+
error, and an unhandled Mutagen exception.
9+
"""
10+
11+
def __init__(self, path, reason):
12+
"""Create an exception describing an operation on the file at
13+
`path` with the underlying (chained) exception `reason`.
14+
"""
15+
super().__init__(path, reason)
16+
self.path = path
17+
self.reason = reason
18+
19+
def __str__(self):
20+
"""Get a string representing the error.
21+
22+
Describe both the underlying reason and the file path in question.
23+
"""
24+
return f"{util.displayable_path(self.path)}: {self.reason}"
25+
26+
27+
class ReadError(FileOperationError):
28+
"""An error while reading a file (i.e. in `Item.read`)."""
29+
30+
def __str__(self):
31+
return "error reading " + str(super())
32+
33+
34+
class WriteError(FileOperationError):
35+
"""An error while writing a file (i.e. in `Item.write`)."""
36+
37+
def __str__(self):
38+
return "error writing " + str(super())

beets/library/library.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
import platformdirs
6+
7+
import beets
8+
from beets import dbcore
9+
from beets.util import normpath
10+
11+
from .models import Album, Item
12+
from .queries import PF_KEY_DEFAULT, parse_query_parts, parse_query_string
13+
14+
if TYPE_CHECKING:
15+
from beets.dbcore import Results
16+
17+
18+
class Library(dbcore.Database):
19+
"""A database of music containing songs and albums."""
20+
21+
_models = (Item, Album)
22+
23+
def __init__(
24+
self,
25+
path="library.blb",
26+
directory: str | None = None,
27+
path_formats=((PF_KEY_DEFAULT, "$artist/$album/$track $title"),),
28+
replacements=None,
29+
):
30+
timeout = beets.config["timeout"].as_number()
31+
super().__init__(path, timeout=timeout)
32+
33+
self.directory = normpath(directory or platformdirs.user_music_path())
34+
35+
self.path_formats = path_formats
36+
self.replacements = replacements
37+
38+
# Used for template substitution performance.
39+
self._memotable: dict[tuple[str, ...], str] = {}
40+
41+
# Adding objects to the database.
42+
43+
def add(self, obj):
44+
"""Add the :class:`Item` or :class:`Album` object to the library
45+
database.
46+
47+
Return the object's new id.
48+
"""
49+
obj.add(self)
50+
self._memotable = {}
51+
return obj.id
52+
53+
def add_album(self, items):
54+
"""Create a new album consisting of a list of items.
55+
56+
The items are added to the database if they don't yet have an
57+
ID. Return a new :class:`Album` object. The list items must not
58+
be empty.
59+
"""
60+
if not items:
61+
raise ValueError("need at least one item")
62+
63+
# Create the album structure using metadata from the first item.
64+
values = {key: items[0][key] for key in Album.item_keys}
65+
album = Album(self, **values)
66+
67+
# Add the album structure and set the items' album_id fields.
68+
# Store or add the items.
69+
with self.transaction():
70+
album.add(self)
71+
for item in items:
72+
item.album_id = album.id
73+
if item.id is None:
74+
item.add(self)
75+
else:
76+
item.store()
77+
78+
return album
79+
80+
# Querying.
81+
82+
def _fetch(self, model_cls, query, sort=None):
83+
"""Parse a query and fetch.
84+
85+
If an order specification is present in the query string
86+
the `sort` argument is ignored.
87+
"""
88+
# Parse the query, if necessary.
89+
try:
90+
parsed_sort = None
91+
if isinstance(query, str):
92+
query, parsed_sort = parse_query_string(query, model_cls)
93+
elif isinstance(query, (list, tuple)):
94+
query, parsed_sort = parse_query_parts(query, model_cls)
95+
except dbcore.query.InvalidQueryArgumentValueError as exc:
96+
raise dbcore.InvalidQueryError(query, exc)
97+
98+
# Any non-null sort specified by the parsed query overrides the
99+
# provided sort.
100+
if parsed_sort and not isinstance(parsed_sort, dbcore.query.NullSort):
101+
sort = parsed_sort
102+
103+
return super()._fetch(model_cls, query, sort)
104+
105+
@staticmethod
106+
def get_default_album_sort():
107+
"""Get a :class:`Sort` object for albums from the config option."""
108+
return dbcore.sort_from_strings(
109+
Album, beets.config["sort_album"].as_str_seq()
110+
)
111+
112+
@staticmethod
113+
def get_default_item_sort():
114+
"""Get a :class:`Sort` object for items from the config option."""
115+
return dbcore.sort_from_strings(
116+
Item, beets.config["sort_item"].as_str_seq()
117+
)
118+
119+
def albums(self, query=None, sort=None) -> Results[Album]:
120+
"""Get :class:`Album` objects matching the query."""
121+
return self._fetch(Album, query, sort or self.get_default_album_sort())
122+
123+
def items(self, query=None, sort=None) -> Results[Item]:
124+
"""Get :class:`Item` objects matching the query."""
125+
return self._fetch(Item, query, sort or self.get_default_item_sort())
126+
127+
# Convenience accessors.
128+
129+
def get_item(self, id):
130+
"""Fetch a :class:`Item` by its ID.
131+
132+
Return `None` if no match is found.
133+
"""
134+
return self._get(Item, id)
135+
136+
def get_album(self, item_or_id):
137+
"""Given an album ID or an item associated with an album, return
138+
a :class:`Album` object for the album.
139+
140+
If no such album exists, return `None`.
141+
"""
142+
if isinstance(item_or_id, int):
143+
album_id = item_or_id
144+
else:
145+
album_id = item_or_id.album_id
146+
if album_id is None:
147+
return None
148+
return self._get(Album, album_id)

0 commit comments

Comments
 (0)