parentwork: simplify work retrieval and tests

This commit is contained in:
Šarūnas Nejus 2025-12-22 15:57:18 +00:00
parent 36964e433e
commit 741f5c4be1
No known key found for this signature in database
2 changed files with 83 additions and 132 deletions

View file

@ -16,56 +16,51 @@
and work composition date
"""
from __future__ import annotations
from typing import Any
import musicbrainzngs
from beets import ui
from beets import __version__, ui
from beets.plugins import BeetsPlugin
def direct_parent_id(mb_workid, work_date=None):
"""Given a Musicbrainz work id, find the id one of the works the work is
part of and the first composition date it encounters.
"""
work_info = musicbrainzngs.get_work_by_id(
mb_workid, includes=["work-rels", "artist-rels"]
)
if "artist-relation-list" in work_info["work"] and work_date is None:
for artist in work_info["work"]["artist-relation-list"]:
if artist["type"] == "composer":
if "end" in artist.keys():
work_date = artist["end"]
if "work-relation-list" in work_info["work"]:
for direct_parent in work_info["work"]["work-relation-list"]:
if (
direct_parent["type"] == "parts"
and direct_parent.get("direction") == "backward"
):
direct_id = direct_parent["work"]["id"]
return direct_id, work_date
return None, work_date
musicbrainzngs.set_useragent("beets", __version__, "https://beets.io/")
def work_parent_id(mb_workid):
"""Find the parent work id and composition date of a work given its id."""
work_date = None
while True:
new_mb_workid, work_date = direct_parent_id(mb_workid, work_date)
if not new_mb_workid:
return mb_workid, work_date
mb_workid = new_mb_workid
return mb_workid, work_date
def find_parentwork_info(mb_workid):
def find_parentwork_info(mb_workid: str) -> tuple[dict[str, Any], str | None]:
"""Get the MusicBrainz information dict about a parent work, including
the artist relations, and the composition date for a work's parent work.
"""
parent_id, work_date = work_parent_id(mb_workid)
work_info = musicbrainzngs.get_work_by_id(
parent_id, includes=["artist-rels"]
)
return work_info, work_date
work_date = None
parent_id: str | None = mb_workid
while parent_id:
current_id = parent_id
work_info = musicbrainzngs.get_work_by_id(
current_id, includes=["work-rels", "artist-rels"]
)["work"]
work_date = work_date or next(
(
end
for a in work_info.get("artist-relation-list", [])
if a["type"] == "composer" and (end := a.get("end"))
),
None,
)
parent_id = next(
(
w["work"]["id"]
for w in work_info.get("work-relation-list", [])
if w["type"] == "parts" and w["direction"] == "backward"
),
None,
)
return musicbrainzngs.get_work_by_id(
current_id, includes=["artist-rels"]
), work_date
class ParentWorkPlugin(BeetsPlugin):

View file

@ -14,74 +14,13 @@
"""Tests for the 'parentwork' plugin."""
from unittest.mock import patch
from typing import Any
from unittest.mock import Mock, patch
import pytest
from beets.library import Item
from beets.test.helper import PluginTestCase
from beetsplug import parentwork
work = {
"work": {
"id": "1",
"title": "work",
"work-relation-list": [
{"type": "parts", "direction": "backward", "work": {"id": "2"}}
],
"artist-relation-list": [
{
"type": "composer",
"artist": {
"name": "random composer",
"sort-name": "composer, random",
},
}
],
}
}
dp_work = {
"work": {
"id": "2",
"title": "directparentwork",
"work-relation-list": [
{"type": "parts", "direction": "backward", "work": {"id": "3"}}
],
"artist-relation-list": [
{
"type": "composer",
"artist": {
"name": "random composer",
"sort-name": "composer, random",
},
}
],
}
}
p_work = {
"work": {
"id": "3",
"title": "parentwork",
"artist-relation-list": [
{
"type": "composer",
"artist": {
"name": "random composer",
"sort-name": "composer, random",
},
}
],
}
}
def mock_workid_response(mbid, includes):
if mbid == "1":
return work
elif mbid == "2":
return dp_work
elif mbid == "3":
return p_work
@pytest.mark.integration_test
@ -134,36 +73,57 @@ class ParentWorkIntegrationTest(PluginTestCase):
item.load()
assert item["mb_parentworkid"] == "XXX"
# test different cases, still with Matthew Passion Ouverture or Mozart
# requiem
def test_direct_parent_work_real(self):
mb_workid = "2e4a3668-458d-3b2a-8be2-0b08e0d8243a"
assert (
"f04b42df-7251-4d86-a5ee-67cfa49580d1"
== parentwork.direct_parent_id(mb_workid)[0]
)
assert (
"45afb3b2-18ac-4187-bc72-beb1b1c194ba"
== parentwork.work_parent_id(mb_workid)[0]
)
def mock_workid_response(mbid, includes):
works: list[dict[str, Any]] = [
{
"id": "1",
"title": "work",
"work-relation-list": [
{
"type": "parts",
"direction": "backward",
"work": {"id": "2"},
}
],
},
{
"id": "2",
"title": "directparentwork",
"work-relation-list": [
{
"type": "parts",
"direction": "backward",
"work": {"id": "3"},
}
],
},
{
"id": "3",
"title": "parentwork",
},
]
return {
"work": {
**next(w for w in works if mbid == w["id"]),
"artist-relation-list": [
{
"type": "composer",
"artist": {
"name": "random composer",
"sort-name": "composer, random",
},
}
],
}
}
@patch("musicbrainzngs.get_work_by_id", Mock(side_effect=mock_workid_response))
class ParentWorkTest(PluginTestCase):
plugin = "parentwork"
def setUp(self):
"""Set up configuration"""
super().setUp()
self.patcher = patch(
"musicbrainzngs.get_work_by_id", side_effect=mock_workid_response
)
self.patcher.start()
def tearDown(self):
super().tearDown()
self.patcher.stop()
def test_normal_case(self):
item = Item(path="/file", mb_workid="1", parentwork_workid_current="1")
item.add(self.lib)
@ -204,7 +164,3 @@ class ParentWorkTest(PluginTestCase):
item.load()
assert item["mb_parentworkid"] == "XXX"
def test_direct_parent_work(self):
assert "2" == parentwork.direct_parent_id("1")[0]
assert "3" == parentwork.work_parent_id("1")[0]