Skip to content

typing: type beets core - #6941

Open
snejus wants to merge 2 commits into
typing-type-command-handlersfrom
type-beets-core
Open

typing: type beets core#6941
snejus wants to merge 2 commits into
typing-type-command-handlersfrom
type-beets-core

Conversation

@snejus

@snejus snejus commented Aug 18, 2026

Copy link
Copy Markdown
Member

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.
    • events now derives ALL_EVENTS from EventType, so the runtime event list stays aligned with the type definition instead of being maintained separately.
    • 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.

Copilot AI lite review requested due to automatic review settings August 18, 2026 18:56
@snejus
snejus requested review from a team and semohr as code owners August 18, 2026 18:56
@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.

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 make type more tight in beets core. many signature now say what they mean (ui command helpers, library model remove flow, events list, util helpers). big change, many place for bug hide; grug like safety, but need fix few real runtime bugs first.

Changes:

  • add/adjust type annotations across ui command modules, util helpers, logging, and test helpers
  • refactor library model removal to split internal _remove() from public remove(...)
  • derive ALL_EVENTS from EventType so runtime list stay in sync with typing

Reviewed changes

Copilot reviewed 36 out of 36 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/test_importer.py update test helper base + import_session call to pass lib
beets/util/units.py add param/return type hints for time/bytes formatting helpers
beets/util/pipeline.py add typing + tweak doc examples for stage helpers
beets/util/m3u.py type playlist path/contents as bytes + annotate methods
beets/util/extension.py make fix_extension() return bytes consistently
beets/util/config.py type UnknownPairError ctor args
beets/ui/commands/write.py type write command helper signature
beets/ui/commands/version.py type show_version return
beets/ui/commands/utils.py type do_query inputs/outputs
beets/ui/commands/update.py type update helper + make id/album handling explicit
beets/ui/commands/stats.py type stats helper signature
beets/ui/commands/remove.py type remove helper + typed printing via singledispatch
beets/ui/commands/move.py type move helper + add TypeIs helper for album selection
beets/ui/commands/modify.py type modify helper functions and return types
beets/ui/commands/list.py type list helper + adjust usage string handling
beets/ui/commands/help.py type help command ctor + assert root parser type
beets/ui/commands/fields.py type internal print helpers
beets/ui/commands/config.py type config_edit signature
beets/ui/commands/completion.py type completion script generator + minor renames
beets/ui/commands/init.py type default command list + __getattr__ return
beets/ui/init.py add/adjust types across ui core (parsing, prompts, parsers)
beets/test/helper.py add typed attrs + type ctor for TerminalImportSessionFixture
beets/test/fixtures.py type fixture model getters/types + tweak DummyIMBackend version handling
beets/test/_common.py type test helpers, require lib for import_session
beets/plugins.py type plugin ctor + import error ctor
beets/metadata_plugins.py type contextmanager yield type
beets/logging.py type logger/formatter methods and overloads
beets/library/models.py split remove into internal _remove() + public remove contract
beets/library/init.py type __getattr__ return
beets/events.py derive ALL_EVENTS from EventType via typing introspection
beets/dbcore/db.py type add() return + widen formatted included_keys type
beets/context.py add future annotations + type contextmanager yield
beets/autotag/match.py type helper return as None
beets/autotag/distance.py tighten Distance numeric ops typing + reject unsupported operands
beets/autotag/init.py type __getattr__ return
beets/init.py type __getattr__ return
Suppressed comments (1)

beets/autotag/distance.py:226

  • grug see bug: Distance.sub/rsub accept Distance, but then do self.distance - other / other - self.distance. when other is Distance, float - Distance raise TypeError. need unwrap Distance to float before math.
    def __sub__(self, other: object) -> float:
        if not isinstance(other, (float, Distance)):
            raise TypeError(
                "unsupported operand type(s) for -: "
                f"'Distance' and {type(other).__name__!r}"

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread beets/autotag/distance.py Outdated
Comment thread beets/test/_common.py Outdated
Comment thread beets/util/pipeline.py
@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.42553% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.12%. Comparing base (6dacb0c) to head (6dbbc76).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
beets/autotag/distance.py 70.00% 3 Missing and 3 partials ⚠️
beets/ui/__init__.py 93.22% 1 Missing and 3 partials ⚠️
beets/events.py 0.00% 3 Missing ⚠️
beets/ui/commands/import_/session.py 40.00% 1 Missing and 2 partials ⚠️
beets/ui/commands/update.py 66.66% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@                       Coverage Diff                        @@
##           typing-type-command-handlers    #6941      +/-   ##
================================================================
- Coverage                         76.29%   76.12%   -0.17%     
================================================================
  Files                               164      164              
  Lines                             21529    21556      +27     
  Branches                           3332     3335       +3     
================================================================
- Hits                              16425    16410      -15     
- Misses                             4315     4357      +42     
  Partials                            789      789              
Files with missing lines Coverage Δ
beets/__init__.py 62.50% <100.00%> (ø)
beets/autotag/__init__.py 71.42% <100.00%> (+2.19%) ⬆️
beets/autotag/match.py 88.09% <ø> (ø)
beets/context.py 100.00% <100.00%> (ø)
beets/dbcore/db.py 94.45% <100.00%> (ø)
beets/library/__init__.py 100.00% <100.00%> (ø)
beets/library/models.py 87.22% <100.00%> (+0.01%) ⬆️
beets/logging.py 95.23% <100.00%> (ø)
beets/metadata_plugins.py 91.01% <100.00%> (ø)
beets/plugins.py 89.56% <100.00%> (ø)
... and 23 more

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

test/test_importer.py:1624

  • grug see this test class stop inherit unittest.TestCase, but class name still not start with "Test". pytest default no collect this class, so these tests no run. rename class to start with "Test".
    beets/util/m3u.py:18
  • grug see M3UFile init type say path is bytes, but callers pass pathlib.Path (and normpath accept PathLike). type too strict, make type checker sad. make param PathLike and add TYPE_CHECKING import.
    beets/autotag/distance.py:220
  • grug see lt/__sub say they accept Distance, but they do self.distance < other / self.distance - other. when other is Distance, this still TypeError. coerce with float(other) so Distance-to-Distance work, and keep NotImplemented for weird types.
    def __lt__(self, other: object) -> bool:
        if isinstance(other, (int, float, Distance)):
            return self.distance < other

        return NotImplemented

Comment thread beets/util/extension.py Outdated
@snejus
snejus force-pushed the type-beets-core branch 2 times, most recently from 65cc028 to 60bfc97 Compare August 18, 2026 22:10
@snejus
snejus requested a lite review from Copilot August 18, 2026 22:10

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

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

beets/util/m3u.py:17

  • grug see M3UFile init say path: bytes, but tests and caller pass Path. typing now lie. use PathLike so type check not fight real code.
    beets/ui/commands/help.py:27
  • grug no like assert for user CLI path. python -O remove assert, then code crash weird. do real check and raise UserError.
        assert isinstance(self.root_parser, ui.SubcommandsOptionParser)

beets/ui/commands/move.py:88

  • grug see move_items type say dest_path always PathLike, but move_func pass None. typing now fight call site. also if dest_path treat empty bytes as None. make dest_path Optional and check is not None.
def move_items(
    lib: Library,
    dest_path: PathLike,
    query: list[str],
    copy: bool,
    album: bool,
    pretend: bool,
    confirm: bool = False,
    export: bool = False,
) -> None:
    """Moves or copies items to a new base directory, given by dest. If
    dest is None, then the library's base directory is used, making the
    command "consolidate" files.
    """
    dest = os.fsencode(dest_path) if dest_path else None

beets/autotag/distance.py:235

  • grug see Distance lt/__sub compare float with Distance object. runtime maybe ok, but type checker cry. also return NotImplemented not match annotation. unwrap Distance to float/int first, and ignore return type for NotImplemented line.
    def __lt__(self, other: object) -> bool:
        if isinstance(other, (int, float, Distance)):
            return self.distance < other

        return NotImplemented

    def __float__(self) -> float:
        return self.distance

    def __sub__(self, other: object) -> float:
        if isinstance(other, (int, float, Distance)):
            return self.distance - other

        return NotImplemented

    def __rsub__(self, other: object) -> float:
        if isinstance(other, (int, float, Distance)):
            return other - self.distance

        return NotImplemented

Comment thread beets/events.py
| NoArgsEventType
| AfterConvertEventType
)
ALL_EVENTS = list(chain.from_iterable(get_args(e) for e in get_args(EventType)))
@snejus
snejus force-pushed the typing-type-command-handlers branch from 74d1233 to 7f8f933 Compare August 18, 2026 22:29
@snejus
snejus force-pushed the typing-type-command-handlers branch from 7f8f933 to ba078ad Compare August 19, 2026 01:40
@snejus
snejus force-pushed the typing-type-command-handlers branch from ba078ad to 8afa61f Compare August 19, 2026 01:52
@snejus
snejus requested a review from JOJ0 as a code owner August 19, 2026 01:52
@snejus
snejus force-pushed the typing-type-command-handlers branch from 8afa61f to ed37e83 Compare August 19, 2026 01:58
@snejus
snejus force-pushed the typing-type-command-handlers branch from ed37e83 to f083d14 Compare August 19, 2026 02:22
@snejus
snejus force-pushed the typing-type-command-handlers branch from f083d14 to 11bee48 Compare August 19, 2026 03:59
@snejus
snejus force-pushed the typing-type-command-handlers branch from 11bee48 to 2191fd8 Compare August 19, 2026 06:44
@snejus
snejus force-pushed the typing-type-command-handlers branch from 2191fd8 to 0ebd95c Compare August 19, 2026 09:49
@snejus
snejus force-pushed the typing-type-command-handlers branch from 0ebd95c to e7b3692 Compare August 19, 2026 10:43
@snejus
snejus force-pushed the typing-type-command-handlers branch from e7b3692 to 40538b7 Compare August 19, 2026 22:57
@snejus
snejus force-pushed the typing-type-command-handlers branch from 40538b7 to 6dacb0c Compare August 20, 2026 07:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants