Files
musicmouse/python-backend/musicmouse/library/sections.py
Martin Bauer a7fb56c9fe Target the Python that Raspberry Pi OS ships
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>
2026-09-19 21:20:59 +02:00

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"),
}