Commit Graph

15228 Commits

Author SHA1 Message Date
Šarūnas Nejus
ffb2ad88ad Add commands to labeler config 2026-08-27 20:53:41 +01:00
Šarūnas Nejus
31c917b8c8 ui: remove do_query to tighten input_select_objects (#6942)
Part of #6924.

Closes #2802.
Closes #4880.

## Summary

- Fix `modify` interactive selection by displaying the changes already
prepared for each object and syncing only the objects returned by
`ui.input_select_objects`. Previously, the selection prompt could apply
the pending mutations again, producing incorrect output or raising a
`TypeError`.
- Fix `move --album --timid` when choosing individual albums. The
selection preview now uses the command's model-specific path expansion,
so it displays the album's item paths instead of trying to read
item-only fields from an `Album`.
- Remove `beets.ui.commands.utils.do_query` and let `modify`, `move`,
`remove`, `update`, `write`, and `edit` own their item and album queries
and no-match behavior.
- Keep thin model-specific query wrappers where mypy needs help
preserving item and album types. Shared `modify` parameters use
`Unpack[TypedDict]` so callers remain checked without duplicating the
parameter list.
- Narrow the return type of `ui.input_select_objects` from `Any` to
`Sequence[T]` and keep `remove` confirmation counts accurate.
- Document both user-facing fixes in the changelog.

## Tests

- Add CLI regression coverage for `modify` selection output and timid
album selection in `move`.
- Update command tests for `fields`, `import`, `list`, `move`, `remove`,
and `update` to exercise their CLI entrypoints, including selective
removal and confirmation counts.
- Remove the obsolete `do_query` unit tests with the helper.
- GitHub checks pass for linting, formatting, typing, docs, CodeQL, base
installation, all Ubuntu Python 3.11-3.14 jobs, and Windows Python 3.13.
The remaining matrix jobs were still running when checked.
2026-08-27 09:50:16 +01:00
Šarūnas Nejus
03877cc20b move: fix moving albums in timid mode 2026-08-27 06:47:40 +01:00
Šarūnas Nejus
e5846915d4 modify: fix selecting objects 2026-08-27 06:47:39 +01:00
Šarūnas Nejus
efc7e4f206 remove: fix ui formatting and test it 2026-08-27 06:47:39 +01:00
Šarūnas Nejus
2971393618 Replace do_query with model-specific logic
This allows to define a proper return type for input_select_objects.
2026-08-27 06:47:39 +01:00
Šarūnas Nejus
dd841f6b99 Test commands by CLI invocation 2026-08-27 01:31:43 +01:00
Šarūnas Nejus
316cf47a47 Add missing commit from the previous PR to git blame ignores 2026-08-27 01:31:40 +01:00
Šarūnas Nejus
a3268e68de typing: type beets core (#6941)
Part of #6924.

- This PR tightens typing across core surfaces in `beets`, especially
around `ui` command handling, `library` models, `autotag`, `util`,
logging, and test helpers.

- Architecturally, the change makes several implicit contracts explicit:
- `ui` command parsers and subcommands now have clearer typed
interfaces.
- `library` removal flow is split into internal `_remove()` and public
`remove(...)`, which better separates shared database-change behavior
from model-specific delete logic.
- Utilities like `fix_extension()` and playlist helpers now expose more
precise return/value types.

- High-level impact is mostly safety and maintainability rather than new
functionality. The goal is to make core APIs easier to reason about,
easier to type-check, and less likely to drift between declared and
actual behavior.

- There are a few small behavioral hardening changes:
- `Distance` arithmetic/comparison now rejects unsupported operand types
instead of silently accepting invalid values.
- Some CLI and command code paths were adjusted to make album/item
branching explicit and type-safe.
- A few test fixtures and helpers were updated to match the stricter
contracts.

- Reviewer takeaway: this is primarily a core typing cleanup with light
refactoring, aimed at improving internal API clarity and catching
mistakes earlier, with only limited runtime behavior changes in edge
cases.
2026-08-25 00:02:41 +01:00
Šarūnas Nejus
01375c7392 typing: fix core annotations 2026-08-22 17:49:49 +01:00
Šarūnas Nejus
029de94b20 typing: add missing core annotations 2026-08-22 17:49:49 +01:00
Šarūnas Nejus
07f39c5204 lyrics: don't match LRCLib entries that have no lyrics (#6900)
## Description

LRCLib stores track metadata independently of the lyrics themselves, so
an entry can come back with both `plainLyrics` and `syncedLyrics` null
while `instrumental` is still `False`.

`LRCLyrics.is_valid` accepted such an entry as a match on duration
alone, and `get_text` then returned `self.plain`, i.e. `None`, despite
being annotated `-> str`. The `None` propagated into `Lyrics`, and the
first access of its text raised:

```
AttributeError: 'NoneType' object has no attribute 'splitlines'
  File "beets/util/lyrics.py", line 108, in _split_lines
    for line in self.text.splitlines()
```

`beet lyrics` surfaces this per track, but during an **import** the
exception escapes the pipeline stage and aborts the entire run, so one
such track strands every file queued behind it. Instrumental-heavy
material (lo-fi, game soundtracks, ambient) hits it often.

### Reproducing

Real response from the public API,
`https://lrclib.net/api/search?track_name=Anther&artist_name=Blue%20Wednesday`:

```json
{
  "id": 37048543,
  "trackName": "Anther",
  "artistName": "Blue Wednesday",
  "duration": 189.0,
  "instrumental": false,
  "plainLyrics": null,
  "syncedLyrics": null
}
```

Against `master`, with no configuration involved:

```
candidate.is_valid  = True     <- accepted as a match
get_text() returned = None     <- annotated `-> str`
AttributeError: 'NoneType' object has no attribute 'splitlines'
```

## Changes

- An entry with no lyrics text at all is no longer a valid match, so the
search continues to other candidates and backends and ultimately reports
that no lyrics were found. This is deliberately kept distinct from an
instrumental track, where "no lyrics" is itself the answer and the
existing `instrumental` handling is unchanged. Marking these as
instrumental would assert something the API response does not tell us:
the lyrics may simply not have been contributed yet.
- A null `plainLyrics` now falls back to synced lyrics rather than
discarding lyrics that are present.
- `LRCLibAPI.Item.plainLyrics` and `LRCLyrics.plain` are annotated as
nullable, matching what the API actually returns.
2026-08-22 17:46:44 +01:00
Šarūnas Nejus
d9e2b65758 Merge branch 'master' into fix/lrclib-null-lyrics 2026-08-22 17:41:55 +01:00
Šarūnas Nejus
096106d0e1 typing: add types to command handlers (#6932)
First part of #6924.

## Summary

- Give command handlers across core commands and many plugins a
consistent `Library`, options, and `list[str]` interface.
- Describe command-specific options with small `Protocol` types where
handlers depend on particular flags, while retaining `optparse.Values`
for handlers that do not need a narrower shape. This makes the CLI
boundary easier for type checkers to follow without coupling handlers to
a concrete options container.
- Broaden `beets.dbcore.sort` from `list` to `Sequence` and tighten
several item/album collection annotations to match the data actually
passed between APIs.
- Fix small runtime mismatches exposed while making these types
concrete:
- `beetsplug.ipfs` invokes `PlayPlugin._play_command` with the expected
options shape and closes remote libraries after use.
- `beetsplug.bpd` reads `control_port` through the config API that
matches its actual value type.
- `beets.ui.commands.update` handles excluded fields, missing prior
items, optional flags, and byte paths safely.
- Command option defaults and config handoffs are normalized where their
runtime values can be optional.

## Tests

- Add regression coverage for IPFS playback through the Play plugin and
context-managed remote libraries.
- Refresh lyrics integration fixtures for the current LRCLIB and LRCGET
responses.

Most changes are annotations and interface clarification. The behavioral
changes are limited to the mismatches listed above.
2026-08-21 21:07:31 +01:00
Šarūnas Nejus
467cf7a8b7 Fix lyrics integration tests 2026-08-21 20:56:39 +01:00
Šarūnas Nejus
914441da16 typing: fix command handlers types 2026-08-21 20:56:38 +01:00
Šarūnas Nejus
a3bb963ea0 ipfs: fix play command 2026-08-20 00:51:44 +01:00
Šarūnas Nejus
bf576238c8 Add commit to blame ignore 2026-08-20 00:51:44 +01:00
Šarūnas Nejus
6241139600 typing: define explicit optparse.Values for each command 2026-08-19 23:57:05 +01:00
Šarūnas Nejus
14eabc6060 typing: add types to command handlers 2026-08-19 23:57:01 +01:00
Šarūnas Nejus
293ae110c7 typing: type utils and modules that are mostly missing annotations (#6925)
Fixes: https://github.com/beetbox/beets/issues/6923

### What changed

- This PR is mostly a typing and module-boundary cleanup.
- Code now prefers public package exports like `beets.library` and
`beets.dbcore` instead of reaching into deeper internal modules.
- Shared model typing was renamed from `AnyLibModel` to `AlbumOrItem`,
which makes intent clearer where code handles either an `Album` or an
`Item`.
- Several typing fixes were added around `import`, `embedart`,
`_utils.art`, and `beets.util.functemplate`.

### Architecture impact

- The main architectural shift is toward using stable, package-level
APIs such as `beets.library` and `beets.dbcore` as the import boundary.
- `dbcore.Results` now behaves like a `Sequence`, which lets callers
depend on a simpler, more general interface instead of a concrete
internal result type.
- `beets.util.functemplate` got a deeper type pass and some small
internal cleanup, but its role in the system stays the same.

### High-level impact

- Improves type safety and IDE support across importer, library, plugin,
and template code.
- Reduces coupling to internal module layout, which should make future
refactors safer.
- Makes a few core interfaces easier to understand and reuse, especially
around library model collections and import-session callbacks.
- Overall, this looks like low-risk maintenance work with small
correctness improvements and no intended feature change.
2026-08-18 23:28:52 +01:00
Šarūnas Nejus
c4953067cb Add commits to blame ignore 2026-08-18 23:22:16 +01:00
Šarūnas Nejus
33a01fe0b3 typing: rename AnyLibModel -> AlbumOrItem 2026-08-18 23:20:39 +01:00
Šarūnas Nejus
27900f58fb Prefer imports from modules that provide import aliases 2026-08-18 23:20:39 +01:00
Šarūnas Nejus
f37306b0cc typing: remove redundant types 2026-08-18 23:20:39 +01:00
Šarūnas Nejus
0cae01cad2 typing: fix types in beets.util.functemplate 2026-08-18 23:20:39 +01:00
Šarūnas Nejus
d8fc064e9c typing: add types to beets.util.functemplate 2026-08-18 23:20:39 +01:00
Šarūnas Nejus
d143e2cb92 typing: fix types under beets.ui.commands.import_ 2026-08-18 23:20:39 +01:00
Šarūnas Nejus
2a9786aad7 typing: add types to beets.ui.import_ 2026-08-18 23:20:39 +01:00
Šarūnas Nejus
4aa5aa62ac typing: fix types in beetsplug._utils.art 2026-08-18 23:20:38 +01:00
Šarūnas Nejus
6acef1d586 typing: add types to beetsplug._utils.art 2026-08-18 23:20:38 +01:00
Šarūnas Nejus
26713de0d6 typing: add types to embedart 2026-08-18 23:20:38 +01:00
Šarūnas Nejus
9adcb601f8 typing: make dbcore.db.Results a Sequence
Now we are able to type Results simply as a Sequence. This way, we can
also now use a more generic representation `Sequence[LibModel]` to refer
to *any* of the two models.
2026-08-18 23:20:38 +01:00
Šarūnas Nejus
09e06f3efb typing: type plugins.send and BeetsPlugin.register_listener explicitly (#6886)
Fixes: https://github.com/beetbox/beets/issues/6921

- Adds explicit typing to the plugin event system in `beets.plugins`,
including per-event argument shapes and return types for
`register_listener()` and `send()`. This makes the listener API much
clearer and turns implicit plugin contracts into checked interfaces.

- Updates all plugins to match those typed event signatures, by adding
concrete parameter/return types and aligning handlers with the events
they subscribe to. The architectural effect is better consistency across
plugin boundaries, especially around importer hooks and metadata
callbacks.

- Includes a small set of follow-up fixes uncovered by the typing work
in places like `advancedrewrite`, `badfiles`, `playlist`, `permissions`,
`importsource`, and query/path handling. These are mostly correctness
and config/path-type cleanups rather than new features.

- High-level impact: this change improves maintainability and static
analysis across the plugin layer, reduces ambiguity in hook behavior,
and makes future plugin changes safer without changing the overall
architecture or user-facing workflows in a major way.
2026-08-18 23:20:16 +01:00
Šarūnas Nejus
1c9d4bb8a8 Add commits to git blame ignore 2026-08-18 10:46:53 +01:00
Šarūnas Nejus
0c05551251 Update events docs 2026-08-18 10:46:33 +01:00
Šarūnas Nejus
12553051af typing: fix typing in plugins 2026-08-18 10:46:33 +01:00
Šarūnas Nejus
1969622874 typing: fix types in event handlers 2026-08-18 10:46:33 +01:00
Šarūnas Nejus
5039c8f6cf typing: add types to event handlers 2026-08-18 10:46:33 +01:00
Šarūnas Nejus
dcc1fd0e4a typing: Import events module instead 2026-08-18 10:46:33 +01:00
Šarūnas Nejus
e9021e17e9 typing: Define events in a separate module, import explicitly 2026-08-18 10:46:33 +01:00
Šarūnas Nejus
88627e062b typing: type handlers and listeners explicitly 2026-08-18 10:46:33 +01:00
J0J0 Todos
e1ffd17302 lastgenre: Refactor _get_genre (#6474)
## Description

The monolithic `_get_genre` method was broken down into several private
instance methods and refactored for readability. The contract is kept
and is already well tested (`test_get_genre`)

- **Core Helpers** - were moved from within `_get_genre` to a reusable
instance method and a `cached_property`:
- `_try_resolve_stage`: Handles the canonicalization and logging of
genres for a specific stage.
- `fallback`: Provides the configured fallback genre. Is used as a last
resort in `_try_resolve_existing_genres` and when `_get_genre` couldn't
find any genre in any stage at all.

- **Lookup Stages** - some were complex enough to deserve their own
instance method for readability, some stay inline in `_get_genre`:
- `_try_resolve_existing_genres`: Manages the initial check for
pre-existing genres and the `cleanup_existing` logic when `force` is
disabled.
    - track stage: stays inline
- album stage: indentical to track stage, but not worth moving /
deduplication doesn't buy much (see subsequent PR though)
- `_fetch_artist_stage`: Fetches and resolves artist-level genres,
including multi-valued album artists and "Various Artists" logic.
- `_fetch_va_genres`: specifically handles the plurality logic for
"Various Artists" albums.
- **Fallbacks**:
- `_try_resolve_original_fallback`: Handles the "keep_existing" logic
that attempts to use/canonicalize originally present genres if no new
ones are found.

Make sure to also look at subsequent PR's:

- https://github.com/beetbox/beets/pull/6890
- https://github.com/beetbox/beets/pull/6893

## To Do

- [x] ~Documentation~
- [x] Changelog. (Not required, refactor only)
- [x] ~Tests~ (_get_genre was already well covered and the signature of
the method was kept)
2026-08-18 07:15:51 +02:00
J0J0 Todos
87633a892b lastgenre: Fix CanonTree type alias docs comment 2026-08-18 07:01:09 +02:00
J0J0 Todos
c1a98e225d lastgenre: Type alias GenresWithLabel
Note that the artist stage is slightly different and GenresWithLabel does
not fit there.
2026-08-18 07:01:09 +02:00
J0J0 Todos
5b0e9e94cd lastgenre: Test new fetch_va_genres helper 2026-08-18 07:01:09 +02:00
J0J0 Todos
1059c2663d lastgenre: Rename genres to existing_genres 2026-08-18 07:01:09 +02:00
J0J0 Todos
5d651170af lastgenre: Extract original fallback stage to method 2026-08-18 07:01:09 +02:00
J0J0 Todos
f202d54ea4 lastgenre: Extract complex artist stage to methods 2026-08-18 07:01:09 +02:00
J0J0 Todos
d03537c0d5 lastgenre: Name vars resolved instead of result 2026-08-18 07:01:09 +02:00