dataclass refactor anlz
This commit is contained in:
1 parent
5810a536bd
commit
d5ef56c3ed
7 files changed
+393
-232
No files matched your search
@@ -0,0 +1,55 @@
|
||||
# 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 is integral:
|
||||
`_mixxx_analyze` raises rather than returning None, 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`, not `str`**, 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""` — per `Track`.
|
||||
`None` is 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`).
|
||||
Reference in new issue
Block a user