Centralise plugin loading in `beets.plugins` and refactor the plugin
loading system to be more straightforward and eliminate complex mocking
in tests. Replace the two-stage class collection and instantiation
process with direct instance creation and storage.
Add plugins.PluginImportError and adjust plugin import tests to only
complain about plugin import issues.
Add `py.typed` marker file to support PEP 561 typing
This PR adds a `py.typed` marker file to the package directory to
indicate that the package includes inline type hints and is PEP 561
compliant.
Before:
```
Traceback (most recent call last):
File "/nix/store/yk2m7a9wdmh8fz8ywca0c73sc5ad2zm6-beets-2.3.1/lib/python3.13/site-packages/beetsplug/discogs.py", line 312, in get_album_info
result.refresh()
~~~~~~~~~~~~~~^^
File "/nix/store/007cfg4f295dz064bl9a2cjw10vlpc83-python3.13-discogs-client-2.8/lib/python3.13/site-packages/discogs_client/models.py", line 204, in refresh
data = self.client._get(self.data['resource_url'])
File "/nix/store/007cfg4f295dz064bl9a2cjw10vlpc83-python3.13-discogs-client-2.8/lib/python3.13/site-packages/discogs_client/client.py", line 114, in _get
return self._request('GET', url)
~~~~~~~~~~~~~^^^^^^^^^^^^
File "/nix/store/007cfg4f295dz064bl9a2cjw10vlpc83-python3.13-discogs-client-2.8/lib/python3.13/site-packages/discogs_client/client.py", line 111, in _request
raise HTTPError(body['message'], status_code)
discogs_client.exceptions.HTTPError: 404: That release does not exist or may have been deleted.
```
After:
```
Discogs release not found: <Release 20919814 'Kumi Tanioka, Yae - Final Fantasy Crystal Chronicles Original Soundtrack'>: 404: That release does not exist or may have been deleted.
```
Here's the link to the relevant release, which [shows up in search
results](https://www.discogs.com/search?q=Kumi+Tanioka%2C+Yae+-+Final+Fantasy+Crystal+Chronicles+Original+Soundtrack&type=all)
but 404s if you click on it:
https://www.discogs.com/release/20919814-Kumi-Tanioka-Yae-Final-Fantasy-Crystal-Chronicles-Original-Soundtrack
Add the artist and album information to the item template in the web
plugin.
When searching for an item, Instead of
`` Doo Woop (That Thing)``
the interface now shows
``Lauryn Hill - The Miseducation of Lauryn Hill - Doo Wop (That Thing)``
which is arguably clearer, and matches the default output of `beet ls`
in the cli.
## Summary
See https://github.com/beetbox/beets/discussions/5891 for context.
This PR introduces a deprecation system for module structure changes and
deprecated functionality in the beets codebase:
### Key Changes
**New deprecation infrastructure:**
- Add `deprecate_imports()` utility function to handle deprecated module
imports with warnings
- Implement `__getattr__` hooks in modules to intercept and redirect
deprecated imports
- Deprecate previously removed imports in `beets.library` and
`beets.autotag`
- Deprecate `decargs` function.
## Fix dynamic plugin type and query registration
This PR refactors the plugin system to properly handle dynamic type and
query registration by converting static class attributes to cached class
properties.
**Problem**: Plugin types and queries were stored as mutable class
attributes that were manually updated during plugin loading/unloading.
This caused issues where:
- Plugin types weren't properly registered in test environments
- Shared mutable state between test runs caused inconsistent behavior
- Manual cleanup was error-prone and incomplete
See https://github.com/beetbox/beets/pull/5833 and specifically
https://github.com/beetbox/beets/pull/5833#issuecomment-3016635209 for
the context.
**Solution**:
- Convert `_types` and `_queries` from static dictionaries to
`@cached_classproperty` decorators
- Types and queries are now dynamically computed from loaded plugins
when accessed
- Eliminates manual mutation of class attributes during plugin loading
- Properly clears cached properties when plugins are loaded/unloaded
- Ensures plugin types are available immediately after registration
**Key Changes**:
- `Model._types` and `LibModel._queries` now use `@cached_classproperty`
- Removed manual `_types.update()` calls in plugin loading code
- Added proper cache clearing in test infrastructure
- Plugin types are now inherited through the class hierarchy correctly
This fixes the developer's issue where `item_types` weren't being
registered properly in tests - the new dynamic property approach ensures
plugin types are available as soon as plugins are loaded.
Convert _queries from mutable class attributes to cached class properties
that dynamically fetch plugin queries. This eliminates the need for manual
query registration and cleanup in plugin loading/unloading logic.
Convert static _types dictionaries to dynamic cached class properties to
enable proper plugin type inheritance and avoid mutating shared state.
Key changes:
- Replace static _types dicts with @cached_classproperty decorators
- Update cached_classproperty to support proper caching with class names
- Remove manual _types mutation in plugin loading/unloading
- Add pluginload event and cache clearing for proper plugin integration
- Fix test to trigger type checking during item creation
This ensures plugin types are properly inherited through the class
hierarchy and eliminates issues with shared mutable state between
test runs.
**Bug Fixes**:
- Wrong `raw_seconds_short` module in `DurationQuery`.
- Missing `byte_paths` variable in `import_func`.
Otherwise
- Addressed all static type checking errors in the codebase (except
`spotify` plugin which has already been fixed in a separate PR)
- Removed `continue-on-error` from in `mypy` CI job, which means any
errors in type checking will now fail the CI job.