Make musicbrainz plugin talk to musicbrainz directly
This PR refactors the MusicBrainz plugin implementation by replacing the musicbrainzngs library with direct HTTP API calls using requests and requests-ratelimiter.
Key Changes:
- New utilities module: Added
beetsplug/_utils/requests.pywithTimeoutSessionclass and HTTP error handling (HTTPNotFoundError,CaptchaError) - MusicBrainz API rewrite: Replaced
musicbrainzngsdependency with customMusicBrainzAPIclass using direct HTTP requests - Rate limiting: Integrated
requests-ratelimiterfor API rate limiting instead ofmusicbrainzngs.set_rate_limit() - Data structure updates: Updated field names to match MusicBrainz JSON API v2 format (e.g.,
medium-list→media,track-list→tracks) - Dependency management:
- Made
musicbrainzngsoptional and added it to plugin-specific extras (listenbrainz,mbcollection,missing,parentwork). Updated plugin docs accordingly. - Made
requestsa required dependency to ensure backwards compatibility (ideally, we would make it an optional dependency undermusicbrainzextra).
- Made
- Error handling: Simplified error handling by removing
MusicBrainzAPIErrorwrapper class
Benefits:
- Direct control over HTTP requests
- Consistent rate limiting across all network requests
- Better alignment with modern MusicBrainz API responses
The changes maintain backward compatibility while modernizing the underlying implementation.
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.
Codecov Report
:x: Patch coverage is 84.34783% with 36 lines in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 68.36%. Comparing base (ac0b6ec) to head (5785ce3).
:warning: Report is 16 commits behind head on master.
:white_check_mark: All tests successful. No failed tests found.
Additional details and impacted files
@@ Coverage Diff @@
## master #6052 +/- ##
==========================================
+ Coverage 68.03% 68.36% +0.32%
==========================================
Files 137 138 +1
Lines 18708 18773 +65
Branches 3163 3172 +9
==========================================
+ Hits 12728 12834 +106
+ Misses 5309 5263 -46
- Partials 671 676 +5
| Files with missing lines | Coverage Δ | |
|---|---|---|
| beetsplug/_utils/requests.py | 98.50% <98.50%> (ø) |
|
| beetsplug/mbpseudo.py | 80.00% <88.88%> (+0.37%) |
:arrow_up: |
| beetsplug/lyrics.py | 85.12% <83.33%> (+6.82%) |
:arrow_up: |
| beetsplug/musicbrainz.py | 81.15% <76.61%> (+1.98%) |
:arrow_up: |
:rocket: New features to boost your workflow:
- :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
I think there's also potential to be able to use the new request code here and refactor the Discogs plugin to make direct API calls as well, since we end up fetching the whole release object and parsing it like a dictionary anyway.
It may replace https://github.com/beetbox/beets/pull/5976 implementation over mbzero.
It may replace #5976 implementation over
mbzero.
Peharps this can be discussed in the original thread #4660
EDIT: I have updated the thread to resume the situation
It may replace #5976 implementation over
mbzero.
I would think so, given that this requires no external dependencies and is simpler.
I tested these adjustments by reimporting all musicbrainz tracks/albums in my library
$ beet -vv import -LI data_source:musicbrainz --plugins=musicbrainz,importreplace (and -s)
I found a couple of issues and fixed them, see the diff:
- Added retry on connection errors (3 attempts, 1s, 2s and 4s later)
- Fixed
incparameter format - Fixed URLs parsing
That retry on connection might really help cut down on currently the persistent Musicbrainz unreachable error! Nice!
Have also added a test for the retry behaviour. Pretty sure it's ready now, merging!
Hi! I'm getting this error with the parentwork plugin:
$beet parentwork -f
parentwork: No work for 10 String Symphony - 10 String Symphony - Prettiest Girl, add one at https://musicbrainz.org/recording/9cebf2bd-8fef-432a-b360-23d70ab03008
Traceback (most recent call last):
File "/home/user/.local/bin/beet", line 10, in <module>
sys.exit(main())
~~~~^^
File "/home/user/.local/lib/beets/beets/ui/__init__.py", line 1631, in main
_raw_main(args)
~~~~~~~~~^^^^^^
File "/home/user/.local/lib/beets/beets/ui/__init__.py", line 1610, in _raw_main
subcommand.func(lib, suboptions, subargs)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/.local/lib/beets/beetsplug/parentwork.py", line 92, in func
changed = self.find_work(item, force_parent, verbose=True)
File "/home/user/.local/lib/beets/beetsplug/parentwork.py", line 193, in find_work
work_info, work_date = find_parentwork_info(item.mb_workid)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
File "/home/user/.local/lib/beets/beetsplug/parentwork.py", line 64, in find_parentwork_info
parent_id, work_date = work_parent_id(mb_workid)
~~~~~~~~~~~~~~^^^^^^^^^^^
File "/home/user/.local/lib/beets/beetsplug/parentwork.py", line 53, in work_parent_id
new_mb_workid, work_date = direct_parent_id(mb_workid, work_date)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/.local/lib/beets/beetsplug/parentwork.py", line 29, in direct_parent_id
work_info = musicbrainzngs.get_work_by_id(
mb_workid, includes=["work-rels", "artist-rels"]
)
File "/usr/lib/python3.13/site-packages/musicbrainzngs/musicbrainz.py", line 904, in get_work_by_id
return _do_mb_query("work", id, includes)
File "/usr/lib/python3.13/site-packages/musicbrainzngs/musicbrainz.py", line 728, in _do_mb_query
return _mb_request(path, 'GET', auth_required, args=args)
File "/usr/lib/python3.13/site-packages/musicbrainzngs/musicbrainz.py", line 417, in __call__
return self.fun(*args, **kwargs)
~~~~~~~~^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.13/site-packages/musicbrainzngs/musicbrainz.py", line 624, in _mb_request
raise UsageError("set a proper user-agent with "
"set_useragent(\"application name\", \"application version\", \"contact info (preferably URL or email for your application)\")")
musicbrainzngs.musicbrainz.UsageError: set a proper user-agent with set_useragent("application name", "application version", "contact info (preferably URL or email for your application)")
It looks like user-agent information was removed here: https://github.com/beetbox/beets/pull/6052/files#diff-3420b9b69bb3555786b2100c434079c83f948e7f1dc796869f4945c33577d3baL87 . Should this now be set elsewhere, or should the parentwork (and presumably other) plugins be refactored to use a similar approach as to here?
Looks like we didn't catch that side effect!
I think it'd be nice to get that plugin & others either using our new request system or fold it into the Musicbrainz plugin. In the interim we can probably patch in a user agent?
I might be able to do some work on parentwork in the next couple weeks. Or would the better plan be to include it directly in the musicbrainz plugin?
Oops, apologies!
I'm currently working on migrating the rest of plugins away from musicbrainzngs so I will take care of this :)
Great! I can test anything out when you have it!