- Renamed image_response_mocker to image_request_mock
- Use requests_mock.mocker.Mocker instead of responses.RequestsMock
- Renamed function add to get
This pull request refactors the `test/plugins/test_mbsync.py` test suite
to use `pytest` fixtures and utilities rather than the previous
unittest-based helpers. It also updates the way logs are captured in
tests to use `pytest`'s `caplog` fixture instead of the custom
`capture_log` context manager.
---
This is part of the multi-step efforts to improve logging in beets
https://github.com/beetbox/beets/issues/6553
if import.from_scratch was set in the config, runnning mbsync would
clear any metadata not provided by MBz (replay gain, lyrics, genres...).
we now ignore this setting when running mbsync to preserve metadata.
Fixes: #6613
if import.from_scratch was set in the config, runnning mbsync would
clear any metadata not provided by MBz (replay gain, lyrics, genres...).
we now ignore this setting when running mbsync to preserve metadata.
Fixes: #6613
* The pugin used an empty format string unless `--count` was provided,
resulting in outputs like `: 1`. It now correctly displays duplicated
items.
* `--count` was ignored (aside from above bug), and item count was
always appended to output.
Fixes: https://github.com/beetbox/beets/issues/6476
* the pugin used an empty format string unless --count was provided,
resulting in outputs like `: 1`. it now correctly displays duplicated
items.
* --count was ignored (aside from above bug), and item count was always
appended to output.
I have not created an issue for this but I tried to use mbpseudo to
apply this pseudorelease:
https://musicbrainz.org/release/6c100fef-6abf-41c4-bd21-6f9becaaab6c
When doing that I encountered two errors as seen below, this PR should
fix those issues. The second issue was only apparent once the first
issue was fixed.
```
Sending event: import_task_choice
Traceback (most recent call last):
File "/lsiopy/bin/beet", line 6, in <module>
sys.exit(main())
^^^^^^
File "/lsiopy/lib/python3.12/site-packages/beets/ui/__init__.py", line 1013, in main
_raw_main(args)
File "/lsiopy/lib/python3.12/site-packages/beets/ui/__init__.py", line 992, in _raw_main
subcommand.func(lib, suboptions, subargs)
File "/lsiopy/lib/python3.12/site-packages/beets/ui/commands/import_/__init__.py", line 131, in import_func
import_files(lib, byte_paths, query)
File "/lsiopy/lib/python3.12/site-packages/beets/ui/commands/import_/__init__.py", line 75, in import_files
session.run()
File "/lsiopy/lib/python3.12/site-packages/beets/importer/session.py", line 237, in run
pl.run_parallel(QUEUE_SIZE)
File "/lsiopy/lib/python3.12/site-packages/beets/util/pipeline.py", line 471, in run_parallel
raise exc_info[1].with_traceback(exc_info[2])
File "/lsiopy/lib/python3.12/site-packages/beets/util/pipeline.py", line 336, in run
out = self.coro.send(msg)
^^^^^^^^^^^^^^^^^^^
File "/lsiopy/lib/python3.12/site-packages/beets/util/pipeline.py", line 195, in coro
task = func(*args, task)
^^^^^^^^^^^^^^^^^
File "/lsiopy/lib/python3.12/site-packages/beets/importer/stages.py", line 217, in user_query
_apply_choice(session, task)
File "/lsiopy/lib/python3.12/site-packages/beets/importer/stages.py", line 323, in _apply_choice
task.apply_metadata()
File "/lsiopy/lib/python3.12/site-packages/beets/importer/tasks.py", line 263, in apply_metadata
self.match.apply_metadata()
File "/lsiopy/lib/python3.12/site-packages/beets/autotag/hooks.py", line 609, in apply_metadata
for item, data in self.merged_pairs:
^^^^^^^^^^^^^^^^^
File "/lsiopy/lib/python3.12/site-packages/beets/autotag/hooks.py", line 603, in merged_pairs
(i, ti.merge_with_album(self.info))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/lsiopy/lib/python3.12/site-packages/beets/autotag/hooks.py", line 482, in merge_with_album
album = album_info.raw_data
^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/functools.py", line 998, in __get__
val = self.func(instance)
^^^^^^^^^^^^^^^^^^^
File "/lsiopy/lib/python3.12/site-packages/beets/autotag/hooks.py", line 301, in raw_data
data = {**super().raw_data}
^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/functools.py", line 998, in __get__
val = self.func(instance)
^^^^^^^^^^^^^^^^^^^
File "/lsiopy/lib/python3.12/site-packages/beets/autotag/hooks.py", line 176, in raw_data
data = self.__class__(**self.copy())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: PseudoAlbumInfo.__init__() missing 2 required positional arguments: 'pseudo_release' and 'official_release'
```
```
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "/home/martin/personal/src-ext/beets/beets/__main__.py", line 24, in <module>
main(sys.argv[1:])
~~~~^^^^^^^^^^^^^^
File "/home/martin/personal/src-ext/beets/beets/ui/__init__.py", line 1013, in main
_raw_main(args)
~~~~~~~~~^^^^^^
File "/home/martin/personal/src-ext/beets/beets/ui/__init__.py", line 992, in _raw_main
subcommand.func(lib, suboptions, subargs)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/martin/personal/src-ext/beets/beets/ui/commands/import_/__init__.py", line 131, in import_func
import_files(lib, byte_paths, query)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/martin/personal/src-ext/beets/beets/ui/commands/import_/__init__.py", line 75, in import_files
session.run()
~~~~~~~~~~~^^
File "/home/martin/personal/src-ext/beets/beets/importer/session.py", line 237, in run
pl.run_parallel(QUEUE_SIZE)
~~~~~~~~~~~~~~~^^^^^^^^^^^^
File "/home/martin/personal/src-ext/beets/beets/util/pipeline.py", line 471, in run_parallel
raise exc_info[1].with_traceback(exc_info[2])
File "/home/martin/personal/src-ext/beets/beets/util/pipeline.py", line 336, in run
out = self.coro.send(msg)
File "/home/martin/personal/src-ext/beets/beets/util/pipeline.py", line 195, in coro
task = func(*args, task)
File "/home/martin/personal/src-ext/beets/beets/importer/stages.py", line 217, in user_query
_apply_choice(session, task)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^
File "/home/martin/personal/src-ext/beets/beets/importer/stages.py", line 326, in _apply_choice
task.add(session.lib)
~~~~~~~~^^^^^^^^^^^^^
File "/home/martin/personal/src-ext/beets/beets/importer/tasks.py", line 503, in add
self.album = lib.add_album(self.imported_items())
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/home/martin/personal/src-ext/beets/beets/library/library.py", line 83, in add_album
item.add(self)
~~~~~~~~^^^^^^
File "/home/martin/personal/src-ext/beets/beets/library/models.py", line 84, in add
super().add(lib)
~~~~~~~~~~~^^^^^
File "/home/martin/personal/src-ext/beets/beets/dbcore/db.py", line 717, in add
self.store()
~~~~~~~~~~^^
File "/home/martin/personal/src-ext/beets/beets/library/models.py", line 74, in store
super().store(fields)
~~~~~~~~~~~~~^^^^^^^^
File "/home/martin/personal/src-ext/beets/beets/dbcore/db.py", line 659, in store
tx.mutate(
~~~~~~~~~^
f"INSERT INTO {self._flex_table} "
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...<2 lines>...
(self.id, key, value),
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/martin/personal/src-ext/beets/beets/dbcore/db.py", line 1039, in mutate
return self.db._connection().execute(statement, subvals).lastrowid
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
sqlite3.ProgrammingError: Error binding parameter 3: type 'dict' is not supported
```
This pull request refactors the `test/plugins/test_hook.py` test suite
to use `pytest` fixtures and utilities rather than the previous
unittest-based helpers. It also updates the way logs are captured in
tests to use `pytest`'s `caplog` fixture instead of the custom
`capture_log` context manager.
---
This is part of the multi-step efforts to improve logging in beets
https://github.com/beetbox/beets/issues/6553
## Description
Add `--nomove` / `-M` option to override the `move: yes` config option
during import.
This option is especially useful for reimporting when using players /
music servers like Navidrome that easily loose track of files when they
change path and tags at the same time.
Now one can retag their files without moving them `beet import -M
my-files/`, wait for a rescan, and then `beet move`.
original idea by @snejus
## To Do
<!--
- 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.)
- [ ] Tests. (Very much encouraged but not strictly required.)
## Refactor `ConvertPlugin` to use instance attributes instead of
parameter threading
This PR cleans up the `ConvertPlugin` class by eliminating the pattern
of passing config values as explicit parameters through the call chain.
### What changed
- **Removed `_get_opts_and_config`** — a method that returned a 9-tuple
of config values. All callers unpacked and re-passed these values
explicitly.
- **Config values are now `@cached_property` attributes** on
`ConvertPlugin`: `dest`, `fmt`, `force`, `pretend`, `link`, `hardlink`,
`threads`, `playlist`, `path_formats`.
- **CLI option defaults now set from config**, so
`self.config.set(vars(opts))` in `convert_func` is sufficient to merge
CLI overrides — no more `opts.x or config["x"]` branching in
`_get_opts_and_config`.
- **`get_format` → `command` cached property** returning a typed
`FormatCommand` `NamedTuple`, replacing a free function that accessed
the global `config`.
- **`should_transcode` and `encode`** signatures simplified — `fmt`,
`force`, and `pretend` no longer passed as arguments; methods read from
`self`.
- **`_parallel_convert` and `convert_item`** reduced to `(items,
keep_new)` signatures.
- **Free module-level functions** (`get_format`, `in_no_convert`,
`should_transcode`) removed; equivalent logic now lives as methods,
removing the dependency on the global `config` object.
- Types and `TYPE_CHECKING` imports added throughout; `after_convert`
event registered in `plugins.py`.
### Impact
No behaviour change. The refactor reduces argument surface area
significantly, makes the config read path explicit and testable
per-instance, and eliminates a source of subtle bugs where CLI flags
could silently fall back to wrong defaults.