2.9 KiB
maple-py conventions
Python conventions for the maple-py/ package. These OVERRIDE default behavior;
follow them exactly. (General repo guidance is in the root CLAUDE.md; 4-space
indentation applies here too.)
The package is pyright-checked (basic mode, Python 3.14). Track
(src/maple/track.py) and the Analysis family (src/maple/analyze.py) are the
reference examples for all of the below.
Imports at the top of the file
All imports go at module top. No lazy/local imports inside functions. Never wrap
an import in try/except ImportError to degrade gracefully — if an integral
dependency (e.g. mixxx_analyzer, scipy) is missing, the process must die at
startup via the top-level import failing. The only acceptable indented import
is an if TYPE_CHECKING: block to break a circular type-only reference.
Fail loud — never swallow into None
"Don't swallow into None" is a general principle at every level, including call
sites. An operation that fails must let the exception propagate — never catch
it and return None / an empty / degraded result. BPM/beat analysis and waveform
extraction are integral: _mixxx_analyze raises rather than returning None,
_decode_pcm raises (RuntimeError) if ffmpeg is missing or fails to decode
rather than returning an empty array, and every Analysis.analyze call site (in
anlz.py, cli.py, collection/core.py) lets failures abort the run (including
not catching around ThreadPoolExecutor fut.result()).
Reserve None returns for legitimate absence (e.g. a cache miss in
Analysis.load), never for failure. The one tolerated catch is a genuinely
optional side-effect failing (e.g. writing the optimization cache via
analysis.save()), which logs and continues because it doesn't affect
correctness.
Specific types over generic ones
- Dataclasses, not bare dicts, for structured values passed around.
pathlib.Path, notstr, for local filesystem paths (strings are fine for serialization / rekordbox-USB paths that are inherently string data).typing.NamedTuple(or a small dataclass), not bare tuples, for fixed records (e.g.Beat(number, tempo_x100, time_ms),Bands(low, mid, high)).- No
None-valued fields. Absent values resolve at instantiation to a concrete sentinel —0.0(0.0 = unknown),"",[],b""— perTrack.Noneis acceptable only as a factory return signalling absence, never as a stored field.
Idiomatic dataclass shape: plain @dataclass (no frozen/slots), @property for
computed views, @classmethod from_*() factories, concrete field defaults.
Type every function parameter
Always annotate every parameter, including nested/inner functions and
@classmethod factory args (self/cls exempt). No bare def f(res):. Use the
real type even from a third-party lib — e.g. mixxx_analyzer.AnalysisResult
(referenced directly; no extra import needed under
from __future__ import annotations).