Skip to content

Refactor musicbrainz to reduce cognitive complexity - #6530

Merged
snejus merged 47 commits into
masterfrom
refactor-musicbrainz
Apr 18, 2026
Merged

Refactor musicbrainz to reduce cognitive complexity#6530
snejus merged 47 commits into
masterfrom
refactor-musicbrainz

Conversation

@snejus

@snejus snejus commented Apr 12, 2026

Copy link
Copy Markdown
Member

MusicBrainz Plugin Refactor

Tip

I strongly recommend to review this PR commit-by-commit!

Summary

A broad internal refactor of beetsplug/musicbrainz.py and its test suite. No user-facing behaviour changes are intended. The goal is to make parsing logic easier to understand, test, and maintain.

This PR halves the cognitive complexity in musicbrainz plugin:

Before

$ complexipy beetsplug/musicbrainz.py
🧠 Total Cognitive Complexity: 181
1 file analyzed in 0.0116 seconds

After

$ complexipy beetsplug/musicbrainz.py
🧠 Total Cognitive Complexity: 93
1 file analyzed in 0.0097 seconds

Production Code Changes

Parsing Decomposition

The monolithic album_info method is broken into focused @staticmethod helpers, each returning a typed TypedDict:

Method Responsibility
_parse_artist_credits Replaces _flatten_artist_credit / _multi_artist_credit
_parse_release_group albumtype, original_year, etc.
_parse_label_infos label, catalognum
_parse_genres Genre list
_parse_external_ids Third-party IDs from URL relations
_parse_work_relations Composer / lyricist credits
_parse_artist_relations Arranger / remixer credits
get_tracks_from_medium Per-medium track iteration

Each helper returns a typed TypedDict, making the data contract explicit and the code easier to compose with **kwargs unpacking into AlbumInfo / TrackInfo.

Other Simplifications

  • _set_date_str (mutable, side-effecting) is replaced by _get_date, a pure function returning (year, month, day).
  • _preferred_release_event and _preferred_alias are simplified.
  • Three @cached_property entries (ignored_media, ignore_data_tracks, ignore_video_tracks) are moved up to sit with other class-level properties.

Test Infrastructure Changes

Factory Layer (test/plugins/factories/musicbrainz.py)

factory-boy / pytest-factoryboy are added as test dependencies. A new factory module introduces composable, deterministic factories:

  • AliasFactory, ArtistFactory, ArtistCreditFactory
  • RecordingFactory, TrackFactory, MediumFactory
  • ReleaseGroupFactory, ReleaseFactory

Factories use a shared _IdFactory base class that generates stable, predictable UUIDs (00000000-0000-0000-0000-000000001001, etc.) based on an id_base + index pair. This makes assertions on IDs readable and deterministic without hard-coded magic strings.

Before:

recording = {
    "title": "foo",
    "id": "bar",
    "length": 42,
    "artist_credit": [{"artist": {"name": "some-artist", "id": "some-id", ...}, ...}],
    ...
}

After:

recording = recording_factory(title="foo", length=42)

Test Consolidation

Many small single-field assertion tests that each constructed an identical release are collapsed into a single test_parse_release snapshot assertion, covering all AlbumInfo fields at once. This reduces redundant fixture setup and makes it immediately obvious what the full output of album_info looks like for a default release.

The hand-rolled _make_release / _make_recording helpers are removed entirely, replaced by composable factory calls using factory-boy's __ double-underscore traversal syntax:

# Before
release = self._make_release(date="1987-03-31")

# After
release = release_factory(release_group__first_release_date="1987-03-31")

@snejus
snejus requested a review from a team as a code owner April 12, 2026 15:29
@github-actions github-actions Bot added the musicbrainz musicbrainz plugin label Apr 12, 2026
@github-actions

Copy link
Copy Markdown

Thank you for the PR! The changelog has not been updated, so here is a friendly reminder to check if you need to add an entry.

Base automatically changed from type-musicbrainz to master April 12, 2026 15:34
@snejus
snejus force-pushed the refactor-musicbrainz branch from 7c0b1e3 to 804c835 Compare April 12, 2026 15:36
Copilot AI review requested due to automatic review settings April 12, 2026 15:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

grug see PR try break big MusicBrainz parse monster into small helpers, plus new test factories, to make brain hurt less. Goal say “no user-facing change”, but grug spot few behavior leaks and test factory sharp edge.

Changes:

  • Split MusicBrainzPlugin.album_info parsing into smaller helpers and add TypedDict contracts.
  • Add factory-boy based factories for MusicBrainz payloads and refactor tests to use them (plus one snapshot-style assertion).
  • Add pytest-factoryboy dependency (and lockfile updates).

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
beetsplug/musicbrainz.py Refactor parsing into helpers; new date/alias logic; new medium/track iteration helper.
test/plugins/test_musicbrainz.py Switch tests to new factories and consolidate many assertions into one snapshot-style test.
test/plugins/factories/musicbrainz.py New deterministic-ish payload factories for MusicBrainz structures.
test/plugins/factories/init.py New package marker for factories.
pyproject.toml Add pytest-factoryboy dependency.
poetry.lock Lock dependency graph for new test deps.

Comment thread beetsplug/musicbrainz.py
Comment thread beetsplug/musicbrainz.py
Comment thread beetsplug/musicbrainz.py Outdated
Comment thread beetsplug/musicbrainz.py
Comment thread test/plugins/factories/musicbrainz.py
Comment thread test/plugins/factories/musicbrainz.py
Comment thread test/plugins/test_musicbrainz.py Outdated
Comment thread beetsplug/musicbrainz.py
@snejus
snejus force-pushed the refactor-musicbrainz branch from 804c835 to c2528ca Compare April 12, 2026 16:05
Comment thread beetsplug/musicbrainz.py Outdated
@snejus
snejus force-pushed the refactor-musicbrainz branch from c2528ca to 2785606 Compare April 13, 2026 09:23
@codecov

codecov Bot commented Apr 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.18%. Comparing base (06f5e14) to head (3451712).
⚠️ Report is 48 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #6530   +/-   ##
=======================================
  Coverage   71.18%   71.18%           
=======================================
  Files         150      150           
  Lines       19172    19172           
  Branches     3084     3084           
=======================================
  Hits        13648    13648           
  Misses       4864     4864           
  Partials      660      660           
Files with missing lines Coverage Δ
beetsplug/_utils/musicbrainz.py 97.29% <100.00%> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aereaux

aereaux commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

I haven't had a chance to look too closely at the code but at least on my library this doesn't cause any unexpected tagging changes!

@snejus snejus mentioned this pull request Apr 13, 2026
5 tasks
@snejus
snejus force-pushed the refactor-musicbrainz branch 2 times, most recently from 0a40300 to 685a095 Compare April 14, 2026 15:10

@semohr semohr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a look at all commits. Seems very reasonable to me.

Fyi: Test coverage improved by 4% here and we got rid of 94 branches! That's crazy.

Before:

Name                       Stmts   Miss Branch BrPart   Cover  
-----------------------------------------------------------------------
beetsplug/musicbrainz.py     360     39    186     26  84.80% 

After

Name                       Stmts   Miss Branch BrPart   Cover
-----------------------------------------------------------------------
beetsplug/musicbrainz.py     313     23     92     14  88.89% 

Before merging this, could you please check whether any other open PRs will be significantly affected by these changes? If so, it would be helpful to give notice and assist with updates where possible. While it may be straightforward for you to refactor your changes onto the new structure. I can see others struggling here :)

Comment thread beetsplug/musicbrainz.py Outdated
Comment thread beetsplug/musicbrainz.py
Comment thread beetsplug/musicbrainz.py
@snejus
snejus force-pushed the refactor-musicbrainz branch from 685a095 to 19fc243 Compare April 17, 2026 15:17
@semohr

semohr commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

I was also generally wondering if the musicbrainz plugin parsing would be a good target for an adapter pattern. Would allow to split all parsing logic into an intermediary layer.

@snejus

snejus commented Apr 17, 2026

Copy link
Copy Markdown
Member Author

I was also generally wondering if the musicbrainz plugin parsing would be a good target for an adapter pattern. Would allow to split all parsing logic into an intermediary layer.

Hmm, not sure about it. Adapter would be useful when we have multiple consumers - but in this case it's just the autotagging system. The parsing methods are already well isolated, so abstracting them into a separate adapter class would simply add a layer of indirection without much benefit.

@snejus

snejus commented Apr 17, 2026

Copy link
Copy Markdown
Member Author

I'll see maybe I can bring the coverage to 100% here 😈

@snejus
snejus force-pushed the refactor-musicbrainz branch 2 times, most recently from e651258 to a56798d Compare April 18, 2026 11:49
@snejus
snejus force-pushed the refactor-musicbrainz branch from 5822b8c to 9eacced Compare April 18, 2026 16:31
@snejus
snejus force-pushed the refactor-musicbrainz branch from 9eacced to 3451712 Compare April 18, 2026 18:29
@snejus

snejus commented Apr 18, 2026

Copy link
Copy Markdown
Member Author

Pushed the coverage to 96%:

Name                       Stmts   Miss Branch BrPart   Cover   Missing
-----------------------------------------------------------------------
beetsplug/musicbrainz.py     312      9     88      7  96.00%   186->185, 199, 297, 462, 470->474, 692, 771-772, 806-807, 812

@snejus
snejus enabled auto-merge April 18, 2026 18:36
@snejus
snejus merged commit 2f3efae into master Apr 18, 2026
20 checks passed
@snejus
snejus deleted the refactor-musicbrainz branch April 18, 2026 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

musicbrainz musicbrainz plugin

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants