diff --git a/beetsplug/parentwork.py b/beetsplug/parentwork.py index eb2fd8f11..6fa4bfbdb 100644 --- a/beetsplug/parentwork.py +++ b/beetsplug/parentwork.py @@ -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): diff --git a/test/plugins/test_parentwork.py b/test/plugins/test_parentwork.py index 1abe25709..809387bbc 100644 --- a/test/plugins/test_parentwork.py +++ b/test/plugins/test_parentwork.py @@ -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]