diff --git a/beets/library/models.py b/beets/library/models.py index efa0f9694..68c80b934 100644 --- a/beets/library/models.py +++ b/beets/library/models.py @@ -45,6 +45,11 @@ class LibModel(dbcore.Model["Library"]): def writable_media_fields(cls) -> set[str]: return set(MediaFile.fields()) & cls._fields.keys() + @property + def filepath(self) -> Path: + """The path to the entity as pathlib.Path.""" + return Path(os.fsdecode(self.path)) + def _template_funcs(self): funcs = DefaultTemplateFunctions(self, self._db).functions() funcs.update(plugins.template_funcs()) @@ -207,6 +212,8 @@ class Album(LibModel): Reflects the library's "albums" table, including album art. """ + artpath: bytes + _table = "albums" _flex_table = "album_attributes" _always_dirty = True @@ -331,6 +338,11 @@ class Album(LibModel): f"ON {cls._table}.id = {cls._relation._table}.album_id" ) + @property + def art_filepath(self) -> Path | None: + """The path to album's cover picture as pathlib.Path.""" + return Path(os.fsdecode(self.artpath)) if self.artpath else None + @classmethod def _getters(cls): # In addition to plugin-provided computed fields, also expose @@ -748,11 +760,6 @@ class Item(LibModel): f"ON {cls._table}.album_id = {cls._relation._table}.id" ) - @property - def filepath(self) -> Path: - """The path to the item's file as pathlib.Path.""" - return Path(os.fsdecode(self.path)) - @property def _cached_album(self): """The Album object that this item belongs to, if any, or diff --git a/test/plugins/test_art.py b/test/plugins/test_art.py index 6577b54fc..9f5817108 100644 --- a/test/plugins/test_art.py +++ b/test/plugins/test_art.py @@ -19,6 +19,7 @@ from __future__ import annotations import os import shutil import unittest +from pathlib import Path from typing import TYPE_CHECKING from unittest.mock import patch @@ -804,12 +805,10 @@ class ArtImporterTest(UseThePlugin): self.plugin.fetch_art(self.session, self.task) self.plugin.assign_art(self.session, self.task) - artpath = self.lib.albums()[0].artpath + artpath = self.lib.albums()[0].art_filepath if should_exist: - assert artpath == os.path.join( - os.path.dirname(self.i.path), b"cover.jpg" - ) - self.assertExists(artpath) + assert artpath == self.i.filepath.parent / "cover.jpg" + assert artpath.exists() else: assert artpath is None return artpath @@ -861,7 +860,7 @@ class ArtImporterTest(UseThePlugin): self.plugin.batch_fetch_art( self.lib, self.lib.albums(), force=False, quiet=False ) - self.assertExists(self.album.artpath) + assert self.album.art_filepath.exists() class ArtForAlbumTest(UseThePlugin): diff --git a/test/plugins/test_embedart.py b/test/plugins/test_embedart.py index 2cada1d5b..1b8528cb7 100644 --- a/test/plugins/test_embedart.py +++ b/test/plugins/test_embedart.py @@ -203,23 +203,21 @@ class EmbedartCliTest(IOMixin, PluginMixin, FetchImageHelper, BeetsTestCase): resource_path = os.path.join(_common.RSRC, b"image.mp3") album = self.add_album_fixture() trackpath = album.items()[0].path - albumpath = album.path shutil.copy(syspath(resource_path), syspath(trackpath)) self.run_command("extractart", "-n", "extracted") - self.assertExists(os.path.join(albumpath, b"extracted.png")) + self.assertExists(album.filepath / "extracted.png") def test_extracted_extension(self): resource_path = os.path.join(_common.RSRC, b"image-jpeg.mp3") album = self.add_album_fixture() trackpath = album.items()[0].path - albumpath = album.path shutil.copy(syspath(resource_path), syspath(trackpath)) self.run_command("extractart", "-n", "extracted") - self.assertExists(os.path.join(albumpath, b"extracted.jpg")) + self.assertExists(album.filepath / "extracted.jpg") def test_clear_art_with_yes_input(self): self._setup_data() diff --git a/test/test_files.py b/test/test_files.py index 8be94f328..55865f4d4 100644 --- a/test/test_files.py +++ b/test/test_files.py @@ -19,6 +19,7 @@ import shutil import stat import unittest from os.path import join +from pathlib import Path import pytest @@ -314,9 +315,10 @@ class ArtFileTest(BeetsTestCase): # Make an album. self.ai = self.lib.add_album((self.i,)) # Make an art file too. - self.art = self.lib.get_album(self.i).art_destination("something.jpg") - touch(self.art) - self.ai.artpath = self.art + art_bytes = self.lib.get_album(self.i).art_destination("something.jpg") + self.art = Path(os.fsdecode(art_bytes)) + self.art.touch() + self.ai.artpath = art_bytes self.ai.store() # Alternate destination dir. self.otherdir = os.path.join(self.temp_dir, b"testotherdir") @@ -345,10 +347,10 @@ class ArtFileTest(BeetsTestCase): self.i.load() # Art should be in new directory. - self.assertNotExists(self.art) - newart = self.lib.get_album(self.i).artpath - self.assertExists(newart) - assert b"testotherdir" in newart + assert not self.art.exists() + newart = self.lib.get_album(self.i).art_filepath + assert newart.exists() + assert "testotherdir" in str(newart) def test_setart_copies_image(self): util.remove(self.art) @@ -363,7 +365,7 @@ class ArtFileTest(BeetsTestCase): assert ai.artpath is None ai.set_art(newart) - self.assertExists(ai.artpath) + assert ai.art_filepath.exists() def test_setart_to_existing_art_works(self): util.remove(self.art) @@ -380,7 +382,7 @@ class ArtFileTest(BeetsTestCase): # Set the art again. ai.set_art(ai.artpath) - self.assertExists(ai.artpath) + assert ai.art_filepath.exists() def test_setart_to_existing_but_unset_art_works(self): newart = os.path.join(self.libdir, b"newart.jpg") @@ -397,7 +399,7 @@ class ArtFileTest(BeetsTestCase): # Set the art again. ai.set_art(artdest) - self.assertExists(ai.artpath) + assert ai.art_filepath.exists() def test_setart_to_conflicting_file_gets_new_path(self): newart = os.path.join(self.libdir, b"newart.jpg") @@ -442,34 +444,34 @@ class ArtFileTest(BeetsTestCase): os.chmod(syspath(ai.artpath), 0o777) def test_move_last_file_moves_albumart(self): - oldartpath = self.lib.albums()[0].artpath - self.assertExists(oldartpath) + oldartpath = self.lib.albums()[0].art_filepath + assert oldartpath.exists() self.ai.album = "different_album" self.ai.store() self.ai.items()[0].move() - artpath = self.lib.albums()[0].artpath - assert b"different_album" in artpath - self.assertExists(artpath) - self.assertNotExists(oldartpath) + artpath = self.lib.albums()[0].art_filepath + assert "different_album" in str(artpath) + assert artpath.exists() + assert not oldartpath.exists() def test_move_not_last_file_does_not_move_albumart(self): i2 = item() i2.albumid = self.ai.id self.lib.add(i2) - oldartpath = self.lib.albums()[0].artpath - self.assertExists(oldartpath) + oldartpath = self.lib.albums()[0].art_filepath + assert oldartpath.exists() self.i.album = "different_album" self.i.album_id = None # detach from album self.i.move() - artpath = self.lib.albums()[0].artpath - assert b"different_album" not in artpath + artpath = self.lib.albums()[0].art_filepath + assert "different_album" not in str(artpath) assert artpath == oldartpath - self.assertExists(oldartpath) + assert oldartpath.exists() class RemoveTest(BeetsTestCase): diff --git a/test/test_importer.py b/test/test_importer.py index 3e362a179..c23b56d7a 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -23,6 +23,7 @@ import sys import unicodedata import unittest from io import StringIO +from pathlib import Path from tarfile import TarFile from tempfile import mkstemp from unittest.mock import Mock, patch @@ -1566,14 +1567,14 @@ class ReimportTest(AutotagImportTestCase): replaced_album = self._album() replaced_album.set_art(art_source) replaced_album.store() - old_artpath = replaced_album.artpath + old_artpath = replaced_album.art_filepath self.importer.run() new_album = self._album() new_artpath = new_album.art_destination(art_source) assert new_album.artpath == new_artpath - self.assertExists(new_artpath) + assert new_album.art_filepath.exists() if new_artpath != old_artpath: - self.assertNotExists(old_artpath) + assert not old_artpath.exists() def test_reimported_album_has_new_flexattr(self): self._setup_session() diff --git a/test/test_ui.py b/test/test_ui.py index 8c93a83ea..7a394a3d9 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -601,12 +601,12 @@ class UpdateTest(IOMixin, BeetsTestCase): assert not self.lib.albums() def test_delete_removes_album_art(self): - artpath = self.album.artpath - self.assertExists(artpath) + art_filepath = self.album.art_filepath + assert art_filepath.exists() util.remove(self.i.path) util.remove(self.i2.path) self._update() - self.assertNotExists(artpath) + assert not art_filepath.exists() def test_modified_metadata_detected(self): mf = MediaFile(syspath(self.i.path))