Requiring 3.13 meant the device needed an interpreter the distribution does not
have, which is what dragged uv in, and uv then had to be matched to the Pi's
32-bit userland by hand and to build Pillow from source because no armv7 wheel
exists for a 3.13 ABI. Dropping to 3.11 removes all of that: apt provides the
interpreter and piwheels has prebuilt armhf wheels for the native dependencies.
The 3.13-only syntax was shallow - PEP 695 throughout, which converts back
mechanically:
type X = Y -> X: TypeAlias = Y
type Handler[E: Event] -> E = TypeVar("E", bound=Event) plus a plain alias,
which is generic anyway because it carries a TypeVar
def f[T: Bound](...) -> a module-level TypeVar
Also drop the one @override (3.12, and static-only), and stop the lirc test
harness calling Server.close_clients(), which is 3.13: the scripted handler now
releases its connection when asked, which is what that call was there to force.
Verified on 3.11.14 - 485 passed, mypy strict clean - and still 496 passed on
the 3.14 dev venv, which additionally has the analysis extra.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
53 lines
2.4 KiB
Python
53 lines
2.4 KiB
Python
"""The four shelves of the music library, and how each one differs.
|
|
|
|
These are folder names, not configuration. The library has one root and the layout
|
|
underneath it is fixed - a section that needed configuring would be a section whose
|
|
quirks are not understood yet.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Final, Literal, TypeAlias
|
|
|
|
__all__ = ["SECTIONS", "AlbumKind", "ArtistSource", "Section", "TitleSource", "TrackOrder"]
|
|
|
|
AlbumKind: TypeAlias = Literal["music", "book"]
|
|
TrackOrder: TypeAlias = Literal["filename", "newest_first"]
|
|
TitleSource: TypeAlias = Literal["tags", "folder"]
|
|
ArtistSource: TypeAlias = Literal["tags", "folder"]
|
|
AlbumUnit: TypeAlias = Literal["folder", "episode"]
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class Section:
|
|
kind: AlbumKind = "music"
|
|
#: Subfolders are figure folders: their name is the figure name from the config.
|
|
figures: bool = False
|
|
order: TrackOrder = "filename"
|
|
title_from: TitleSource = "tags"
|
|
artist_from: ArtistSource = "tags"
|
|
#: "folder" (default): one album per folder, every audio file a track/chapter of it.
|
|
#: "episode": one album per audio *file* - a show folder groups its episodes rather
|
|
#: than being one giant album itself. `title_from`/`artist_from` are not consulted
|
|
#: for this unit: an episode's title always comes from its own tags (or filename),
|
|
#: and the folder always supplies the artist/series, so episodes of the same show
|
|
#: still group together everywhere the browse view groups by category.
|
|
album_unit: AlbumUnit = "folder"
|
|
|
|
|
|
#: ``Kinderpodcasts`` is the odd one out twice over. Its ``artist`` tag is the full
|
|
#: presenter list ("Thomas Welling, Sarah Schultes, ...") and its ``album`` tag is the
|
|
#: feed name, so neither groups usefully - the folder name is the show. And its files
|
|
#: are named ``YYYYMMDD - Title.mp3``, so reversing filename order puts the newest
|
|
#: episode first, which is the one anybody wants.
|
|
SECTIONS: Final[dict[str, Section]] = {
|
|
# A figure folder is named after the figure, and its contents are whatever that
|
|
# figure should play - often several albums' worth. The folder name is the honest
|
|
# title; the tags still supply a useful artist.
|
|
"Figuren": Section(figures=True, title_from="folder"),
|
|
"Musik": Section(),
|
|
"Hörbücher": Section(kind="book"),
|
|
"Kinderpodcasts": Section(kind="book", order="newest_first", album_unit="episode"),
|
|
}
|