* Move genre-to-genres migration into a dedicated Migration class and
wire it into Library._migrations for items and albums.
* Add batched SQL updates via mutate_many and share the multi-value
delimiter as a constant.
* Cover migration behavior with new tests.
I initially attempted to migrate using our model infrastructure
/ Model.store(), see the comparison below:
Durations migrating my library of ~9000 items and ~2300 albums:
1. Using our Python logic: 11 minutes
2. Using SQL directly: 4 seconds
That's why I've gone ahead with option 2.
Migration now happens automatically when the database schema is
updated (in Library._make_table()), so the manual 'beet migrate'
command is no longer needed.
Addresses PR review comment.
- Add Library._make_table() override to automatically migrate genres when database schema is updated
- Migration splits comma/semicolon/slash-separated genre strings into genres list
- Writes changes to both database and media files with progress reporting
- Remove lazy migration from correct_list_fields() - now handled at database level
- Remove migration-specific tests (migration is now automatic, not lazy)
- Update changelog to reflect automatic migration behavior
Related PR review comment changes:
- Replace _is_valid with _filter_valid method in lastgenre plugin
- Use unique_list and remove genre field from Beatport plugin
- Simplify LastGenre tests - remove separator logic
- Document separator deprecation in lastgenre plugin
- Add deprecation warning for genre parameter in Info.__init__()
Simplify multi-genre implementation based on maintainer feedback (PR #6169).
Changes:
- Remove multi_value_genres and genre_separator config options
- Replace complex sync_genre_fields() with ensure_first_value('genre', 'genres')
- Update all plugins (Beatport, MusicBrainz, LastGenre) to always write genres as lists
- Add automatic migration for comma/semicolon/slash-separated genre strings
- Add 'beet migrate genres' command for explicit batch migration with --pretend flag
- Update all tests to reflect simplified approach (44 tests passing)
- Update documentation
Implementation aligns with maintainer vision of always using multi-value genres
internally with automatic backward-compatible sync to the genre field via
ensure_first_value(), eliminating configuration complexity.
Migration strategy avoids problems from #5540:
- Automatic lazy migration on item access (no reimport/mbsync needed)
- Optional batch migration command for user control
- No endless rewrite loops due to proper field synchronization
Fixes#6339
This change moves MusicBrainz lookup defaults into
`beetsplug/_utils/musicbrainz.py` so `MusicBrainzAPI` now owns the
default `includes` for both `get_release` and `get_recording`.
#### Architecture impact
- Added centralized defaults: `RELEASE_INCLUDES` and
`RECORDING_INCLUDES` in `MusicBrainzAPI`.
- Updated `get_release` and `get_recording` to apply these defaults via
`kwargs.setdefault("includes", ...)`.
- Removed duplicated include lists from `beetsplug/musicbrainz.py` and
simplified call sites to `self.mb_api.get_release(...)` /
`self.mb_api.get_recording(...)` without passing includes explicitly.
#### High-level result
- Ensures consistent MusicBrainz payload shape across callers.
- Fixes the `mbpseudo` failure caused by missing `artist_credit` data in
some responses.
- Reduces coupling and duplication by making include policy an API-layer
concern.
## PR Summary
Fixes#2946Fixes#6335
Replaygain plugin tests used a **fragmented configuration approach**:
each backend mixin set only `backend` as a class variable, while
additional `command` configuration was required for `CmdBackendMixin`.
### Key changes
- **`CmdBackendMixin`** now explicitly sets `"command": "mp3gain"`
alongside the backend name.
- All tests now reference plugin-scoped `self.config` configuration
instead of global one.
- The `GAIN_PROG_AVAILABLE` check now also looks for `mp3rgain` in
addition to `mp3gain` and `aacgain`.
- `mp3gain` is now installed for both `ubuntu` and `windows`-based
runners.
- Tests on Windows caught a legitimate issue #2946 which this PR fixes
also.
This was missed out in #6289 since these tests did not run in CI.
Purpose of the plugin is to make manual metadata editing faster.
I wanted to add custom metadata like "language", "mood", and "context"
but existing methods felt too slow, especially when I needed to find and
open the music file to playback separately. So I created this plugin and
I'd like to share it with the community. It helped me to update metadata
for 500 tracks in about 1.5 hours.
See plugin's
[README](https://github.com/amiv1/beets-fillmissing/blob/main/README.md)
file for more details.
## To Do
- [x] Documentation. (If you've added a new command-line flag, for
example, find the appropriate page under `docs/` to describe it.)
- [x] ~Changelog. (Add an entry to `docs/changelog.rst` to the bottom of
one of the lists near the top of the document.)~
- [x] ~Tests. (Very much encouraged but not strictly required.)~
* Update configuration handling to use fully typed confuse API which
will be released in confuse `v2.2.0`.
* Use `Subview`, `.sequence()`, `MappingTemplate`, and typed `OneOf`.
* Replace 'naked' configuration dictionary access with typed
`.get/.as_*` equivalents.
* Add typing annotations and `cached_property` where appropriate.
* Fix related issues in `discogs`, `fetchart`, `lyrics`, `playlist`,
`smartplaylist`, and `titlecase` plugins.
> [!IMPORTANT]
> Depends on https://github.com/beetbox/confuse/pull/187 being merged
and released (as `v2.2.0`)
Added note about setting the translator resource region to Global to
avoid 401 errors.
## Description
Had a bit of trouble with the lyrics plugin where the translator was not
working. Creating a global resource instead of a regional one fixed the
issue. Added a small note to the documentation so that people won't run
into the same issue in the future.
For a bit more info, you need to specify the region in the request
headers if its a regional resource. This is not required for a global
one.
I got a little bit nerdsniped by the problems observed in #5027. In
short, my high-level diagnosis in
https://github.com/beetbox/beets/pull/5027#issuecomment-1857953929 seems
to have been correct: other tests were suppressing the legitimate
failure of a flaky test.
I found the problem by running other tests before the problem test, like
this:
```
$ pytest -k 'test_nonexistant_db or test_delete_removes_item' test/test_ui.py
```
When running `test_nonexistant_db` alone, it fails. When running it like
this with another test that goes first, it passes. That's the problem.
However, `test_delete_removes_item` is just one example that works to
make this problem happen. It appeared that _any_ test in a class that
used our `_common.TestCase` base class had this power. I tracked down
the issue to our `DummyIO` utility, which was having an unintentional
effect even when it was never actually used.
Here's the solution. Instead of restoring `sys.stdin` to
`sys.__stdin__`, we now restore it to whatever it was before we
installed out dummy I/O hooks. This is relevant in pytest, for example,
which installs its *own* `sys.stdin`, which we were then clobbering.
This was leading to the suppression of test failures observed in #5021
and addressed in #5027.
The CI will fail for this PR because it now (correctly) exposes a
failing test. Hopefully by combining this with the fixes in the works in
#5027, we'll be back to a passing test suite. 😃 @Phil305, could
you perhaps help validate that hypothesis?
Edit: @snejus:
I've now consolidated test I/O handling by removing the legacy
`control_stdin`/`capture_stdout` context managers and the custom
`DummyOut` stream, replacing them with a pytest-driven `io` fixture
that:
- provides controllable `stdin` via a lightweight `DummyIn`
- captures `stdout` via `capteesys`
- attaches a `DummyIO` helper to test classes as `self.io`
## Description
Fixes#6302
Todo: Test. I could not figure out how to add a test for this bug.
<!--
- If you believe one of below checkpoints is not required for the change
you
are submitting, cross it out and check the box nonetheless to let us
know.
For example: - [x] ~Changelog~
- Regarding the changelog, often it makes sense to add your entry only
once
reviewing is finished. That way you might prevent conflicts from other
PR's in
that file, as well as keep the chance high your description fits with
the
latest revision of your feature/fix.
- Regarding documentation, bugfixes often don't require additions to the
docs.
- Please remove the descriptive sentences in braces from the enumeration
below,
which helps to unclutter your PR description.
-->
- [x] Documentation. (If you've added a new command-line flag, for
example, find the appropriate page under `docs/` to describe it.)
- [x] Changelog. (Add an entry to `docs/changelog.rst` to the bottom of
one of the lists near the top of the document.)
- [x] Tests. (Very much encouraged but not strictly required.)
This PR hardens the release/changelog generation pipeline so ReST ->
Markdown conversion produces stable, correctly formatted links and
inline code.
- Fix broken rendering of generated references by ensuring RST
`~`-shortened forms like ``` `:class:`~beetsplug._utils...``` are
converted into real Markdown links.
- Updates `extra/release.py` to generate clearer link text for CLI
command refs (e.g., `list-cmd` becomes 'list command') while keeping the
same documentation targets.
- Simplifies the Markdown post-processing step by removing the redundant
`MD_REPLACEMENTS` layer, reducing ad-hoc formatting logic in the release
script.
- Standardizes changelog section header structure in
`docs/changelog.rst` (and the release template) to align with
Sphinx/ReST conventions and avoid ambiguous colon-based headers.
- Strengthens CI by extending `lint-docs` in `pyproject.toml` to fail on
single-backtick inline literals in `.rst`, enforcing double-backtick
literals so `pandoc` does not emit meaningless `<span
class="title-ref">...` output that breaks Markdown rendering.
Net effect: more predictable release notes output, fewer conversion edge
cases, and earlier detection of docs formatting that would render
incorrectly downstream.