lexicon-python 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lexicon_python-0.3.0/src/lexicon_python.egg-info → lexicon_python-0.4.0}/PKG-INFO +108 -49
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/README.md +107 -48
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/pyproject.toml +1 -1
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/__init__.py +13 -1
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/client.py +36 -0
- lexicon_python-0.4.0/src/lexicon/colors.py +29 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/tracks.py +226 -9
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/tracks_types.py +60 -1
- lexicon_python-0.4.0/src/lexicon/tools/tempo.py +74 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0/src/lexicon_python.egg-info}/PKG-INFO +108 -49
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon_python.egg-info/SOURCES.txt +5 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/tests/test_client.py +58 -15
- lexicon_python-0.4.0/tests/test_colors.py +34 -0
- lexicon_python-0.4.0/tests/test_integration_lifecycle.py +366 -0
- lexicon_python-0.4.0/tests/test_tools_tempo.py +79 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/tests/test_tracks.py +263 -1
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/LICENSE +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/setup.cfg +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/__init__.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/_common_types.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/base.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/playlist_tracks.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/playlist_tracks_types.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/playlists.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/playlists_types.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/tag_categories.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/tag_categories_types.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/tags.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/resources/tags_types.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/tools/__init__.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/tools/playlists.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/tools/tracks.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon/utils.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon_python.egg-info/dependency_links.txt +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon_python.egg-info/requires.txt +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/src/lexicon_python.egg-info/top_level.txt +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/tests/test_base.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/tests/test_common_types.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/tests/test_playlist_tracks.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/tests/test_playlists.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/tests/test_playlists_types.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/tests/test_tag_categories.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/tests/test_tags.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/tests/test_tools_playlists.py +0 -0
- {lexicon_python-0.3.0 → lexicon_python-0.4.0}/tests/test_tools_tracks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lexicon-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python client for the Lexicon DJ API
|
|
5
5
|
Author-email: Garrison Burger <burgerga123@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -17,9 +17,9 @@ Dynamic: license-file
|
|
|
17
17
|
|
|
18
18
|
Python client for the Lexicon DJ Local API.
|
|
19
19
|
|
|
20
|
-
This SDK wraps the Lexicon Local API with resource groups, sensible defaults, and
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
This SDK wraps the Lexicon Local API with resource groups, sensible defaults, and optional validation. It is designed
|
|
21
|
+
for scripting library automation, playlist management, and metadata edits while keeping a clean escape hatch to the raw
|
|
22
|
+
API.
|
|
23
23
|
|
|
24
24
|
## Features
|
|
25
25
|
|
|
@@ -46,17 +46,22 @@ pip install lexicon-python
|
|
|
46
46
|
```python
|
|
47
47
|
from lexicon import Lexicon
|
|
48
48
|
|
|
49
|
-
lex = Lexicon()
|
|
49
|
+
lex = Lexicon() # verifies the API is reachable; raises LexiconConnectionError otherwise
|
|
50
|
+
playlist = lex.playlists.choose()
|
|
50
51
|
|
|
51
|
-
# list tracks (default fields)
|
|
52
|
+
# list tracks (default fields: id, artist, title, bpm, key, ...)
|
|
52
53
|
tracks = lex.tracks.list(limit=10) or []
|
|
53
54
|
for t in tracks:
|
|
54
55
|
print(t.get("artist"), "-", t.get("title"))
|
|
55
56
|
|
|
56
|
-
# search
|
|
57
|
+
# search — pass fields=[...] for a narrow projection, or fields="all" for the
|
|
58
|
+
# full payload (tempomarkers, cuepoints, tags, etc.)
|
|
57
59
|
results = lex.tracks.search({"artist": "Daft Punk"}) or []
|
|
58
60
|
print("matches:", len(results))
|
|
59
61
|
|
|
62
|
+
# get a single track by id — always returns the full record
|
|
63
|
+
full = lex.tracks.get(123)
|
|
64
|
+
|
|
60
65
|
# get a playlist by path
|
|
61
66
|
playlist = lex.playlists.get_by_path(["Genres", "Drum & Bass"], playlist_type="folder")
|
|
62
67
|
print(playlist)
|
|
@@ -80,6 +85,8 @@ Other options:
|
|
|
80
85
|
- `default_timeout`: request timeout in seconds
|
|
81
86
|
- `session`: optional `requests.Session`
|
|
82
87
|
- `raise_on_error`: raise HTTP errors instead of returning None
|
|
88
|
+
- `verify_connection` (default `True`): probe the API on construction and raise `LexiconConnectionError` if Lexicon
|
|
89
|
+
isn't reachable. Set to `False` to build a client without a running Lexicon (e.g. for tests).
|
|
83
90
|
|
|
84
91
|
Environment variables:
|
|
85
92
|
|
|
@@ -92,8 +99,8 @@ export LEXICON_PORT=48624
|
|
|
92
99
|
|
|
93
100
|
Many methods accept a `validation` parameter with three modes:
|
|
94
101
|
|
|
95
|
-
- `"warn"` (default): invalid inputs are skipped with a warning. This avoids API
|
|
96
|
-
|
|
102
|
+
- `"warn"` (default): invalid inputs are skipped with a warning. This avoids API failures, but intended changes may be
|
|
103
|
+
ignored.
|
|
97
104
|
- `"strict"`: invalid inputs raise `ValueError`.
|
|
98
105
|
- `"off"`: skips normalization and sends inputs as-is (inputs must match API-native shapes)
|
|
99
106
|
|
|
@@ -107,9 +114,8 @@ lex.tracks.search({"rating": "bad"}, validation="off") # sends as-is
|
|
|
107
114
|
|
|
108
115
|
## Convenience vs Raw API
|
|
109
116
|
|
|
110
|
-
This SDK adds several quality-of-life behaviors on top of the raw Lexicon API.
|
|
111
|
-
|
|
112
|
-
single-item lists are collapsed to a single dict).
|
|
117
|
+
This SDK adds several quality-of-life behaviors on top of the raw Lexicon API. In general, response shapes are unwrapped
|
|
118
|
+
(e.g., `"data": {...}` is removed and single-item lists are collapsed to a single dict).
|
|
113
119
|
|
|
114
120
|
### Additional Methods (Not in the Raw API)
|
|
115
121
|
|
|
@@ -122,28 +128,25 @@ single-item lists are collapsed to a single dict).
|
|
|
122
128
|
|
|
123
129
|
### Input Normalization (Broader Accepted Inputs)
|
|
124
130
|
|
|
125
|
-
- **ID normalization**: Many methods accept lists of IDs instead of a single ID
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
- Fields used as a search filter or sort item are also returned.
|
|
131
|
+
- **ID normalization**: Many methods accept lists of IDs instead of a single ID for easier batch operations.
|
|
132
|
+
- **Field selection**: By default, `tracks.list()` and `tracks.search()` return a minimal set of fields rather than the
|
|
133
|
+
full payload:
|
|
134
|
+
- `id`, `artist`, `title`, `albumTitle`, `bpm`, `key`, `duration`, `year`
|
|
135
|
+
- Fields used as a search filter or sort item are also returned.
|
|
131
136
|
- **Search filter normalization**:
|
|
132
137
|
- Text fields accept `None` (becomes `"NONE"` in filter context).
|
|
133
138
|
- Numeric filters accept `None` (becomes `"0"`).
|
|
134
|
-
- Date filters accept `YYYY-MM-DD`, full datetime strings, or `datetime.date` /
|
|
135
|
-
|
|
136
|
-
|
|
139
|
+
- Date filters accept `YYYY-MM-DD`, full datetime strings, or `datetime.date` / `datetime.datetime` inputs (time is
|
|
140
|
+
stripped).
|
|
141
|
+
- Comparisons (`>YYYY-MM-DD`) are warned/blocked because the API currently ignores them.
|
|
137
142
|
- **Sort normalization**:
|
|
138
143
|
- Accepts tuple shorthand: `[("title", "asc")]`.
|
|
139
144
|
- **Track update helpers**:
|
|
140
|
-
- Cuepoint and tempomarker entries are normalized (e.g., cuepoint type accepts
|
|
141
|
-
name/number variants).
|
|
145
|
+
- Cuepoint and tempomarker entries are normalized (e.g., cuepoint type accepts name/number variants).
|
|
142
146
|
- Invalid entries can be skipped in `"warn"` mode without failing the update.
|
|
143
147
|
|
|
144
|
-
If no SDK normalization is desired, use `validation="off"` and pass
|
|
145
|
-
|
|
146
|
-
those modes simply add normalization/validation on top. For fully raw access,
|
|
148
|
+
If no SDK normalization is desired, use `validation="off"` and pass API-native payloads. API-native shapes are also
|
|
149
|
+
accepted in `"warn"`/`"strict"`; those modes simply add normalization/validation on top. For fully raw access,
|
|
147
150
|
`lex.request(...)` can always be called directly.
|
|
148
151
|
|
|
149
152
|
## Tracks
|
|
@@ -159,23 +162,60 @@ tracks = lex.tracks.search({"artist": "Daft Punk"})
|
|
|
159
162
|
added = lex.tracks.add(["/path/to/file1.mp3", "/path/to/file2.mp3"])
|
|
160
163
|
updated = lex.tracks.update(123, {"title": "New Title"})
|
|
161
164
|
lex.tracks.delete([123, 456])
|
|
165
|
+
|
|
166
|
+
# Replace tempomarkers while keeping cuepoints anchored to the same beats
|
|
167
|
+
lex.tracks.update_tempogrid(123, [{"startTime": 0.0, "bpm": 124.0}])
|
|
168
|
+
|
|
169
|
+
# Start Lexicon's analyzer on specific tracks
|
|
170
|
+
lex.tracks.analyze([123, 456], analyze_beatgrid=True, do_not_overwrite=False)
|
|
162
171
|
```
|
|
163
172
|
|
|
164
173
|
Notes:
|
|
165
174
|
|
|
166
175
|
- `tracks.search()` results are capped at 1000 by the API.
|
|
167
176
|
- `tracks.get_many()` preserves input order and returns `None` for missing IDs.
|
|
168
|
-
- `
|
|
169
|
-
|
|
170
|
-
- `fields=
|
|
171
|
-
- `
|
|
172
|
-
|
|
177
|
+
- `tracks.get()` always returns the full record (no `fields` parameter).
|
|
178
|
+
- For `tracks.list()` / `tracks.search()`:
|
|
179
|
+
- `fields=None` returns a minimal default set.
|
|
180
|
+
- In `validation="off"` mode, `fields=None` returns the full API payload.
|
|
181
|
+
- Pass a list of field names for a narrow projection (`fields=["id", "location"]`).
|
|
182
|
+
- `fields="all"` or `fields="*"` requests the full payload.
|
|
183
|
+
- Comma-separated strings (e.g. `"id,location"`) are _not_ accepted; pass a list. With `validation="warn"` the call
|
|
184
|
+
falls back to the default field set and logs.
|
|
185
|
+
- `tracks.add()` returns track dicts, but analysis fields (tempo markers, key, etc.) may be populated later by Lexicon.
|
|
186
|
+
- `tracks.update_tempogrid()` replaces tempomarkers while preserving each cuepoint's beat position by rewriting
|
|
187
|
+
`startTime`/`endTime` against the new grid. Use `tracks.update(track_id, {"tempomarkers": [...]})` directly if you
|
|
188
|
+
want the raw field replacement (cuepoints' seconds stay put, beats shift).
|
|
189
|
+
- `tracks.analyze()` returns as soon as Lexicon accepts the request. Analysis runs in the background and the API has no
|
|
190
|
+
completion signal, so poll `tracks.get()` and watch `tempomarkers`, `bpm`, `key`, or `dateModified`.
|
|
191
|
+
- `tracks.analyze(tempo_range=...)` takes one of the eight ids Lexicon's analyze dropdown stores: `"0_300"` (Any),
|
|
192
|
+
`"58_115"`, `"68_135"`, `"78_155"`, `"88_175"`, `"98_195"`, `"128_255"`, `"180_300"` (180+). An equivalent
|
|
193
|
+
`(min, max)` tuple works too. The value is written to Lexicon's stored analysis setting and persists after the call;
|
|
194
|
+
the boolean options apply to that run only.
|
|
195
|
+
|
|
196
|
+
### Track Payload Shapes
|
|
197
|
+
|
|
198
|
+
Rich-payload fields like `cuepoints` and `tempomarkers` aren't in the default field set; request them explicitly
|
|
199
|
+
(`fields=["cuepoints", "tempomarkers"]`) or ask for everything (`fields="all"`). Their shapes are typed via
|
|
200
|
+
`CuePointResponse` and `TempoMarkerResponse`, both re-exported from `lexicon`:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from lexicon import TrackResponse, TempoMarkerResponse, CuePointResponse
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Color values (cuepoint/tag/etc. `color` fields) are Lexicon-specific name strings declared by the `Color` literal type.
|
|
207
|
+
To convert a name to RGB:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from lexicon import color_rgb
|
|
211
|
+
|
|
212
|
+
color_rgb("red_dark") # → (158, 15, 7)
|
|
213
|
+
```
|
|
173
214
|
|
|
174
215
|
### Track Search, Filters, and Sort
|
|
175
216
|
|
|
176
|
-
`tracks.search(filter=...)` accepts a dict of field names and values. The SDK
|
|
177
|
-
|
|
178
|
-
in `"off"` mode.
|
|
217
|
+
`tracks.search(filter=...)` accepts a dict of field names and values. The SDK validates fields/values in
|
|
218
|
+
`"warn"`/`"strict"` modes and can send API-native values in `"off"` mode.
|
|
179
219
|
|
|
180
220
|
Examples:
|
|
181
221
|
|
|
@@ -233,6 +273,7 @@ Playlist type accepts:
|
|
|
233
273
|
- `1`, `2`, `3` (or string numerals `"1"`, `"2"`, `"3"`)
|
|
234
274
|
|
|
235
275
|
### Playlist tracks helpers
|
|
276
|
+
|
|
236
277
|
For getting the tracks of a playlist or editing the tracklist.
|
|
237
278
|
|
|
238
279
|
```python
|
|
@@ -286,6 +327,7 @@ print(path)
|
|
|
286
327
|
```
|
|
287
328
|
|
|
288
329
|
## Raw Requests (Escape Hatch)
|
|
330
|
+
|
|
289
331
|
The API can always be accessed directly with `lex.request`:
|
|
290
332
|
|
|
291
333
|
```python
|
|
@@ -304,8 +346,8 @@ High level namespaces (full mapping in `docs/resource-map.md`):
|
|
|
304
346
|
|
|
305
347
|
## Type Hints
|
|
306
348
|
|
|
307
|
-
The SDK includes TypedDict and Literal types for payloads and enums. These are
|
|
308
|
-
|
|
349
|
+
The SDK includes TypedDict and Literal types for payloads and enums. These are intended to improve editor autocomplete
|
|
350
|
+
and static checks.
|
|
309
351
|
|
|
310
352
|
For full payload schemas and endpoint details, refer to the Lexicon API docs:
|
|
311
353
|
|
|
@@ -316,10 +358,10 @@ For full payload schemas and endpoint details, refer to the Lexicon API docs:
|
|
|
316
358
|
|
|
317
359
|
### Setup
|
|
318
360
|
|
|
319
|
-
#### Pip
|
|
361
|
+
#### Pip
|
|
320
362
|
|
|
321
|
-
Ensure that you have Python 3.9+ installed locally.
|
|
322
|
-
|
|
363
|
+
Ensure that you have Python 3.9+ installed locally. To install all runtime and dev dependencies into a local virtual
|
|
364
|
+
environment using pip:
|
|
323
365
|
|
|
324
366
|
```bash
|
|
325
367
|
python -m venv .venv
|
|
@@ -329,7 +371,8 @@ pip install -e ".[dev]"
|
|
|
329
371
|
|
|
330
372
|
#### uv
|
|
331
373
|
|
|
332
|
-
To use [uv](https://docs.astral.sh/uv/) to install all runtime and dev dependencies into a local virtual environment,
|
|
374
|
+
To use [uv](https://docs.astral.sh/uv/) to install all runtime and dev dependencies into a local virtual environment,
|
|
375
|
+
simply install `uv` and run:
|
|
333
376
|
|
|
334
377
|
```bash
|
|
335
378
|
uv sync --dev
|
|
@@ -351,11 +394,10 @@ make run-integration-tests
|
|
|
351
394
|
|
|
352
395
|
### Linting and Formatting
|
|
353
396
|
|
|
354
|
-
The project uses [ruff](https://docs.astral.sh/ruff/) for both linting and
|
|
355
|
-
formatting.
|
|
397
|
+
The project uses [ruff](https://docs.astral.sh/ruff/) for both linting and formatting.
|
|
356
398
|
|
|
357
|
-
`make test` runs the full suite: format, lint (with auto-fix), then tests.
|
|
358
|
-
|
|
399
|
+
`make test` runs the full suite: format, lint (with auto-fix), then tests. `make fix` runs all auto-fixers (lint +
|
|
400
|
+
format) without running tests.
|
|
359
401
|
|
|
360
402
|
```bash
|
|
361
403
|
make test # format-fix → lint-fix → format-check → lint-check → tests
|
|
@@ -372,10 +414,28 @@ make format-check # check formatting
|
|
|
372
414
|
make format-fix # auto-fix formatting
|
|
373
415
|
```
|
|
374
416
|
|
|
417
|
+
### Pre-commit hook (recommended)
|
|
418
|
+
|
|
419
|
+
The repo ships a [pre-commit](https://pre-commit.com/) config (`.pre-commit-config.yaml`) that mirrors the CI lint job.
|
|
420
|
+
After cloning, install the hook once:
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
uv tool install pre-commit # or: pipx install pre-commit
|
|
424
|
+
pre-commit install
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
`git commit` now runs `ruff format` + `ruff check --fix` on staged files and blocks the commit on any failure. Run
|
|
428
|
+
against the whole repo any time with:
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
pre-commit run --all-files
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
The ruff version is pinned in `.pre-commit-config.yaml`; bump it when CI bumps to keep them aligned.
|
|
435
|
+
|
|
375
436
|
### CI
|
|
376
437
|
|
|
377
|
-
GitHub Actions runs on every push to `main` and on pull requests
|
|
378
|
-
targeting those branches. The pipeline includes:
|
|
438
|
+
GitHub Actions runs on every push to `main` and on pull requests targeting those branches. The pipeline includes:
|
|
379
439
|
|
|
380
440
|
- **Tests** across Python 3.9, 3.10, 3.11, and 3.12
|
|
381
441
|
- **Lint and format checks** via ruff on Python 3.12
|
|
@@ -384,9 +444,8 @@ Before opening a PR, make sure `make test` passes locally.
|
|
|
384
444
|
|
|
385
445
|
### Pull Requests
|
|
386
446
|
|
|
387
|
-
A PR template is provided at `.github/pull_request_template.md`. When opening a
|
|
388
|
-
|
|
389
|
-
testing/checklist items.
|
|
447
|
+
A PR template is provided at `.github/pull_request_template.md`. When opening a PR, fill in the description, check the
|
|
448
|
+
relevant change-type boxes, and confirm testing/checklist items.
|
|
390
449
|
|
|
391
450
|
## License
|
|
392
451
|
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Python client for the Lexicon DJ Local API.
|
|
4
4
|
|
|
5
|
-
This SDK wraps the Lexicon Local API with resource groups, sensible defaults, and
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
This SDK wraps the Lexicon Local API with resource groups, sensible defaults, and optional validation. It is designed
|
|
6
|
+
for scripting library automation, playlist management, and metadata edits while keeping a clean escape hatch to the raw
|
|
7
|
+
API.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
@@ -31,17 +31,22 @@ pip install lexicon-python
|
|
|
31
31
|
```python
|
|
32
32
|
from lexicon import Lexicon
|
|
33
33
|
|
|
34
|
-
lex = Lexicon()
|
|
34
|
+
lex = Lexicon() # verifies the API is reachable; raises LexiconConnectionError otherwise
|
|
35
|
+
playlist = lex.playlists.choose()
|
|
35
36
|
|
|
36
|
-
# list tracks (default fields)
|
|
37
|
+
# list tracks (default fields: id, artist, title, bpm, key, ...)
|
|
37
38
|
tracks = lex.tracks.list(limit=10) or []
|
|
38
39
|
for t in tracks:
|
|
39
40
|
print(t.get("artist"), "-", t.get("title"))
|
|
40
41
|
|
|
41
|
-
# search
|
|
42
|
+
# search — pass fields=[...] for a narrow projection, or fields="all" for the
|
|
43
|
+
# full payload (tempomarkers, cuepoints, tags, etc.)
|
|
42
44
|
results = lex.tracks.search({"artist": "Daft Punk"}) or []
|
|
43
45
|
print("matches:", len(results))
|
|
44
46
|
|
|
47
|
+
# get a single track by id — always returns the full record
|
|
48
|
+
full = lex.tracks.get(123)
|
|
49
|
+
|
|
45
50
|
# get a playlist by path
|
|
46
51
|
playlist = lex.playlists.get_by_path(["Genres", "Drum & Bass"], playlist_type="folder")
|
|
47
52
|
print(playlist)
|
|
@@ -65,6 +70,8 @@ Other options:
|
|
|
65
70
|
- `default_timeout`: request timeout in seconds
|
|
66
71
|
- `session`: optional `requests.Session`
|
|
67
72
|
- `raise_on_error`: raise HTTP errors instead of returning None
|
|
73
|
+
- `verify_connection` (default `True`): probe the API on construction and raise `LexiconConnectionError` if Lexicon
|
|
74
|
+
isn't reachable. Set to `False` to build a client without a running Lexicon (e.g. for tests).
|
|
68
75
|
|
|
69
76
|
Environment variables:
|
|
70
77
|
|
|
@@ -77,8 +84,8 @@ export LEXICON_PORT=48624
|
|
|
77
84
|
|
|
78
85
|
Many methods accept a `validation` parameter with three modes:
|
|
79
86
|
|
|
80
|
-
- `"warn"` (default): invalid inputs are skipped with a warning. This avoids API
|
|
81
|
-
|
|
87
|
+
- `"warn"` (default): invalid inputs are skipped with a warning. This avoids API failures, but intended changes may be
|
|
88
|
+
ignored.
|
|
82
89
|
- `"strict"`: invalid inputs raise `ValueError`.
|
|
83
90
|
- `"off"`: skips normalization and sends inputs as-is (inputs must match API-native shapes)
|
|
84
91
|
|
|
@@ -92,9 +99,8 @@ lex.tracks.search({"rating": "bad"}, validation="off") # sends as-is
|
|
|
92
99
|
|
|
93
100
|
## Convenience vs Raw API
|
|
94
101
|
|
|
95
|
-
This SDK adds several quality-of-life behaviors on top of the raw Lexicon API.
|
|
96
|
-
|
|
97
|
-
single-item lists are collapsed to a single dict).
|
|
102
|
+
This SDK adds several quality-of-life behaviors on top of the raw Lexicon API. In general, response shapes are unwrapped
|
|
103
|
+
(e.g., `"data": {...}` is removed and single-item lists are collapsed to a single dict).
|
|
98
104
|
|
|
99
105
|
### Additional Methods (Not in the Raw API)
|
|
100
106
|
|
|
@@ -107,28 +113,25 @@ single-item lists are collapsed to a single dict).
|
|
|
107
113
|
|
|
108
114
|
### Input Normalization (Broader Accepted Inputs)
|
|
109
115
|
|
|
110
|
-
- **ID normalization**: Many methods accept lists of IDs instead of a single ID
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
- Fields used as a search filter or sort item are also returned.
|
|
116
|
+
- **ID normalization**: Many methods accept lists of IDs instead of a single ID for easier batch operations.
|
|
117
|
+
- **Field selection**: By default, `tracks.list()` and `tracks.search()` return a minimal set of fields rather than the
|
|
118
|
+
full payload:
|
|
119
|
+
- `id`, `artist`, `title`, `albumTitle`, `bpm`, `key`, `duration`, `year`
|
|
120
|
+
- Fields used as a search filter or sort item are also returned.
|
|
116
121
|
- **Search filter normalization**:
|
|
117
122
|
- Text fields accept `None` (becomes `"NONE"` in filter context).
|
|
118
123
|
- Numeric filters accept `None` (becomes `"0"`).
|
|
119
|
-
- Date filters accept `YYYY-MM-DD`, full datetime strings, or `datetime.date` /
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
- Date filters accept `YYYY-MM-DD`, full datetime strings, or `datetime.date` / `datetime.datetime` inputs (time is
|
|
125
|
+
stripped).
|
|
126
|
+
- Comparisons (`>YYYY-MM-DD`) are warned/blocked because the API currently ignores them.
|
|
122
127
|
- **Sort normalization**:
|
|
123
128
|
- Accepts tuple shorthand: `[("title", "asc")]`.
|
|
124
129
|
- **Track update helpers**:
|
|
125
|
-
- Cuepoint and tempomarker entries are normalized (e.g., cuepoint type accepts
|
|
126
|
-
name/number variants).
|
|
130
|
+
- Cuepoint and tempomarker entries are normalized (e.g., cuepoint type accepts name/number variants).
|
|
127
131
|
- Invalid entries can be skipped in `"warn"` mode without failing the update.
|
|
128
132
|
|
|
129
|
-
If no SDK normalization is desired, use `validation="off"` and pass
|
|
130
|
-
|
|
131
|
-
those modes simply add normalization/validation on top. For fully raw access,
|
|
133
|
+
If no SDK normalization is desired, use `validation="off"` and pass API-native payloads. API-native shapes are also
|
|
134
|
+
accepted in `"warn"`/`"strict"`; those modes simply add normalization/validation on top. For fully raw access,
|
|
132
135
|
`lex.request(...)` can always be called directly.
|
|
133
136
|
|
|
134
137
|
## Tracks
|
|
@@ -144,23 +147,60 @@ tracks = lex.tracks.search({"artist": "Daft Punk"})
|
|
|
144
147
|
added = lex.tracks.add(["/path/to/file1.mp3", "/path/to/file2.mp3"])
|
|
145
148
|
updated = lex.tracks.update(123, {"title": "New Title"})
|
|
146
149
|
lex.tracks.delete([123, 456])
|
|
150
|
+
|
|
151
|
+
# Replace tempomarkers while keeping cuepoints anchored to the same beats
|
|
152
|
+
lex.tracks.update_tempogrid(123, [{"startTime": 0.0, "bpm": 124.0}])
|
|
153
|
+
|
|
154
|
+
# Start Lexicon's analyzer on specific tracks
|
|
155
|
+
lex.tracks.analyze([123, 456], analyze_beatgrid=True, do_not_overwrite=False)
|
|
147
156
|
```
|
|
148
157
|
|
|
149
158
|
Notes:
|
|
150
159
|
|
|
151
160
|
- `tracks.search()` results are capped at 1000 by the API.
|
|
152
161
|
- `tracks.get_many()` preserves input order and returns `None` for missing IDs.
|
|
153
|
-
- `
|
|
154
|
-
|
|
155
|
-
- `fields=
|
|
156
|
-
- `
|
|
157
|
-
|
|
162
|
+
- `tracks.get()` always returns the full record (no `fields` parameter).
|
|
163
|
+
- For `tracks.list()` / `tracks.search()`:
|
|
164
|
+
- `fields=None` returns a minimal default set.
|
|
165
|
+
- In `validation="off"` mode, `fields=None` returns the full API payload.
|
|
166
|
+
- Pass a list of field names for a narrow projection (`fields=["id", "location"]`).
|
|
167
|
+
- `fields="all"` or `fields="*"` requests the full payload.
|
|
168
|
+
- Comma-separated strings (e.g. `"id,location"`) are _not_ accepted; pass a list. With `validation="warn"` the call
|
|
169
|
+
falls back to the default field set and logs.
|
|
170
|
+
- `tracks.add()` returns track dicts, but analysis fields (tempo markers, key, etc.) may be populated later by Lexicon.
|
|
171
|
+
- `tracks.update_tempogrid()` replaces tempomarkers while preserving each cuepoint's beat position by rewriting
|
|
172
|
+
`startTime`/`endTime` against the new grid. Use `tracks.update(track_id, {"tempomarkers": [...]})` directly if you
|
|
173
|
+
want the raw field replacement (cuepoints' seconds stay put, beats shift).
|
|
174
|
+
- `tracks.analyze()` returns as soon as Lexicon accepts the request. Analysis runs in the background and the API has no
|
|
175
|
+
completion signal, so poll `tracks.get()` and watch `tempomarkers`, `bpm`, `key`, or `dateModified`.
|
|
176
|
+
- `tracks.analyze(tempo_range=...)` takes one of the eight ids Lexicon's analyze dropdown stores: `"0_300"` (Any),
|
|
177
|
+
`"58_115"`, `"68_135"`, `"78_155"`, `"88_175"`, `"98_195"`, `"128_255"`, `"180_300"` (180+). An equivalent
|
|
178
|
+
`(min, max)` tuple works too. The value is written to Lexicon's stored analysis setting and persists after the call;
|
|
179
|
+
the boolean options apply to that run only.
|
|
180
|
+
|
|
181
|
+
### Track Payload Shapes
|
|
182
|
+
|
|
183
|
+
Rich-payload fields like `cuepoints` and `tempomarkers` aren't in the default field set; request them explicitly
|
|
184
|
+
(`fields=["cuepoints", "tempomarkers"]`) or ask for everything (`fields="all"`). Their shapes are typed via
|
|
185
|
+
`CuePointResponse` and `TempoMarkerResponse`, both re-exported from `lexicon`:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from lexicon import TrackResponse, TempoMarkerResponse, CuePointResponse
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Color values (cuepoint/tag/etc. `color` fields) are Lexicon-specific name strings declared by the `Color` literal type.
|
|
192
|
+
To convert a name to RGB:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from lexicon import color_rgb
|
|
196
|
+
|
|
197
|
+
color_rgb("red_dark") # → (158, 15, 7)
|
|
198
|
+
```
|
|
158
199
|
|
|
159
200
|
### Track Search, Filters, and Sort
|
|
160
201
|
|
|
161
|
-
`tracks.search(filter=...)` accepts a dict of field names and values. The SDK
|
|
162
|
-
|
|
163
|
-
in `"off"` mode.
|
|
202
|
+
`tracks.search(filter=...)` accepts a dict of field names and values. The SDK validates fields/values in
|
|
203
|
+
`"warn"`/`"strict"` modes and can send API-native values in `"off"` mode.
|
|
164
204
|
|
|
165
205
|
Examples:
|
|
166
206
|
|
|
@@ -218,6 +258,7 @@ Playlist type accepts:
|
|
|
218
258
|
- `1`, `2`, `3` (or string numerals `"1"`, `"2"`, `"3"`)
|
|
219
259
|
|
|
220
260
|
### Playlist tracks helpers
|
|
261
|
+
|
|
221
262
|
For getting the tracks of a playlist or editing the tracklist.
|
|
222
263
|
|
|
223
264
|
```python
|
|
@@ -271,6 +312,7 @@ print(path)
|
|
|
271
312
|
```
|
|
272
313
|
|
|
273
314
|
## Raw Requests (Escape Hatch)
|
|
315
|
+
|
|
274
316
|
The API can always be accessed directly with `lex.request`:
|
|
275
317
|
|
|
276
318
|
```python
|
|
@@ -289,8 +331,8 @@ High level namespaces (full mapping in `docs/resource-map.md`):
|
|
|
289
331
|
|
|
290
332
|
## Type Hints
|
|
291
333
|
|
|
292
|
-
The SDK includes TypedDict and Literal types for payloads and enums. These are
|
|
293
|
-
|
|
334
|
+
The SDK includes TypedDict and Literal types for payloads and enums. These are intended to improve editor autocomplete
|
|
335
|
+
and static checks.
|
|
294
336
|
|
|
295
337
|
For full payload schemas and endpoint details, refer to the Lexicon API docs:
|
|
296
338
|
|
|
@@ -301,10 +343,10 @@ For full payload schemas and endpoint details, refer to the Lexicon API docs:
|
|
|
301
343
|
|
|
302
344
|
### Setup
|
|
303
345
|
|
|
304
|
-
#### Pip
|
|
346
|
+
#### Pip
|
|
305
347
|
|
|
306
|
-
Ensure that you have Python 3.9+ installed locally.
|
|
307
|
-
|
|
348
|
+
Ensure that you have Python 3.9+ installed locally. To install all runtime and dev dependencies into a local virtual
|
|
349
|
+
environment using pip:
|
|
308
350
|
|
|
309
351
|
```bash
|
|
310
352
|
python -m venv .venv
|
|
@@ -314,7 +356,8 @@ pip install -e ".[dev]"
|
|
|
314
356
|
|
|
315
357
|
#### uv
|
|
316
358
|
|
|
317
|
-
To use [uv](https://docs.astral.sh/uv/) to install all runtime and dev dependencies into a local virtual environment,
|
|
359
|
+
To use [uv](https://docs.astral.sh/uv/) to install all runtime and dev dependencies into a local virtual environment,
|
|
360
|
+
simply install `uv` and run:
|
|
318
361
|
|
|
319
362
|
```bash
|
|
320
363
|
uv sync --dev
|
|
@@ -336,11 +379,10 @@ make run-integration-tests
|
|
|
336
379
|
|
|
337
380
|
### Linting and Formatting
|
|
338
381
|
|
|
339
|
-
The project uses [ruff](https://docs.astral.sh/ruff/) for both linting and
|
|
340
|
-
formatting.
|
|
382
|
+
The project uses [ruff](https://docs.astral.sh/ruff/) for both linting and formatting.
|
|
341
383
|
|
|
342
|
-
`make test` runs the full suite: format, lint (with auto-fix), then tests.
|
|
343
|
-
|
|
384
|
+
`make test` runs the full suite: format, lint (with auto-fix), then tests. `make fix` runs all auto-fixers (lint +
|
|
385
|
+
format) without running tests.
|
|
344
386
|
|
|
345
387
|
```bash
|
|
346
388
|
make test # format-fix → lint-fix → format-check → lint-check → tests
|
|
@@ -357,10 +399,28 @@ make format-check # check formatting
|
|
|
357
399
|
make format-fix # auto-fix formatting
|
|
358
400
|
```
|
|
359
401
|
|
|
402
|
+
### Pre-commit hook (recommended)
|
|
403
|
+
|
|
404
|
+
The repo ships a [pre-commit](https://pre-commit.com/) config (`.pre-commit-config.yaml`) that mirrors the CI lint job.
|
|
405
|
+
After cloning, install the hook once:
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
uv tool install pre-commit # or: pipx install pre-commit
|
|
409
|
+
pre-commit install
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
`git commit` now runs `ruff format` + `ruff check --fix` on staged files and blocks the commit on any failure. Run
|
|
413
|
+
against the whole repo any time with:
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
pre-commit run --all-files
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
The ruff version is pinned in `.pre-commit-config.yaml`; bump it when CI bumps to keep them aligned.
|
|
420
|
+
|
|
360
421
|
### CI
|
|
361
422
|
|
|
362
|
-
GitHub Actions runs on every push to `main` and on pull requests
|
|
363
|
-
targeting those branches. The pipeline includes:
|
|
423
|
+
GitHub Actions runs on every push to `main` and on pull requests targeting those branches. The pipeline includes:
|
|
364
424
|
|
|
365
425
|
- **Tests** across Python 3.9, 3.10, 3.11, and 3.12
|
|
366
426
|
- **Lint and format checks** via ruff on Python 3.12
|
|
@@ -369,9 +429,8 @@ Before opening a PR, make sure `make test` passes locally.
|
|
|
369
429
|
|
|
370
430
|
### Pull Requests
|
|
371
431
|
|
|
372
|
-
A PR template is provided at `.github/pull_request_template.md`. When opening a
|
|
373
|
-
|
|
374
|
-
testing/checklist items.
|
|
432
|
+
A PR template is provided at `.github/pull_request_template.md`. When opening a PR, fill in the description, check the
|
|
433
|
+
relevant change-type boxes, and confirm testing/checklist items.
|
|
375
434
|
|
|
376
435
|
## License
|
|
377
436
|
|
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
"""Public package surface for the lexicon Python client."""
|
|
2
2
|
|
|
3
|
-
from .client import DEFAULT_HOST, LEXICON_PORT, Lexicon
|
|
3
|
+
from .client import DEFAULT_HOST, LEXICON_PORT, Lexicon, LexiconConnectionError
|
|
4
|
+
from .colors import Color, color_rgb
|
|
4
5
|
from .resources.playlists_types import PlaylistResponse
|
|
5
6
|
from .resources.tag_categories_types import TagCategoryResponse
|
|
6
7
|
from .resources.tags_types import TagResponse
|
|
7
8
|
from .resources.tracks_types import (
|
|
8
9
|
CuePointResponse,
|
|
10
|
+
CuePointUpdate,
|
|
11
|
+
TEMPO_RANGES,
|
|
9
12
|
TempoMarkerResponse,
|
|
13
|
+
TempoMarkerUpdate,
|
|
14
|
+
TempoRange,
|
|
10
15
|
TrackResponse,
|
|
11
16
|
)
|
|
12
17
|
|
|
13
18
|
__all__ = [
|
|
19
|
+
"Color",
|
|
20
|
+
"color_rgb",
|
|
14
21
|
"DEFAULT_HOST",
|
|
15
22
|
"LEXICON_PORT",
|
|
16
23
|
"Lexicon",
|
|
24
|
+
"LexiconConnectionError",
|
|
17
25
|
"TrackResponse",
|
|
18
26
|
"PlaylistResponse",
|
|
19
27
|
"TagResponse",
|
|
20
28
|
"TagCategoryResponse",
|
|
21
29
|
"CuePointResponse",
|
|
30
|
+
"CuePointUpdate",
|
|
22
31
|
"TempoMarkerResponse",
|
|
32
|
+
"TempoMarkerUpdate",
|
|
33
|
+
"TempoRange",
|
|
34
|
+
"TEMPO_RANGES",
|
|
23
35
|
]
|