mirror of
https://github.com/beetbox/beets.git
synced 2026-03-27 07:43:38 +01:00
Create a dedicated get_model_changes function in beets/util/diff.py
- Add `get_model_changes` as the public API for computing diffs - Remove tests for `show_model_changes` as test_diff.py fully covers them.
This commit is contained in:
parent
1d54f2bf66
commit
fc39ab791c
3 changed files with 40 additions and 76 deletions
|
|
@ -46,7 +46,7 @@ from beets.util.color import (
|
|||
uncolorize,
|
||||
)
|
||||
from beets.util.deprecation import deprecate_for_maintainers
|
||||
from beets.util.diff import _field_diff
|
||||
from beets.util.diff import get_model_changes
|
||||
from beets.util.functemplate import template
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -802,32 +802,14 @@ def show_model_changes(
|
|||
always: bool = False,
|
||||
print_obj: bool = True,
|
||||
) -> bool:
|
||||
"""Given a Model object, print a list of changes from its pristine
|
||||
version stored in the database. Return a boolean indicating whether
|
||||
any changes were found.
|
||||
"""Print a diff of changes between two library model states.
|
||||
|
||||
`old` may be the "original" object to avoid using the pristine
|
||||
version from the database. `fields` may be a list of fields to
|
||||
restrict the detection to. `always` indicates whether the object is
|
||||
always identified, regardless of whether any changes are present.
|
||||
Optionally prints the original object label before listing field-level
|
||||
changes when `print_obj` is enabled. When `always` is set, the object
|
||||
label is printed even if no changes are detected. Returns whether any
|
||||
changes were found.
|
||||
"""
|
||||
old = old or new.get_fresh_from_db()
|
||||
|
||||
# Keep the formatted views around instead of re-creating them in each
|
||||
# iteration step
|
||||
old_fmt = old.formatted()
|
||||
new_fmt = new.formatted()
|
||||
|
||||
# Build up lines showing changed fields.
|
||||
diff_fields = (set(old) | set(new)) - {"mtime"}
|
||||
if allowed_fields := set(fields or {}):
|
||||
diff_fields &= allowed_fields
|
||||
|
||||
changes = [
|
||||
d
|
||||
for f in sorted(diff_fields)
|
||||
if (d := _field_diff(f, old_fmt, new_fmt))
|
||||
]
|
||||
changes = get_model_changes(new, old, fields)
|
||||
|
||||
# Print changes.
|
||||
if print_obj and (changes or always):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ from typing import TYPE_CHECKING
|
|||
from .color import colorize
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable
|
||||
|
||||
from beets.dbcore.db import FormattedMapping
|
||||
from beets.library.models import LibModel
|
||||
|
||||
|
||||
def colordiff(a: str, b: str) -> tuple[str, str]:
|
||||
|
|
@ -79,3 +82,33 @@ def _field_diff(
|
|||
newstr = colorize("text_diff_added", newstr)
|
||||
|
||||
return f"{field}: {oldstr} -> {newstr}"
|
||||
|
||||
|
||||
def get_model_changes(
|
||||
new: LibModel,
|
||||
old: LibModel | None,
|
||||
fields: Iterable[str] | None,
|
||||
) -> list[str]:
|
||||
"""Compute human-readable diff lines for changed fields between two models.
|
||||
|
||||
Compares `new` against `old`, falling back to the database version of
|
||||
`new` when `old` is not provided. When `fields` is given, only those
|
||||
fields are considered. The `mtime` field is always excluded.
|
||||
"""
|
||||
old = old or new.get_fresh_from_db()
|
||||
|
||||
# Keep the formatted views around instead of re-creating them in each
|
||||
# iteration step
|
||||
old_fmt = old.formatted()
|
||||
new_fmt = new.formatted()
|
||||
|
||||
# Build up lines showing changed fields.
|
||||
diff_fields = (set(old) | set(new)) - {"mtime"}
|
||||
if allowed_fields := set(fields or {}):
|
||||
diff_fields &= allowed_fields
|
||||
|
||||
return [
|
||||
d
|
||||
for f in sorted(diff_fields)
|
||||
if (d := _field_diff(f, old_fmt, new_fmt))
|
||||
]
|
||||
|
|
|
|||
|
|
@ -329,57 +329,6 @@ class ConfigTest(IOMixin, TestPluginTestCase):
|
|||
assert config["statefile"].as_path() == self.beetsdir / "state"
|
||||
|
||||
|
||||
class ShowModelChangeTest(IOMixin, unittest.TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.a = _common.item()
|
||||
self.b = _common.item()
|
||||
self.a.path = self.b.path
|
||||
|
||||
def _show(self, **kwargs):
|
||||
change = ui.show_model_changes(self.a, self.b, **kwargs)
|
||||
out = self.io.getoutput()
|
||||
return change, out
|
||||
|
||||
def test_identical(self):
|
||||
change, out = self._show()
|
||||
assert not change
|
||||
assert out == ""
|
||||
|
||||
def test_string_fixed_field_change(self):
|
||||
self.b.title = "x"
|
||||
change, out = self._show()
|
||||
assert change
|
||||
assert "title" in out
|
||||
|
||||
def test_int_fixed_field_change(self):
|
||||
self.b.track = 9
|
||||
change, out = self._show()
|
||||
assert change
|
||||
assert "track" in out
|
||||
|
||||
def test_floats_close_to_identical(self):
|
||||
self.a.length = 1.00001
|
||||
self.b.length = 1.00005
|
||||
change, out = self._show()
|
||||
assert not change
|
||||
assert out == ""
|
||||
|
||||
def test_floats_different(self):
|
||||
self.a.length = 1.00001
|
||||
self.b.length = 2.00001
|
||||
change, out = self._show()
|
||||
assert change
|
||||
assert "length" in out
|
||||
|
||||
def test_both_values_shown(self):
|
||||
self.a.title = "foo"
|
||||
self.b.title = "bar"
|
||||
_, out = self._show()
|
||||
assert "foo" in out
|
||||
assert "bar" in out
|
||||
|
||||
|
||||
class PathFormatTest(unittest.TestCase):
|
||||
def test_custom_paths_prepend(self):
|
||||
default_formats = ui.get_path_formats()
|
||||
|
|
|
|||
Loading…
Reference in a new issue