Skip to content

Commit e3e8793

Browse files
authored
Centralise field type defs (#6550)
### Summary This PR eliminates duplicated field-to-type mappings across `Album` and `Item` model classes. Previously, each model class maintained its own `_fields: dict[str, types.Type]` — a verbose, hand-written dictionary that encoded both _which_ fields exist and _what type_ each one has. This duplication made it easy for the two definitions to drift out of sync. --- ### Architecture Change **Before**: Each model class owned a full inline `_fields` dict mapping field names to type instances. ``` Album._fields = {"id": types.PRIMARY_ID, "artpath": types.NullPathType(), "added": types.DATE, ...} # ~40 entries Item._fields = {"id": types.PRIMARY_ID, "path": types.PathType(), "album_id": types.FOREIGN_ID, ...} # ~80 entries ``` **After**: Field names are declared as a `set[str]`, and the type mapping is derived lazily from a single centralised `TYPE_BY_FIELD` registry in `beets.dbcore.fields`. ``` Album._field_names = {"id", "artpath", "added", ...} # plain set of names Item._field_names = Album._field_names - {"artpath"} | {...} # extends Album's set LibModel._fields (cached_classproperty) = {f: TYPE_BY_FIELD[f] for f in cls._field_names} ``` This creates a clean separation of concerns: - **What fields belong to a model** → declared in the model (`_field_names`) - **What type a field has** → declared once in `dbcore.fields.TYPE_BY_FIELD` --- ### Key Impacts | Area | Before | After | |---|---|---| | Type definitions | Inline per model | Single registry in `dbcore.fields` | | `Item._fields` | Fully independent dict | Derived from `Album._field_names` | | `Album.item_keys` | Hard-coded list of 40+ strings | `_field_names - {"artpath", "id"}` | | `_media_fields` / `_media_tag_fields` | Intersected with `_fields.keys()` | Intersected with `_field_names` directly | | Coverage measurement | `--cov=beets --cov=beetsplug` | `--cov=.` (fixes missing `beetsplug/musicbrainz.py`) | --- ### Risk Areas to Review - **`TYPE_BY_FIELD` completeness**: all field names in `_field_names` must have a corresponding entry in the registry — a missing key will raise at class definition time via `cached_classproperty`. - **`Album.item_keys` semantics**: changed from `list` to `set` and is now derived automatically. Any code that relied on ordering or specific list membership should be checked. - **`Item._field_names` inheritance expression**: `Album._field_names - {"artpath"} | {...}` — operator precedence here is `(Album._field_names - {"artpath"}) | {...}`, which is the intended behaviour, but worth a close read.
2 parents 82384da + e2e8e80 commit e3e8793

File tree

4 files changed

+215
-194
lines changed

4 files changed

+215
-194
lines changed

beets/library/fields.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from beets.dbcore import types
2+
3+
TYPE_BY_FIELD: dict[str, types.Type] = {
4+
"acoustid_fingerprint": types.STRING,
5+
"acoustid_id": types.STRING,
6+
"added": types.DATE,
7+
"albumartist_credit": types.STRING,
8+
"albumartists_credit": types.MULTI_VALUE_DSV,
9+
"albumartist_sort": types.STRING,
10+
"albumartists_sort": types.MULTI_VALUE_DSV,
11+
"albumartists": types.MULTI_VALUE_DSV,
12+
"albumartist": types.STRING,
13+
"albumdisambig": types.STRING,
14+
"album_id": types.FOREIGN_ID,
15+
"albumstatus": types.STRING,
16+
"album": types.STRING,
17+
"albumtypes": types.SEMICOLON_SPACE_DSV,
18+
"albumtype": types.STRING,
19+
"arrangers": types.MULTI_VALUE_DSV,
20+
"arrangers_ids": types.MULTI_VALUE_DSV,
21+
"artist_credit": types.STRING,
22+
"artists_credit": types.MULTI_VALUE_DSV,
23+
"artists_ids": types.MULTI_VALUE_DSV,
24+
"artist_sort": types.STRING,
25+
"artists_sort": types.MULTI_VALUE_DSV,
26+
"artists": types.MULTI_VALUE_DSV,
27+
"artist": types.STRING,
28+
"artpath": types.NullPathType(),
29+
"asin": types.STRING,
30+
"barcode": types.STRING,
31+
"bitdepth": types.INTEGER,
32+
"bitrate_mode": types.STRING,
33+
"bitrate": types.ScaledInt(1000, "kbps"),
34+
"bpm": types.INTEGER,
35+
"catalognum": types.STRING,
36+
"channels": types.INTEGER,
37+
"comments": types.STRING,
38+
"composer_sort": types.STRING,
39+
"composers": types.MULTI_VALUE_DSV,
40+
"composers_ids": types.MULTI_VALUE_DSV,
41+
"comp": types.BOOLEAN,
42+
"country": types.STRING,
43+
"data_source": types.STRING,
44+
"day": types.PaddedInt(2),
45+
"discogs_albumid": types.INTEGER,
46+
"discogs_artistid": types.INTEGER,
47+
"discogs_labelid": types.INTEGER,
48+
"disctitle": types.STRING,
49+
"disctotal": types.PaddedInt(2),
50+
"disc": types.PaddedInt(2),
51+
"encoder_info": types.STRING,
52+
"encoder_settings": types.STRING,
53+
"encoder": types.STRING,
54+
"format": types.STRING,
55+
"genres": types.MULTI_VALUE_DSV,
56+
"grouping": types.STRING,
57+
"id": types.PRIMARY_ID,
58+
"initial_key": types.MusicalKey(),
59+
"isrc": types.STRING,
60+
"label": types.STRING,
61+
"language": types.STRING,
62+
"length": types.DurationType(),
63+
"lyricists": types.MULTI_VALUE_DSV,
64+
"lyricists_ids": types.MULTI_VALUE_DSV,
65+
"lyrics": types.STRING,
66+
"mb_albumartistids": types.MULTI_VALUE_DSV,
67+
"mb_albumartistid": types.STRING,
68+
"mb_albumid": types.STRING,
69+
"mb_artistids": types.MULTI_VALUE_DSV,
70+
"mb_artistid": types.STRING,
71+
"mb_releasegroupid": types.STRING,
72+
"mb_releasetrackid": types.STRING,
73+
"mb_trackid": types.STRING,
74+
"mb_workid": types.STRING,
75+
"media": types.STRING,
76+
"month": types.PaddedInt(2),
77+
"mtime": types.DATE,
78+
"original_day": types.PaddedInt(2),
79+
"original_month": types.PaddedInt(2),
80+
"original_year": types.PaddedInt(4),
81+
"path": types.PathType(),
82+
"r128_album_gain": types.NULL_FLOAT,
83+
"r128_track_gain": types.NULL_FLOAT,
84+
"releasegroupdisambig": types.STRING,
85+
"release_group_title": types.STRING,
86+
"remixers": types.MULTI_VALUE_DSV,
87+
"remixers_ids": types.MULTI_VALUE_DSV,
88+
"rg_album_gain": types.NULL_FLOAT,
89+
"rg_album_peak": types.NULL_FLOAT,
90+
"rg_track_gain": types.NULL_FLOAT,
91+
"rg_track_peak": types.NULL_FLOAT,
92+
"samplerate": types.ScaledInt(1000, "kHz"),
93+
"script": types.STRING,
94+
"style": types.STRING,
95+
"title": types.STRING,
96+
"trackdisambig": types.STRING,
97+
"tracktotal": types.PaddedInt(2),
98+
"track": types.PaddedInt(2),
99+
"work_disambig": types.STRING,
100+
"work": types.STRING,
101+
"year": types.PaddedInt(4),
102+
}

0 commit comments

Comments
 (0)