Skip to content

typing: type plugins.send and BeetsPlugin.register_listener explicitly - #6886

Open
snejus wants to merge 6 commits into
masterfrom
type-handlers-and-listeners-explicitly
Open

typing: type plugins.send and BeetsPlugin.register_listener explicitly#6886
snejus wants to merge 6 commits into
masterfrom
type-handlers-and-listeners-explicitly

Conversation

@snejus

@snejus snejus commented Jul 29, 2026

Copy link
Copy Markdown
Member
  • 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.

Copilot AI review requested due to automatic review settings July 29, 2026 09:44
@snejus
snejus requested review from a team, asardaes, henry-oberholtzer and semohr as code owners July 29, 2026 09:44
@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.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

❌ 9 Tests Failed:

Tests completed Failed Passed Skipped
2751 9 2742 69
View the top 3 failed test(s) by shortest run time
test/dbcore/test_query.py::TestPathQuery::test_implicit[slash-with-explicit-field-no-match]
Stack Traces | 0.009s run time
self = <test.dbcore.test_query.TestPathQuery object at 0x7f0eb9fc5210>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f0eaa4d4d30>
lib = <beets.library.library.Library object at 0x7f0ebe6fa2c0>
q = b'title:/a/b', expected_titles = []

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.skipif(sys.platform == #x1B[33m"#x1B[39;49;00m#x1B[33mwin32#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, reason=WIN32_NO_IMPLICIT_PATHS)#x1B[90m#x1B[39;49;00m
    #x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[90m#x1B[39;49;00m
        #x1B[33m"#x1B[39;49;00m#x1B[33mq, expected_titles#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
        [#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m/aaa/bb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mpath item#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mslashed-query#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m/aaa/bb , /aaa#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mpath item#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mpath-in-or-query#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mc.mp3#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mno-slash-no-match#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mtitle:/a/b#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mslash-with-explicit-field-no-match#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
        ],#x1B[90m#x1B[39;49;00m
    )#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_implicit#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, monkeypatch, lib, q, expected_titles):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Test implicit path detection when queries contain path separators."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        monkeypatch.setattr(#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mbeets.dbcore.query.PathQuery.is_path_query#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94mlambda#x1B[39;49;00m path: #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m {i.title #x1B[94mfor#x1B[39;49;00m i #x1B[95min#x1B[39;49;00m lib.items(q)} == #x1B[96mset#x1B[39;49;00m(expected_titles)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE       AssertionError: assert {'absolute it...ckslash', ...} == set()#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m
#x1B[1m#x1B[31mE         Extra items in the left set:#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mcaps path#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith underscore#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mabsolute item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33manother item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith backslash#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mpath item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith percent#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mrelative item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m
#x1B[1m#x1B[31mE         Full diff:#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[91m- set()#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+ {#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'absolute item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'another item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'caps path',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'path item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'relative item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with backslash',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with percent',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with underscore',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+ }#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m

#x1B[1m#x1B[31mtest/dbcore/test_query.py#x1B[0m:398: AssertionError
test/dbcore/test_query.py::TestPathQuery::test_implicit[slashed-query]
Stack Traces | 0.009s run time
self = <test.dbcore.test_query.TestPathQuery object at 0x7f0eb9fc5390>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f0eaa547d60>
lib = <beets.library.library.Library object at 0x7f0ebe6fa2c0>, q = b'/aaa/bb'
expected_titles = ['path item']

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.skipif(sys.platform == #x1B[33m"#x1B[39;49;00m#x1B[33mwin32#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, reason=WIN32_NO_IMPLICIT_PATHS)#x1B[90m#x1B[39;49;00m
    #x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[90m#x1B[39;49;00m
        #x1B[33m"#x1B[39;49;00m#x1B[33mq, expected_titles#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
        [#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m/aaa/bb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mpath item#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mslashed-query#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m/aaa/bb , /aaa#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mpath item#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mpath-in-or-query#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mc.mp3#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mno-slash-no-match#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mtitle:/a/b#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mslash-with-explicit-field-no-match#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
        ],#x1B[90m#x1B[39;49;00m
    )#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_implicit#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, monkeypatch, lib, q, expected_titles):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Test implicit path detection when queries contain path separators."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        monkeypatch.setattr(#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mbeets.dbcore.query.PathQuery.is_path_query#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94mlambda#x1B[39;49;00m path: #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m {i.title #x1B[94mfor#x1B[39;49;00m i #x1B[95min#x1B[39;49;00m lib.items(q)} == #x1B[96mset#x1B[39;49;00m(expected_titles)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE       AssertionError: assert {'absolute it...ckslash', ...} == {'path item'}#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m
#x1B[1m#x1B[31mE         Extra items in the left set:#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mcaps path#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith underscore#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mabsolute item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33manother item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith backslash#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith percent#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mrelative item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m
#x1B[1m#x1B[31mE         Full diff:#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[90m #x1B[39;49;00m {#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'absolute item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'another item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'caps path',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[90m #x1B[39;49;00m     'path item',#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'relative item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with backslash',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with percent',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with underscore',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[90m #x1B[39;49;00m }#x1B[90m#x1B[39;49;00m#x1B[0m

#x1B[1m#x1B[31mtest/dbcore/test_query.py#x1B[0m:398: AssertionError
test/dbcore/test_query.py::TestPathQuery::test_implicit[no-slash-no-match]
Stack Traces | 0.01s run time
self = <test.dbcore.test_query.TestPathQuery object at 0x7f0eb9fc73a0>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f0eaa4928f0>
lib = <beets.library.library.Library object at 0x7f0ebe6fa2c0>, q = b'c.mp3'
expected_titles = []

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.skipif(sys.platform == #x1B[33m"#x1B[39;49;00m#x1B[33mwin32#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, reason=WIN32_NO_IMPLICIT_PATHS)#x1B[90m#x1B[39;49;00m
    #x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[90m#x1B[39;49;00m
        #x1B[33m"#x1B[39;49;00m#x1B[33mq, expected_titles#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
        [#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m/aaa/bb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mpath item#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mslashed-query#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m/aaa/bb , /aaa#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mpath item#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mpath-in-or-query#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mc.mp3#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mno-slash-no-match#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mtitle:/a/b#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mslash-with-explicit-field-no-match#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
        ],#x1B[90m#x1B[39;49;00m
    )#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_implicit#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, monkeypatch, lib, q, expected_titles):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Test implicit path detection when queries contain path separators."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        monkeypatch.setattr(#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mbeets.dbcore.query.PathQuery.is_path_query#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94mlambda#x1B[39;49;00m path: #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m {i.title #x1B[94mfor#x1B[39;49;00m i #x1B[95min#x1B[39;49;00m lib.items(q)} == #x1B[96mset#x1B[39;49;00m(expected_titles)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE       AssertionError: assert {'absolute it...ckslash', ...} == set()#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m
#x1B[1m#x1B[31mE         Extra items in the left set:#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mcaps path#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith underscore#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mabsolute item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33manother item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith backslash#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mpath item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith percent#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mrelative item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m
#x1B[1m#x1B[31mE         Full diff:#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[91m- set()#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+ {#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'absolute item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'another item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'caps path',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'path item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'relative item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with backslash',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with percent',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with underscore',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+ }#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m

#x1B[1m#x1B[31mtest/dbcore/test_query.py#x1B[0m:398: AssertionError
test/dbcore/test_query.py::TestPathQuery::test_implicit[path-in-or-query]
Stack Traces | 0.01s run time
self = <test.dbcore.test_query.TestPathQuery object at 0x7f0eb9fc5240>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f0eaa4d61a0>
lib = <beets.library.library.Library object at 0x7f0ebe6fa2c0>
q = b'/aaa/bb , /aaa', expected_titles = ['path item']

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.skipif(sys.platform == #x1B[33m"#x1B[39;49;00m#x1B[33mwin32#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, reason=WIN32_NO_IMPLICIT_PATHS)#x1B[90m#x1B[39;49;00m
    #x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[90m#x1B[39;49;00m
        #x1B[33m"#x1B[39;49;00m#x1B[33mq, expected_titles#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
        [#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m/aaa/bb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mpath item#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mslashed-query#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m/aaa/bb , /aaa#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [#x1B[33m"#x1B[39;49;00m#x1B[33mpath item#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mpath-in-or-query#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mc.mp3#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mno-slash-no-match#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            _p(#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mtitle:/a/b#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, [], #x1B[96mid#x1B[39;49;00m=#x1B[33m"#x1B[39;49;00m#x1B[33mslash-with-explicit-field-no-match#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
        ],#x1B[90m#x1B[39;49;00m
    )#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_implicit#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, monkeypatch, lib, q, expected_titles):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Test implicit path detection when queries contain path separators."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        monkeypatch.setattr(#x1B[90m#x1B[39;49;00m
            #x1B[33m"#x1B[39;49;00m#x1B[33mbeets.dbcore.query.PathQuery.is_path_query#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94mlambda#x1B[39;49;00m path: #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m {i.title #x1B[94mfor#x1B[39;49;00m i #x1B[95min#x1B[39;49;00m lib.items(q)} == #x1B[96mset#x1B[39;49;00m(expected_titles)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE       AssertionError: assert {'absolute it...ckslash', ...} == {'path item'}#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m
#x1B[1m#x1B[31mE         Extra items in the left set:#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mcaps path#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith underscore#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mabsolute item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33manother item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith backslash#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith percent#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mrelative item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m
#x1B[1m#x1B[31mE         Full diff:#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[90m #x1B[39;49;00m {#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'absolute item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'another item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'caps path',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[90m #x1B[39;49;00m     'path item',#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'relative item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with backslash',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with percent',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with underscore',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[90m #x1B[39;49;00m }#x1B[90m#x1B[39;49;00m#x1B[0m

#x1B[1m#x1B[31mtest/dbcore/test_query.py#x1B[0m:398: AssertionError
test\plugins\test_mpdstats.py::plugins::test_mpdstats::TestMPDStats::test_get_item
Stack Traces | 0.01s run time
self = <test.plugins.test_mpdstats.TestMPDStats object at 0x0000022AC093C7D0>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_get_item#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
        item_path = util.normpath(#x1B[33m"#x1B[39;49;00m#x1B[33m/foo/bar.flac#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        item = Item(title=#x1B[33m"#x1B[39;49;00m#x1B[33mtitle#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, path=item_path, #x1B[96mid#x1B[39;49;00m=#x1B[94m1#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        item.add(#x1B[96mself#x1B[39;49;00m.lib)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        log = Mock()#x1B[90m#x1B[39;49;00m
        mpdstats = MPDStats(#x1B[96mself#x1B[39;49;00m.lib, log)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m #x1B[96mstr#x1B[39;49;00m(mpdstats.get_item(item_path)) == #x1B[96mstr#x1B[39;49;00m(item)#x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m mpdstats.get_item(#x1B[33m"#x1B[39;49;00m#x1B[....../some/non-existing/path#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m) #x1B[95mis#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[31mtest\plugins\test_mpdstats.py#x1B[0m:32: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
#x1B[1m#x1B[31mbeetsplug\mpdstats.py#x1B[0m:154: in get_item
    #x1B[0mquery = PathQuery(#x1B[33m"#x1B[39;49;00m#x1B[33mpath#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, path)#x1B[90m#x1B[39;49;00m
            ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mbeets\dbcore\query.py#x1B[0m:291: in __init__
    #x1B[0mpattern = os.path.join(music_dir, pattern)#x1B[90m#x1B[39;49;00m
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31m<frozen ntpath>#x1B[0m:139: in join
    #x1B[0m#x1B[04m#x1B[91m?#x1B[39;49;00m#x1B[04m#x1B[91m?#x1B[39;49;00m#x1B[04m#x1B[91m?#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

funcname = 'join'
args = (b'C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\tmp7vwts_b_\\libdir', '.../some/non-existing/path')

>   #x1B[0m#x1B[04m#x1B[91m?#x1B[39;49;00m#x1B[04m#x1B[91m?#x1B[39;49;00m#x1B[04m#x1B[91m?#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE   TypeError: Can't mix strings and bytes in path components#x1B[0m

#x1B[1m#x1B[31m<frozen genericpath>#x1B[0m:191: TypeError
test/plugins/test_importadded.py::ImportAddedTest::test_import_album_with_added_dates
Stack Traces | 0.039s run time
self = <test.plugins.test_importadded.ImportAddedTest testMethod=test_import_album_with_added_dates>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_import_album_with_added_dates#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m.importer.run()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        album = #x1B[96mself#x1B[39;49;00m.lib.albums().get()#x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m album.added == #x1B[96mself#x1B[39;49;00m.min_mtime#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE       AttributeError: 'NoneType' object has no attribute 'added'#x1B[0m

#x1B[1m#x1B[31mtest/plugins/test_importadded.py#x1B[0m:51: AttributeError
test/plugins/test_importadded.py::ImportAddedTest::test_import_album_with_preserved_mtimes
Stack Traces | 0.039s run time
self = <test.plugins.test_importadded.ImportAddedTest testMethod=test_import_album_with_preserved_mtimes>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_import_album_with_preserved_mtimes#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m.config[#x1B[33m"#x1B[39;49;00m#x1B[33mimportadded#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m][#x1B[33m"#x1B[39;49;00m#x1B[33mpreserve_mtimes#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] = #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m.importer.run()#x1B[90m#x1B[39;49;00m
        album = #x1B[96mself#x1B[39;49;00m.lib.albums().get()#x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m album.added == #x1B[96mself#x1B[39;49;00m.min_mtime#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE       AttributeError: 'NoneType' object has no attribute 'added'#x1B[0m

#x1B[1m#x1B[31mtest/plugins/test_importadded.py#x1B[0m:69: AttributeError
test/plugins/test_importadded.py::ImportAddedTest::test_reimported_album_skipped
Stack Traces | 0.04s run time
self = <test.plugins.test_importadded.ImportAddedTest testMethod=test_reimported_album_skipped>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_reimported_album_skipped#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
        #x1B[90m# Import and record the original added dates#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m.importer.run()#x1B[90m#x1B[39;49;00m
        album = #x1B[96mself#x1B[39;49;00m.lib.albums().get()#x1B[90m#x1B[39;49;00m
>       album_added_before = album.added#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE       AttributeError: 'NoneType' object has no attribute 'added'#x1B[0m

#x1B[1m#x1B[31mtest/plugins/test_importadded.py#x1B[0m:82: AttributeError
test/plugins/test_importadded.py::ImportAddedTest::test_import_album_inplace_with_added_dates
Stack Traces | 0.042s run time
self = <test.plugins.test_importadded.ImportAddedTest testMethod=test_import_album_inplace_with_added_dates>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_import_album_inplace_with_added_dates#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m.config[#x1B[33m"#x1B[39;49;00m#x1B[33mimport#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m][#x1B[33m"#x1B[39;49;00m#x1B[33mcopy#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m] = #x1B[94mFalse#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m.importer.run()#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        album = #x1B[96mself#x1B[39;49;00m.lib.albums().get()#x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m album.added == #x1B[96mself#x1B[39;49;00m.min_mtime#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE       AttributeError: 'NoneType' object has no attribute 'added'#x1B[0m

#x1B[1m#x1B[31mtest/plugins/test_importadded.py#x1B[0m:61: AttributeError
test/dbcore/test_query.py::TestPathQuery::test_relative
Stack Traces | 0.101s run time
self = <test.dbcore.test_query.TestPathQuery object at 0x7f0ebbf25f90>
lib = <beets.library.library.Library object at 0x7f0ebe6fa2c0>
helper = <beets.test.helper.TestHelper object at 0x7f0eaa4971f0>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_relative#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, lib, helper):#x1B[90m#x1B[39;49;00m
        item_path = helper.lib_path / #x1B[33m"#x1B[39;49;00m#x1B[33mrelative#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m / #x1B[33m"#x1B[39;49;00m#x1B[33mitem.mp3#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        bytes_path = os.fsencode(item_path)#x1B[90m#x1B[39;49;00m
        helper.add_item(path=bytes_path, title=#x1B[33m"#x1B[39;49;00m#x1B[33mrelative item#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        q = #x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mpath:relative/item.mp3#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m {i.title #x1B[94mfor#x1B[39;49;00m i #x1B[95min#x1B[39;49;00m lib.items(q)} == {#x1B[33m"#x1B[39;49;00m#x1B[33mrelative item#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m}#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE       AssertionError: assert {'absolute it...ckslash', ...} == {'relative item'}#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m
#x1B[1m#x1B[31mE         Extra items in the left set:#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mcaps path#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith underscore#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mabsolute item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33manother item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith backslash#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mpath item#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mwith percent#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m
#x1B[1m#x1B[31mE         Full diff:#x1B[0m
#x1B[1m#x1B[31mE         #x1B[0m#x1B[90m #x1B[39;49;00m {#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'absolute item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'another item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'caps path',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'path item',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[90m #x1B[39;49;00m     'relative item',#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with backslash',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with percent',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[92m+     'with underscore',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m#x1B[0m
#x1B[1m#x1B[31mE         #x1B[90m #x1B[39;49;00m }#x1B[90m#x1B[39;49;00m#x1B[0m

#x1B[1m#x1B[31mtest/dbcore/test_query.py#x1B[0m:380: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

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 plugin event system more typed. grug like clearer contracts, less guess. but grug also see one real runtime footgun now.

Changes:

  • add explicit typing for plugin event names + per-event args/return types in beets.plugins (register_listener, send)
  • update many plugins to match typed listener signatures (args + return types) and add TYPE_CHECKING imports
  • small correctness cleanups found by typing pass (path/query typing, config parsing, handler return shapes)

Reviewed changes

Copilot reviewed 41 out of 41 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
beetsplug/zero.py add future annotations + type listener args for importer/write hooks
beetsplug/titlecase.py make handler return type explicit
beetsplug/tidal/init.py type import_begin auth hook to take session
beetsplug/thumbnails.py add future annotations + type album processing hooks
beetsplug/subsonicupdate.py type db/exit hooks and expected args
beetsplug/sonosupdate.py type db/exit hooks and expected args
beetsplug/smartplaylist.py tighten model type to shared LibModel for db-change/matching
beetsplug/scrub.py add future annotations + type importer hook args
beetsplug/replaygain.py add missing -> None annotations, type import_end args
beetsplug/plexupdate.py type db/exit hooks and expected args
beetsplug/playlist.py type config parsing and listener args; refine relative_to handling
beetsplug/play.py add future annotations + type importer choice hook return
beetsplug/permissions.py split item vs album handlers; type listener args
beetsplug/mpdupdate.py add future annotations + type db/exit hooks; annotate helpers
beetsplug/mbsubmit.py type importer hook return; normalize to always return list
beetsplug/mbpseudo.py add explicit -> None where needed
beetsplug/loadext.py type library_opened; iterate config as sequence explicitly
beetsplug/kodiupdate.py type db/exit hooks and expected args
beetsplug/importsource.py fix path query bytes + use public item.db access
beetsplug/importfeeds.py add future annotations + type importer/library hooks
beetsplug/importadded.py type import hooks + switch some handlers to list returns (introduces bug)
beetsplug/ihate.py add future annotations + type importer hook args
beetsplug/hook.py type nested hook function return
beetsplug/fromfilename.py type importer hook args; use task.items list directly
beetsplug/filefilter.py type import_task_created handler to return task list
beetsplug/fetchart.py add missing -> None annotations; typed config template usage
beetsplug/embyupdate.py add future annotations + type db/exit hooks
beetsplug/edit.py type importer choice hook return
beetsplug/chroma.py type importer hook args; use task.items list directly
beetsplug/beatport.py add missing -> None annotations
beetsplug/badfiles.py add config default + type importer hooks and action types
beetsplug/advancedrewrite.py refactor rule parsing into helpers + typed config shapes
beetsplug/acousticbrainz.py add future annotations + type importer hook args
beets/util/color.py widen colorize input to StrPath and normalize via os.fspath
beets/ui/commands/import_/session.py rely on typed send return for import_task_before_choice
beets/plugins.py define typed event unions + typed event arg TypedDicts; overload listener/send APIs
beets/dbcore/queryparse.py tighten query type params/return for query construction
beets/dbcore/query.py simplify relative path join under bytes-only music_dir context
Comments suppressed due to low confidence (1)

beetsplug/importadded.py:85

  • grug see same drop-task bug. This import_task_created listener always return [] at end, so it tells importer "no tasks" and importer drop work. Should return None when only side effects.
            for item in items:
                self.record_import_mtime(item, item.path, item.path)

        return []

Comment thread beetsplug/importadded.py
Comment on lines +51 to +55
def check_config(
self, task: ImportTask, session: ImportSession
) -> list[ImportTask]:
self.config["preserve_mtimes"].get(bool)
return []
Comment thread beets/plugins.py
Comment on lines +467 to +472
@overload
def register_listener(
self,
event: ImportTaskCreatedEventType,
func: Callable[[Unpack[ImportTaskEventArgs]], list[ImportTask]],
) -> None: ...
@snejus
snejus force-pushed the simplify-util branch 3 times, most recently from 9d08f90 to da5f7dd Compare July 29, 2026 23:53
@snejus
snejus force-pushed the type-handlers-and-listeners-explicitly branch 3 times, most recently from f2f60d0 to 662fd13 Compare July 30, 2026 02:08
@snejus
snejus force-pushed the type-handlers-and-listeners-explicitly branch from 662fd13 to bbb95df Compare July 30, 2026 02:13
@snejus
snejus force-pushed the type-handlers-and-listeners-explicitly branch from bbb95df to 4ee7cca Compare July 30, 2026 07:15
@snejus
snejus force-pushed the simplify-util branch 2 times, most recently from 527d072 to eab29f5 Compare July 30, 2026 11:21
@snejus
snejus force-pushed the type-handlers-and-listeners-explicitly branch from 4ee7cca to c61e61d Compare July 30, 2026 11:21
Base automatically changed from simplify-util to migrate-tests-to-pathlib July 30, 2026 11:22
Base automatically changed from migrate-tests-to-pathlib to master July 30, 2026 11:29
@snejus
snejus force-pushed the type-handlers-and-listeners-explicitly branch from c61e61d to 0f25d52 Compare July 30, 2026 12:17
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