Update dependencies and do not install reflink on Windows for tests (#5407)

See my comment under #5406 for context

> The build on win32 is failing to install reflink because it's [only
supported until Python
3.7](https://gitlab.com/rubdos/pyreflink/-/blob/master/setup.py?ref_type=heads).
>
> I will address this in a separate PR and rebase this one accordingly
once the fix is merged.
>
> Note: this issue popped up now because I added a new requests-mock
dependency which invalidated cached dependencies.
This commit is contained in:
Šarūnas Nejus 2024-09-08 12:31:02 +01:00 committed by GitHub
commit 54b2435c72
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 803 additions and 742 deletions

View file

@ -34,7 +34,7 @@ jobs:
run: | run: |
sudo apt update sudo apt update
sudo apt install ffmpeg gobject-introspection libgirepository1.0-dev sudo apt install ffmpeg gobject-introspection libgirepository1.0-dev
poetry install --extras replaygain poetry install --extras=replaygain --extras=reflink
- name: Install Python dependencies - name: Install Python dependencies
run: poetry install --only=main,test --extras=autobpm run: poetry install --only=main,test --extras=autobpm

View file

@ -16,7 +16,6 @@
import os import os
import sys import sys
import tempfile
import unittest import unittest
from contextlib import contextmanager from contextlib import contextmanager
@ -66,13 +65,6 @@ _item_ident = 0
HAVE_SYMLINK = sys.platform != "win32" HAVE_SYMLINK = sys.platform != "win32"
HAVE_HARDLINK = sys.platform != "win32" HAVE_HARDLINK = sys.platform != "win32"
try:
import reflink
HAVE_REFLINK = reflink.supported_at(tempfile.gettempdir())
except ImportError:
HAVE_REFLINK = False
def item(lib=None): def item(lib=None):
global _item_ident global _item_ident

View file

@ -42,7 +42,7 @@ from enum import Enum
from functools import cached_property from functools import cached_property
from io import StringIO from io import StringIO
from pathlib import Path from pathlib import Path
from tempfile import mkdtemp, mkstemp from tempfile import gettempdir, mkdtemp, mkstemp
from typing import Any, ClassVar from typing import Any, ClassVar
from unittest.mock import patch from unittest.mock import patch
@ -147,6 +147,20 @@ def has_program(cmd, args=["--version"]):
return True return True
def check_reflink_support(path: str) -> bool:
try:
import reflink
except ImportError:
return False
return reflink.supported_at(path)
NEEDS_REFLINK = unittest.skipUnless(
check_reflink_support(gettempdir()), "no reflink support for libdir"
)
class TestHelper(_common.Assertions): class TestHelper(_common.Assertions):
"""Helper mixin for high-level cli and plugin tests. """Helper mixin for high-level cli and plugin tests.

View file

@ -13,6 +13,7 @@
# included in all copies or substantial portions of the Software. # included in all copies or substantial portions of the Software.
"""Miscellaneous utility functions.""" """Miscellaneous utility functions."""
from __future__ import annotations from __future__ import annotations
import errno import errno
@ -29,6 +30,7 @@ import traceback
from collections import Counter from collections import Counter
from contextlib import suppress from contextlib import suppress
from enum import Enum from enum import Enum
from importlib import import_module
from logging import Logger from logging import Logger
from multiprocessing.pool import ThreadPool from multiprocessing.pool import ThreadPool
from pathlib import Path from pathlib import Path
@ -615,31 +617,33 @@ def reflink(
Raise an `OSError` if `dest` already exists, unless `replace` is Raise an `OSError` if `dest` already exists, unless `replace` is
True. If `path` == `dest`, then do nothing. True. If `path` == `dest`, then do nothing.
If reflinking fails and `fallback` is enabled, try copying the file If `fallback` is enabled, ignore errors and copy the file instead.
instead. Otherwise, raise an error without trying a plain copy. Otherwise, errors are re-raised as FilesystemError with an explanation.
May raise an `ImportError` if the `reflink` module is not available.
""" """
import reflink as pyreflink
if samefile(path, dest): if samefile(path, dest):
return return
if os.path.exists(syspath(dest)) and not replace: if os.path.exists(syspath(dest)) and not replace:
raise FilesystemError("file exists", "rename", (path, dest)) raise FilesystemError("target exists", "rename", (path, dest))
if fallback:
with suppress(Exception):
return import_module("reflink").reflink(path, dest)
return copy(path, dest, replace)
try: try:
pyreflink.reflink(path, dest) import_module("reflink").reflink(path, dest)
except (NotImplementedError, pyreflink.ReflinkImpossibleError): except (ImportError, OSError):
if fallback: raise
copy(path, dest, replace) except Exception as exc:
else: msg = {
"EXDEV": "Cannot reflink across devices",
"EOPNOTSUPP": "Device does not support reflinks",
}.get(str(exc), "OS does not support reflinks")
raise FilesystemError( raise FilesystemError(
"OS/filesystem does not support reflinks.", msg, "reflink", (path, dest), traceback.format_exc()
"link", ) from exc
(path, dest),
traceback.format_exc(),
)
def unique_path(path: bytes) -> bytes: def unique_path(path: bytes) -> bytes:

View file

@ -606,7 +606,7 @@ documentation. Note that you need to install ``pyreflink``, either through
The option is ignored if ``move`` is enabled (i.e., beets can move or The option is ignored if ``move`` is enabled (i.e., beets can move or
copy files but it doesn't make sense to do both). copy files but it doesn't make sense to do both).
.. _file clones: https://blogs.oracle.com/otn/save-disk-space-on-linux-by-cloning-files-on-btrfs-and-ocfs2 .. _file clones: https://en.wikipedia.org/wiki/Copy-on-write
.. _pyreflink: https://reflink.readthedocs.io/en/latest/ .. _pyreflink: https://reflink.readthedocs.io/en/latest/
resume resume

1442
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -43,6 +43,7 @@ musicbrainzngs = ">=0.4"
pyyaml = "*" pyyaml = "*"
typing_extensions = { version = "*", python = "<=3.10" } typing_extensions = { version = "*", python = "<=3.10" }
unidecode = ">=1.3.6" unidecode = ">=1.3.6"
beautifulsoup4 = { version = "*", optional = true } beautifulsoup4 = { version = "*", optional = true }
dbus-python = { version = "*", optional = true } dbus-python = { version = "*", optional = true }
flask = { version = "*", optional = true } flask = { version = "*", optional = true }
@ -61,7 +62,7 @@ pyxdg = { version = "*", optional = true }
rarfile = { version = "*", optional = true } rarfile = { version = "*", optional = true }
reflink = { version = "*", optional = true } reflink = { version = "*", optional = true }
requests = { version = "*", optional = true } requests = { version = "*", optional = true }
resampy = {version = ">=0.4.3", optional = true} resampy = { version = ">=0.4.3", optional = true }
requests-oauthlib = { version = ">=0.6.1", optional = true } requests-oauthlib = { version = ">=0.6.1", optional = true }
soco = { version = "*", optional = true } soco = { version = "*", optional = true }
@ -79,7 +80,6 @@ python3-discogs-client = ">=2.3.15"
py7zr = "*" py7zr = "*"
pyxdg = "*" pyxdg = "*"
rarfile = "*" rarfile = "*"
reflink = "*"
requests_oauthlib = "*" requests_oauthlib = "*"
responses = ">=0.3.0" responses = ">=0.3.0"

View file

@ -12,8 +12,7 @@
# The above copyright notice and this permission notice shall be # The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software. # included in all copies or substantial portions of the Software.
"""Test file manipulation functionality of Item. """Test file manipulation functionality of Item."""
"""
import os import os
import shutil import shutil
@ -27,7 +26,7 @@ import beets.library
from beets import util from beets import util
from beets.test import _common from beets.test import _common
from beets.test._common import item, touch from beets.test._common import item, touch
from beets.test.helper import BeetsTestCase from beets.test.helper import NEEDS_REFLINK, BeetsTestCase
from beets.util import MoveOperation, bytestring_path, syspath from beets.util import MoveOperation, bytestring_path, syspath
@ -87,22 +86,20 @@ class MoveTest(BeetsTestCase):
self.i.move(operation=MoveOperation.COPY) self.i.move(operation=MoveOperation.COPY)
self.assertExists(self.path) self.assertExists(self.path)
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink")
def test_reflink_arrives(self): def test_reflink_arrives(self):
self.i.move(operation=MoveOperation.REFLINK_AUTO) self.i.move(operation=MoveOperation.REFLINK_AUTO)
self.assertExists(self.dest) self.assertExists(self.dest)
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink")
def test_reflink_does_not_depart(self): def test_reflink_does_not_depart(self):
self.i.move(operation=MoveOperation.REFLINK_AUTO) self.i.move(operation=MoveOperation.REFLINK_AUTO)
self.assertExists(self.path) self.assertExists(self.path)
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink") @NEEDS_REFLINK
def test_force_reflink_arrives(self): def test_force_reflink_arrives(self):
self.i.move(operation=MoveOperation.REFLINK) self.i.move(operation=MoveOperation.REFLINK)
self.assertExists(self.dest) self.assertExists(self.dest)
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink") @NEEDS_REFLINK
def test_force_reflink_does_not_depart(self): def test_force_reflink_does_not_depart(self):
self.i.move(operation=MoveOperation.REFLINK) self.i.move(operation=MoveOperation.REFLINK)
self.assertExists(self.path) self.assertExists(self.path)
@ -286,7 +283,7 @@ class AlbumFileTest(BeetsTestCase):
self.assertExists(oldpath) self.assertExists(oldpath)
self.assertExists(self.i.path) self.assertExists(self.i.path)
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink") @NEEDS_REFLINK
def test_albuminfo_move_reflinks_file(self): def test_albuminfo_move_reflinks_file(self):
oldpath = self.i.path oldpath = self.i.path
self.ai.album = "newAlbumName" self.ai.album = "newAlbumName"
@ -571,7 +568,7 @@ class SafeMoveCopyTest(BeetsTestCase):
self.assertExists(self.dest) self.assertExists(self.dest)
self.assertExists(self.path) self.assertExists(self.path)
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink") @NEEDS_REFLINK
def test_successful_reflink(self): def test_successful_reflink(self):
util.reflink(self.path, self.dest) util.reflink(self.path, self.dest)
self.assertExists(self.dest) self.assertExists(self.dest)
@ -585,9 +582,8 @@ class SafeMoveCopyTest(BeetsTestCase):
with pytest.raises(util.FilesystemError): with pytest.raises(util.FilesystemError):
util.copy(self.path, self.otherpath) util.copy(self.path, self.otherpath)
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflink")
def test_unsuccessful_reflink(self): def test_unsuccessful_reflink(self):
with pytest.raises(util.FilesystemError): with pytest.raises(util.FilesystemError, match="target exists"):
util.reflink(self.path, self.otherpath) util.reflink(self.path, self.otherpath)
def test_self_move(self): def test_self_move(self):

View file

@ -13,8 +13,8 @@
# included in all copies or substantial portions of the Software. # included in all copies or substantial portions of the Software.
"""Tests for the general importer functionality. """Tests for the general importer functionality."""
"""
import os import os
import re import re
import shutil import shutil
@ -37,6 +37,7 @@ from beets.autotag import AlbumInfo, AlbumMatch, TrackInfo
from beets.importer import albums_in_dir from beets.importer import albums_in_dir
from beets.test import _common from beets.test import _common
from beets.test.helper import ( from beets.test.helper import (
NEEDS_REFLINK,
AsIsImporterMixin, AsIsImporterMixin,
AutotagStub, AutotagStub,
BeetsTestCase, BeetsTestCase,
@ -209,7 +210,7 @@ class NonAutotaggedImportTest(AsIsImporterMixin, ImportTestCase):
s2[stat.ST_DEV], s2[stat.ST_DEV],
) )
@unittest.skipUnless(_common.HAVE_REFLINK, "need reflinks") @NEEDS_REFLINK
def test_import_reflink_arrives(self): def test_import_reflink_arrives(self):
# Detecting reflinks is currently tricky due to various fs # Detecting reflinks is currently tricky due to various fs
# implementations, we'll just check the file exists. # implementations, we'll just check the file exists.
@ -392,7 +393,7 @@ class ImportSingletonTest(ImportTestCase):
assert len(self.lib.albums()) == 2 assert len(self.lib.albums()) == 2
def test_set_fields(self): def test_set_fields(self):
genre = "\U0001F3B7 Jazz" genre = "\U0001f3b7 Jazz"
collection = "To Listen" collection = "To Listen"
config["import"]["set_fields"] = { config["import"]["set_fields"] = {
@ -579,7 +580,7 @@ class ImportTest(ImportTestCase):
self.lib.items().get().data_source self.lib.items().get().data_source
def test_set_fields(self): def test_set_fields(self):
genre = "\U0001F3B7 Jazz" genre = "\U0001f3b7 Jazz"
collection = "To Listen" collection = "To Listen"
comments = "managed by beets" comments = "managed by beets"