From 5523ca94a293fa64cd56659133afded333c9a8e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sat, 10 Jan 2026 02:28:18 +0000 Subject: [PATCH] Document ArtistState --- beetsplug/discogs.py | 61 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py index a7206c7d6..017969e27 100644 --- a/beetsplug/discogs.py +++ b/beetsplug/discogs.py @@ -127,7 +127,23 @@ class TracklistInfo(TypedDict): @dataclass class ArtistState: + """Represent Discogs artist credits. + + This object centralizes the plugin's policy for which Discogs artist fields + to prefer (name vs. ANV), how to treat 'Various', how to format join + phrases, and how to separate featured artists. It exposes both per-artist + components and fully joined strings for common tag targets like 'artist' and + 'artist_credit'. + """ + class ValidArtist(NamedTuple): + """A normalized, render-ready artist entry extracted from Discogs data. + + Instances represent the subset of Discogs artist information needed for + tagging, including the join token following the artist and whether the + entry is considered a featured appearance. + """ + id: str name: str credit: str @@ -135,9 +151,14 @@ class ArtistState: is_feat: bool def get_artist(self, property_name: str) -> str: - return getattr(self, property_name) + ( - {",": ", ", "": ""}.get(self.join, f" {self.join} ") - ) + """Return the requested display field with its trailing join token. + + The join token is normalized so commas become ', ' and other join + phrases are surrounded with spaces, producing a single fragment that + can be concatenated to form a full artist string. + """ + join = {",": ", ", "": ""}.get(self.join, f" {self.join} ") + return f"{getattr(self, property_name)}{join}" raw_artists: list[Artist] use_anv: bool @@ -147,25 +168,38 @@ class ArtistState: @property def info(self) -> ArtistInfo: + """Expose the state in the shape expected by downstream tag mapping.""" return {k: getattr(self, k) for k in ArtistInfo.__annotations__} # type: ignore[return-value] def strip_disambiguation(self, text: str) -> str: - """Removes discogs specific disambiguations from a string. - Turns 'Label Name (5)' to 'Label Name' or 'Artist (1) & Another Artist (2)' - to 'Artist & Another Artist'. Does nothing if strip_disambiguation is False.""" + """Strip Discogs disambiguation suffixes from an artist or label string. + + This removes Discogs-specific numeric suffixes like 'Name (5)' and can + be applied to multi-artist strings as well (e.g., 'A (1) & B (2)'). When + the feature is disabled, the input is returned unchanged. + """ if self.should_strip_disambiguation: return DISAMBIGUATION_RE.sub("", text) return text @cached_property def valid_artists(self) -> list[ValidArtist]: + """Build the ordered, filtered list of artists used for rendering. + + The resulting list normalizes Discogs entries by: + - substituting the configured 'Various Artists' name when Discogs uses + 'Various' + - choosing between name and ANV according to plugin settings + - excluding non-empty roles unless they indicate a featured appearance + - capturing join tokens so the original credit formatting is preserved + """ va_name = config["va_name"].as_str() return [ self.ValidArtist( str(a["id"]), self.strip_disambiguation(anv if self.use_anv else name), self.strip_disambiguation(anv if self.use_credit_anv else name), - a["join"], + a["join"].strip(), is_feat, ) for a in self.raw_artists @@ -181,29 +215,42 @@ class ArtistState: @property def artists_ids(self) -> list[str]: + """Return Discogs artist IDs for all valid artists, preserving order.""" return [a.id for a in self.valid_artists] @property def artist_id(self) -> str: + """Return the primary Discogs artist ID.""" return self.artists_ids[0] @property def artists(self) -> list[str]: + """Return the per-artist display names used for the 'artist' field.""" return [a.name for a in self.valid_artists] @property def artists_credit(self) -> list[str]: + """Return the per-artist display names used for the credit field.""" return [a.credit for a in self.valid_artists] @property def artist(self) -> str: + """Return the fully rendered artist string using display names.""" return self.join_artists("name") @property def artist_credit(self) -> str: + """Return the fully rendered artist credit string.""" return self.join_artists("credit") def join_artists(self, property_name: str) -> str: + """Render a single artist string with join phrases and featured artists. + + Non-featured artists are concatenated using their join tokens. Featured + artists are appended after the configured 'featured' marker, preserving + Discogs order while keeping featured credits separate from the main + artist string. + """ non_featured = [a for a in self.valid_artists if not a.is_feat] featured = [a for a in self.valid_artists if a.is_feat]