gukebox 0.9.0__tar.gz → 1.0.0.dev2__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.
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/PKG-INFO +56 -16
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/README.md +53 -15
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/adapters/inbound/api_controller.py +50 -3
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/adapters/inbound/cli_controller.py +1 -1
- gukebox-1.0.0.dev2/discstore/adapters/inbound/config.py +250 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/adapters/inbound/ui_controller.py +72 -6
- gukebox-1.0.0.dev2/discstore/app.py +92 -0
- gukebox-1.0.0.dev2/discstore/command_handlers.py +31 -0
- gukebox-1.0.0.dev2/discstore/commands.py +90 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/di_container.py +0 -31
- gukebox-1.0.0.dev2/jukebox/.DS_Store +0 -0
- gukebox-1.0.0.dev2/jukebox/adapters/.DS_Store +0 -0
- gukebox-1.0.0.dev2/jukebox/adapters/inbound/config.py +100 -0
- gukebox-1.0.0.dev2/jukebox/adapters/outbound/.DS_Store +0 -0
- gukebox-1.0.0.dev2/jukebox/adapters/outbound/players/sonos_player_adapter.py +200 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/adapters/outbound/readers/dryrun_reader_adapter.py +3 -1
- gukebox-1.0.0.dev2/jukebox/admin/__init__.py +1 -0
- gukebox-1.0.0.dev2/jukebox/admin/app.py +355 -0
- gukebox-1.0.0.dev2/jukebox/admin/cli_presentation.py +484 -0
- gukebox-1.0.0.dev2/jukebox/admin/command_handlers.py +115 -0
- gukebox-1.0.0.dev2/jukebox/admin/commands.py +70 -0
- gukebox-1.0.0.dev2/jukebox/admin/di_container.py +73 -0
- gukebox-1.0.0.dev2/jukebox/app.py +75 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/di_container.py +18 -32
- gukebox-1.0.0.dev2/jukebox/domain/.DS_Store +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/entities/__init__.py +2 -0
- gukebox-1.0.0.dev2/jukebox/domain/entities/current_tag_action.py +11 -0
- gukebox-1.0.0.dev2/jukebox/domain/entities/playback_session.py +19 -0
- gukebox-1.0.0.dev2/jukebox/domain/use_cases/__init__.py +5 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/use_cases/determine_action.py +10 -11
- gukebox-1.0.0.dev2/jukebox/domain/use_cases/determine_current_tag_action.py +35 -0
- gukebox-1.0.0.dev2/jukebox/domain/use_cases/handle_tag_event.py +121 -0
- gukebox-1.0.0.dev2/jukebox/settings/__init__.py +14 -0
- gukebox-1.0.0.dev2/jukebox/settings/definitions.py +159 -0
- gukebox-1.0.0.dev2/jukebox/settings/dict_utils.py +17 -0
- gukebox-1.0.0.dev2/jukebox/settings/entities.py +267 -0
- gukebox-1.0.0.dev2/jukebox/settings/errors.py +14 -0
- gukebox-1.0.0.dev2/jukebox/settings/file_settings_repository.py +83 -0
- gukebox-1.0.0.dev2/jukebox/settings/migration.py +40 -0
- gukebox-1.0.0.dev2/jukebox/settings/repositories.py +12 -0
- gukebox-1.0.0.dev2/jukebox/settings/resolve.py +459 -0
- gukebox-1.0.0.dev2/jukebox/settings/runtime_validation.py +26 -0
- gukebox-1.0.0.dev2/jukebox/settings/service_protocols.py +20 -0
- gukebox-1.0.0.dev2/jukebox/settings/sonos_runtime.py +175 -0
- gukebox-1.0.0.dev2/jukebox/settings/timing_validation.py +8 -0
- gukebox-1.0.0.dev2/jukebox/settings/types.py +4 -0
- gukebox-1.0.0.dev2/jukebox/settings/validation_rules.py +81 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/shared/config_utils.py +0 -18
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/shared/dependency_messages.py +1 -3
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/pyproject.toml +7 -4
- gukebox-0.9.0/discstore/adapters/inbound/config.py +0 -187
- gukebox-0.9.0/discstore/app.py +0 -50
- gukebox-0.9.0/jukebox/adapters/inbound/config.py +0 -138
- gukebox-0.9.0/jukebox/adapters/outbound/players/sonos_player_adapter.py +0 -68
- gukebox-0.9.0/jukebox/app.py +0 -18
- gukebox-0.9.0/jukebox/domain/entities/playback_session.py +0 -16
- gukebox-0.9.0/jukebox/domain/use_cases/__init__.py +0 -4
- gukebox-0.9.0/jukebox/domain/use_cases/handle_tag_event.py +0 -139
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/LICENSE +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/adapters/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/adapters/inbound/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/adapters/inbound/cli_display.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/adapters/inbound/interactive_cli_controller.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/adapters/outbound/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/adapters/outbound/json_library_adapter.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/adapters/outbound/text_current_tag_adapter.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/entities/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/entities/current_tag_status.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/repositories/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/use_cases/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/use_cases/add_disc.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/use_cases/edit_disc.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/use_cases/get_current_tag_status.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/use_cases/get_disc.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/use_cases/list_discs.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/use_cases/remove_disc.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/use_cases/resolve_tag_id.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/discstore/domain/use_cases/search_discs.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/adapters/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/adapters/inbound/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/adapters/inbound/cli_controller.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/adapters/outbound/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/adapters/outbound/json_library_adapter.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/adapters/outbound/players/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/adapters/outbound/players/dryrun_player_adapter.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/adapters/outbound/readers/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/adapters/outbound/readers/nfc_reader_adapter.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/adapters/outbound/text_current_tag_adapter.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/entities/disc.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/entities/library.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/entities/playback_action.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/entities/tag_event.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/ports/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/ports/player_port.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/ports/reader_port.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/repositories/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/repositories/current_tag_repository.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/domain/repositories/library_repository.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/shared/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/shared/logger.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/jukebox/shared/timing.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/pn532/__init__.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/pn532/pn532.py +0 -0
- {gukebox-0.9.0 → gukebox-1.0.0.dev2}/pn532/spi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: gukebox
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0.dev2
|
|
4
4
|
Summary: A Jukebox to play music on speakers using 'CD' with NFC tag
|
|
5
5
|
Keywords: jukebox,discstore,music,nfc
|
|
6
6
|
Author: Gudsfile
|
|
@@ -36,6 +36,8 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
36
36
|
Classifier: Programming Language :: Python :: 3.13
|
|
37
37
|
Requires-Dist: pydantic==2.12.5
|
|
38
38
|
Requires-Dist: soco==0.30.14
|
|
39
|
+
Requires-Dist: typer==0.23.2 ; python_full_version < '3.10'
|
|
40
|
+
Requires-Dist: typer==0.24.1 ; python_full_version >= '3.10'
|
|
39
41
|
Requires-Dist: fastapi==0.128.7 ; python_full_version < '3.10' and extra == 'api'
|
|
40
42
|
Requires-Dist: fastapi==0.135.1 ; python_full_version >= '3.10' and extra == 'api'
|
|
41
43
|
Requires-Dist: uvicorn==0.39.0 ; python_full_version < '3.10' and extra == 'api'
|
|
@@ -146,7 +148,7 @@ pip install "gukebox[nfc]"
|
|
|
146
148
|
- For non-system Python 3.13+, you can still install via pip/uv/poetry/etc. but you must build the `lgpio` package from source and it may require other system packages.
|
|
147
149
|
- All releases can be downloaded and installed from the [GitHub releases page](https://github.com/Gudsfile/jukebox/releases).
|
|
148
150
|
|
|
149
|
-
###
|
|
151
|
+
### Installation for development
|
|
150
152
|
|
|
151
153
|
For development read the [Developer setup](#developer-setup) section.
|
|
152
154
|
|
|
@@ -158,9 +160,7 @@ uv sync
|
|
|
158
160
|
|
|
159
161
|
## First steps
|
|
160
162
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
Initialize the library file with `discstore` or manually create it at `~/.jukebox/library.json`.
|
|
163
|
+
Initialize the library file with `discstore` or manually create it at `~/.config/jukebox/library.json`.
|
|
164
164
|
|
|
165
165
|
### Manage the library with the discstore
|
|
166
166
|
|
|
@@ -176,6 +176,16 @@ discstore add --from-current --uri /path/to/media.mp3
|
|
|
176
176
|
|
|
177
177
|
Other commands are available, use `--help` to see them.
|
|
178
178
|
|
|
179
|
+
### Admin CLI
|
|
180
|
+
|
|
181
|
+
Use `jukebox-admin` for admin workflows such as settings inspection and the
|
|
182
|
+
admin API/UI servers.
|
|
183
|
+
|
|
184
|
+
```shell
|
|
185
|
+
jukebox-admin settings show
|
|
186
|
+
jukebox-admin settings show --effective
|
|
187
|
+
```
|
|
188
|
+
|
|
179
189
|
To use the `api` and `ui` commands, additional packages are required. You can install the `package[extra]` syntax regardless of the package manager you use, for example:
|
|
180
190
|
|
|
181
191
|
```shell
|
|
@@ -189,13 +199,15 @@ uv tool install gukebox[ui]
|
|
|
189
199
|
When running from this repository with `uv`, include the extra on the command as well:
|
|
190
200
|
|
|
191
201
|
```shell
|
|
192
|
-
uv run --extra api
|
|
193
|
-
uv run --extra ui
|
|
202
|
+
uv run --extra api jukebox-admin api
|
|
203
|
+
uv run --extra ui jukebox-admin ui
|
|
194
204
|
```
|
|
195
205
|
|
|
206
|
+
`discstore settings ...`, `discstore api`, and `discstore ui` remain available as compatibility commands, but `jukebox-admin` is the preferred CLI for admin flows.
|
|
207
|
+
|
|
196
208
|
### Manage the library manually
|
|
197
209
|
|
|
198
|
-
Complete your `~/.jukebox/library.json` file with each tag id and the expected media URI.
|
|
210
|
+
Complete your `~/.config/jukebox/library.json` file with each tag id and the expected media URI.
|
|
199
211
|
Take a look at `library.example.json` and the [The library file](#the-library-file) section for more information.
|
|
200
212
|
|
|
201
213
|
## Usage
|
|
@@ -213,7 +225,7 @@ Optional Parameters
|
|
|
213
225
|
| Parameter | Description |
|
|
214
226
|
| --- | --- |
|
|
215
227
|
| `--help` | Show help message. |
|
|
216
|
-
| `--library` | Path to the library file, default: `~/.jukebox/library.json`. |
|
|
228
|
+
| `--library` | Path to the library file, default: `~/.config/jukebox/library.json`. |
|
|
217
229
|
| `--pause-delay SECONDS` | Grace period before pausing when the NFC tag is removed. Fractional values such as `0.5` or `0.2` are supported, with a minimum of `0.2` seconds to avoid pausing on brief missed reads. Default: 0.25 seconds. |
|
|
218
230
|
| `--pause-duration SECONDS` | Maximum duration of a pause before resetting the queue. Default: 900 seconds (15 minutes). |
|
|
219
231
|
| `--verbose` | Enable verbose logging. |
|
|
@@ -242,9 +254,13 @@ Displays the events that a real speaker would have performed (`playing …`, `pa
|
|
|
242
254
|
|
|
243
255
|
**Sonos** (`sonos`) [](https://github.com/SoCo/SoCo)
|
|
244
256
|
Play music through a Sonos speaker.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
257
|
+
Three ways to select the speaker (mutually exclusive):
|
|
258
|
+
|
|
259
|
+
| Option | CLI flag | Environment variable | Behaviour |
|
|
260
|
+
| --- | --- | --- | --- |
|
|
261
|
+
| By IP | `--sonos-host 192.168.0.x` | `JUKEBOX_SONOS_HOST` | Connect directly, no discovery |
|
|
262
|
+
| By name | `--sonos-name "Living Room"` | `JUKEBOX_SONOS_NAME` | Discover, then filter by name (case-sensitive) |
|
|
263
|
+
| Auto | *(omit both)* | *(omit both)* | Discover, pick the first speaker alphabetically |
|
|
248
264
|
|
|
249
265
|
## The library file
|
|
250
266
|
|
|
@@ -252,7 +268,7 @@ The `library.json` file is a JSON file that contains the artists, albums and tag
|
|
|
252
268
|
It is used by the `jukebox` command to find the corresponding metadata for each tag.
|
|
253
269
|
And the `discstore` command help you to managed this file with a CLI, an interactive CLI, an API or an UI (see `discstore --help`).
|
|
254
270
|
|
|
255
|
-
By default, this file should be placed at `~/.jukebox/library.json`. But you can use another path by creating a `JUKEBOX_LIBRARY_PATH` environment variable or with the `--library` argument.
|
|
271
|
+
By default, this file should be placed at `~/.config/jukebox/library.json`. But you can use another path by creating a `JUKEBOX_LIBRARY_PATH` environment variable or with the `--library` argument.
|
|
256
272
|
|
|
257
273
|
```json
|
|
258
274
|
{
|
|
@@ -291,7 +307,7 @@ It is also possible to use the `shuffle` key to play the album in shuffle mode:
|
|
|
291
307
|
}
|
|
292
308
|
```
|
|
293
309
|
|
|
294
|
-
To summarize, for example, if you have the following `~/.jukebox/library.json` file:
|
|
310
|
+
To summarize, for example, if you have the following `~/.config/jukebox/library.json` file:
|
|
295
311
|
|
|
296
312
|
```json
|
|
297
313
|
{
|
|
@@ -324,7 +340,8 @@ uv sync
|
|
|
324
340
|
|
|
325
341
|
Add `--all-extras` to install dependencies for all extras (`api` and `ui`).
|
|
326
342
|
|
|
327
|
-
|
|
343
|
+
If needed, set `JUKEBOX_SONOS_HOST` (IP) or `JUKEBOX_SONOS_NAME` (speaker name) to select your Sonos speaker (see [Players](#players)).
|
|
344
|
+
If neither is set, the jukebox will auto-discover a speaker on the network.
|
|
328
345
|
To do this you can use a `.env` file and `uv run --env-file .env <command to run>`.
|
|
329
346
|
A `.env.example` file is available, you can copy it and modify it to use it.
|
|
330
347
|
|
|
@@ -344,9 +361,23 @@ Start the discstore `uv` and use `--help` to show help message
|
|
|
344
361
|
uv run discstore --help
|
|
345
362
|
```
|
|
346
363
|
|
|
347
|
-
|
|
364
|
+
Use `jukebox-admin` for admin commands:
|
|
348
365
|
|
|
349
366
|
```shell
|
|
367
|
+
uv run jukebox-admin settings show
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
For the server-backed admin commands, include the matching extra:
|
|
371
|
+
|
|
372
|
+
```shell
|
|
373
|
+
uv run --extra api jukebox-admin api
|
|
374
|
+
uv run --extra ui jukebox-admin ui
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Legacy compatibility commands remain available during the transition:
|
|
378
|
+
|
|
379
|
+
```shell
|
|
380
|
+
uv run discstore settings show
|
|
350
381
|
uv run --extra api discstore api
|
|
351
382
|
uv run --extra ui discstore ui
|
|
352
383
|
```
|
|
@@ -360,6 +391,15 @@ Other commands are available:
|
|
|
360
391
|
| `uv run ruff check --fix` | Fix the code. |
|
|
361
392
|
| `uv run pytest` | Run the tests. |
|
|
362
393
|
|
|
394
|
+
### Pre-commit
|
|
395
|
+
|
|
396
|
+
[prek](https://github.com/j178/prek) is configured; you can [install it](https://github.com/j178/prek?tab=readme-ov-file#installation) to automatically run validations on each commit.
|
|
397
|
+
|
|
398
|
+
```shell
|
|
399
|
+
uv tool install prek
|
|
400
|
+
prek install
|
|
401
|
+
```
|
|
402
|
+
|
|
363
403
|
## Contributing
|
|
364
404
|
|
|
365
405
|
Contributions are welcome! Feel free to open an issue or a pull request.
|
|
@@ -91,7 +91,7 @@ pip install "gukebox[nfc]"
|
|
|
91
91
|
- For non-system Python 3.13+, you can still install via pip/uv/poetry/etc. but you must build the `lgpio` package from source and it may require other system packages.
|
|
92
92
|
- All releases can be downloaded and installed from the [GitHub releases page](https://github.com/Gudsfile/jukebox/releases).
|
|
93
93
|
|
|
94
|
-
###
|
|
94
|
+
### Installation for development
|
|
95
95
|
|
|
96
96
|
For development read the [Developer setup](#developer-setup) section.
|
|
97
97
|
|
|
@@ -103,9 +103,7 @@ uv sync
|
|
|
103
103
|
|
|
104
104
|
## First steps
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
Initialize the library file with `discstore` or manually create it at `~/.jukebox/library.json`.
|
|
106
|
+
Initialize the library file with `discstore` or manually create it at `~/.config/jukebox/library.json`.
|
|
109
107
|
|
|
110
108
|
### Manage the library with the discstore
|
|
111
109
|
|
|
@@ -121,6 +119,16 @@ discstore add --from-current --uri /path/to/media.mp3
|
|
|
121
119
|
|
|
122
120
|
Other commands are available, use `--help` to see them.
|
|
123
121
|
|
|
122
|
+
### Admin CLI
|
|
123
|
+
|
|
124
|
+
Use `jukebox-admin` for admin workflows such as settings inspection and the
|
|
125
|
+
admin API/UI servers.
|
|
126
|
+
|
|
127
|
+
```shell
|
|
128
|
+
jukebox-admin settings show
|
|
129
|
+
jukebox-admin settings show --effective
|
|
130
|
+
```
|
|
131
|
+
|
|
124
132
|
To use the `api` and `ui` commands, additional packages are required. You can install the `package[extra]` syntax regardless of the package manager you use, for example:
|
|
125
133
|
|
|
126
134
|
```shell
|
|
@@ -134,13 +142,15 @@ uv tool install gukebox[ui]
|
|
|
134
142
|
When running from this repository with `uv`, include the extra on the command as well:
|
|
135
143
|
|
|
136
144
|
```shell
|
|
137
|
-
uv run --extra api
|
|
138
|
-
uv run --extra ui
|
|
145
|
+
uv run --extra api jukebox-admin api
|
|
146
|
+
uv run --extra ui jukebox-admin ui
|
|
139
147
|
```
|
|
140
148
|
|
|
149
|
+
`discstore settings ...`, `discstore api`, and `discstore ui` remain available as compatibility commands, but `jukebox-admin` is the preferred CLI for admin flows.
|
|
150
|
+
|
|
141
151
|
### Manage the library manually
|
|
142
152
|
|
|
143
|
-
Complete your `~/.jukebox/library.json` file with each tag id and the expected media URI.
|
|
153
|
+
Complete your `~/.config/jukebox/library.json` file with each tag id and the expected media URI.
|
|
144
154
|
Take a look at `library.example.json` and the [The library file](#the-library-file) section for more information.
|
|
145
155
|
|
|
146
156
|
## Usage
|
|
@@ -158,7 +168,7 @@ Optional Parameters
|
|
|
158
168
|
| Parameter | Description |
|
|
159
169
|
| --- | --- |
|
|
160
170
|
| `--help` | Show help message. |
|
|
161
|
-
| `--library` | Path to the library file, default: `~/.jukebox/library.json`. |
|
|
171
|
+
| `--library` | Path to the library file, default: `~/.config/jukebox/library.json`. |
|
|
162
172
|
| `--pause-delay SECONDS` | Grace period before pausing when the NFC tag is removed. Fractional values such as `0.5` or `0.2` are supported, with a minimum of `0.2` seconds to avoid pausing on brief missed reads. Default: 0.25 seconds. |
|
|
163
173
|
| `--pause-duration SECONDS` | Maximum duration of a pause before resetting the queue. Default: 900 seconds (15 minutes). |
|
|
164
174
|
| `--verbose` | Enable verbose logging. |
|
|
@@ -187,9 +197,13 @@ Displays the events that a real speaker would have performed (`playing …`, `pa
|
|
|
187
197
|
|
|
188
198
|
**Sonos** (`sonos`) [](https://github.com/SoCo/SoCo)
|
|
189
199
|
Play music through a Sonos speaker.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
200
|
+
Three ways to select the speaker (mutually exclusive):
|
|
201
|
+
|
|
202
|
+
| Option | CLI flag | Environment variable | Behaviour |
|
|
203
|
+
| --- | --- | --- | --- |
|
|
204
|
+
| By IP | `--sonos-host 192.168.0.x` | `JUKEBOX_SONOS_HOST` | Connect directly, no discovery |
|
|
205
|
+
| By name | `--sonos-name "Living Room"` | `JUKEBOX_SONOS_NAME` | Discover, then filter by name (case-sensitive) |
|
|
206
|
+
| Auto | *(omit both)* | *(omit both)* | Discover, pick the first speaker alphabetically |
|
|
193
207
|
|
|
194
208
|
## The library file
|
|
195
209
|
|
|
@@ -197,7 +211,7 @@ The `library.json` file is a JSON file that contains the artists, albums and tag
|
|
|
197
211
|
It is used by the `jukebox` command to find the corresponding metadata for each tag.
|
|
198
212
|
And the `discstore` command help you to managed this file with a CLI, an interactive CLI, an API or an UI (see `discstore --help`).
|
|
199
213
|
|
|
200
|
-
By default, this file should be placed at `~/.jukebox/library.json`. But you can use another path by creating a `JUKEBOX_LIBRARY_PATH` environment variable or with the `--library` argument.
|
|
214
|
+
By default, this file should be placed at `~/.config/jukebox/library.json`. But you can use another path by creating a `JUKEBOX_LIBRARY_PATH` environment variable or with the `--library` argument.
|
|
201
215
|
|
|
202
216
|
```json
|
|
203
217
|
{
|
|
@@ -236,7 +250,7 @@ It is also possible to use the `shuffle` key to play the album in shuffle mode:
|
|
|
236
250
|
}
|
|
237
251
|
```
|
|
238
252
|
|
|
239
|
-
To summarize, for example, if you have the following `~/.jukebox/library.json` file:
|
|
253
|
+
To summarize, for example, if you have the following `~/.config/jukebox/library.json` file:
|
|
240
254
|
|
|
241
255
|
```json
|
|
242
256
|
{
|
|
@@ -269,7 +283,8 @@ uv sync
|
|
|
269
283
|
|
|
270
284
|
Add `--all-extras` to install dependencies for all extras (`api` and `ui`).
|
|
271
285
|
|
|
272
|
-
|
|
286
|
+
If needed, set `JUKEBOX_SONOS_HOST` (IP) or `JUKEBOX_SONOS_NAME` (speaker name) to select your Sonos speaker (see [Players](#players)).
|
|
287
|
+
If neither is set, the jukebox will auto-discover a speaker on the network.
|
|
273
288
|
To do this you can use a `.env` file and `uv run --env-file .env <command to run>`.
|
|
274
289
|
A `.env.example` file is available, you can copy it and modify it to use it.
|
|
275
290
|
|
|
@@ -289,9 +304,23 @@ Start the discstore `uv` and use `--help` to show help message
|
|
|
289
304
|
uv run discstore --help
|
|
290
305
|
```
|
|
291
306
|
|
|
292
|
-
|
|
307
|
+
Use `jukebox-admin` for admin commands:
|
|
293
308
|
|
|
294
309
|
```shell
|
|
310
|
+
uv run jukebox-admin settings show
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
For the server-backed admin commands, include the matching extra:
|
|
314
|
+
|
|
315
|
+
```shell
|
|
316
|
+
uv run --extra api jukebox-admin api
|
|
317
|
+
uv run --extra ui jukebox-admin ui
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Legacy compatibility commands remain available during the transition:
|
|
321
|
+
|
|
322
|
+
```shell
|
|
323
|
+
uv run discstore settings show
|
|
295
324
|
uv run --extra api discstore api
|
|
296
325
|
uv run --extra ui discstore ui
|
|
297
326
|
```
|
|
@@ -305,6 +334,15 @@ Other commands are available:
|
|
|
305
334
|
| `uv run ruff check --fix` | Fix the code. |
|
|
306
335
|
| `uv run pytest` | Run the tests. |
|
|
307
336
|
|
|
337
|
+
### Pre-commit
|
|
338
|
+
|
|
339
|
+
[prek](https://github.com/j178/prek) is configured; you can [install it](https://github.com/j178/prek?tab=readme-ov-file#installation) to automatically run validations on each commit.
|
|
340
|
+
|
|
341
|
+
```shell
|
|
342
|
+
uv tool install prek
|
|
343
|
+
prek install
|
|
344
|
+
```
|
|
345
|
+
|
|
308
346
|
## Contributing
|
|
309
347
|
|
|
310
348
|
Contributions are welcome! Feel free to open an issue or a pull request.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from typing import Dict
|
|
1
|
+
from typing import Any, Dict, cast
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, RootModel
|
|
2
4
|
|
|
3
5
|
from jukebox.shared.dependency_messages import optional_extra_dependency_message
|
|
4
6
|
|
|
@@ -15,6 +17,9 @@ from discstore.domain.use_cases.edit_disc import EditDisc
|
|
|
15
17
|
from discstore.domain.use_cases.get_current_tag_status import GetCurrentTagStatus
|
|
16
18
|
from discstore.domain.use_cases.list_discs import ListDiscs
|
|
17
19
|
from discstore.domain.use_cases.remove_disc import RemoveDisc
|
|
20
|
+
from jukebox.settings.errors import SettingsError
|
|
21
|
+
from jukebox.settings.service_protocols import SettingsService
|
|
22
|
+
from jukebox.settings.types import JsonObject
|
|
18
23
|
|
|
19
24
|
|
|
20
25
|
class DiscInput(Disc):
|
|
@@ -29,6 +34,14 @@ class CurrentTagStatusOutput(CurrentTagStatus):
|
|
|
29
34
|
pass
|
|
30
35
|
|
|
31
36
|
|
|
37
|
+
class SettingsResetInput(BaseModel):
|
|
38
|
+
path: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SettingsPatchInput(RootModel[Dict[str, Any]]):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
32
45
|
class APIController:
|
|
33
46
|
def __init__(
|
|
34
47
|
self,
|
|
@@ -37,12 +50,14 @@ class APIController:
|
|
|
37
50
|
remove_disc: RemoveDisc,
|
|
38
51
|
edit_disc: EditDisc,
|
|
39
52
|
get_current_tag_status: GetCurrentTagStatus,
|
|
53
|
+
settings_service: SettingsService,
|
|
40
54
|
):
|
|
41
55
|
self.add_disc = add_disc
|
|
42
56
|
self.list_discs = list_discs
|
|
43
57
|
self.remove_disc = remove_disc
|
|
44
58
|
self.edit_disc = edit_disc
|
|
45
59
|
self.get_current_tag_status = get_current_tag_status
|
|
60
|
+
self.settings_service = settings_service
|
|
46
61
|
self.app = FastAPI(
|
|
47
62
|
title="DiscStore API",
|
|
48
63
|
description="API for managing Jukebox disc library",
|
|
@@ -68,6 +83,38 @@ class APIController:
|
|
|
68
83
|
|
|
69
84
|
return CurrentTagStatusOutput(**current_tag_status.model_dump())
|
|
70
85
|
|
|
86
|
+
@self.app.get("/api/v1/settings")
|
|
87
|
+
def get_settings():
|
|
88
|
+
try:
|
|
89
|
+
return self.settings_service.get_persisted_settings_view()
|
|
90
|
+
except Exception as err:
|
|
91
|
+
raise HTTPException(status_code=500, detail=f"Server error: {str(err)}")
|
|
92
|
+
|
|
93
|
+
@self.app.get("/api/v1/settings/effective")
|
|
94
|
+
def get_effective_settings():
|
|
95
|
+
try:
|
|
96
|
+
return self.settings_service.get_effective_settings_view()
|
|
97
|
+
except Exception as err:
|
|
98
|
+
raise HTTPException(status_code=500, detail=f"Server error: {str(err)}")
|
|
99
|
+
|
|
100
|
+
@self.app.patch("/api/v1/settings")
|
|
101
|
+
def patch_settings(patch: SettingsPatchInput):
|
|
102
|
+
try:
|
|
103
|
+
return self.settings_service.patch_persisted_settings(cast(JsonObject, patch.root))
|
|
104
|
+
except SettingsError as err:
|
|
105
|
+
raise HTTPException(status_code=400, detail=str(err))
|
|
106
|
+
except Exception as err:
|
|
107
|
+
raise HTTPException(status_code=500, detail=f"Server error: {str(err)}")
|
|
108
|
+
|
|
109
|
+
@self.app.post("/api/v1/settings/reset")
|
|
110
|
+
def reset_settings(payload: SettingsResetInput):
|
|
111
|
+
try:
|
|
112
|
+
return self.settings_service.reset_persisted_value(payload.path)
|
|
113
|
+
except SettingsError as err:
|
|
114
|
+
raise HTTPException(status_code=400, detail=str(err))
|
|
115
|
+
except Exception as err:
|
|
116
|
+
raise HTTPException(status_code=500, detail=f"Server error: {str(err)}")
|
|
117
|
+
|
|
71
118
|
@self.app.post("/api/v1/disc", status_code=201)
|
|
72
119
|
def add_or_edit_disc(tag_id: str, disc: DiscInput):
|
|
73
120
|
try:
|
|
@@ -85,7 +132,7 @@ class APIController:
|
|
|
85
132
|
try:
|
|
86
133
|
self.remove_disc.execute(tag_id)
|
|
87
134
|
return {"message": "Disc removed"}
|
|
88
|
-
except ValueError as
|
|
89
|
-
raise HTTPException(status_code=404, detail=str(
|
|
135
|
+
except ValueError as value_err:
|
|
136
|
+
raise HTTPException(status_code=404, detail=str(value_err))
|
|
90
137
|
except Exception as err:
|
|
91
138
|
raise HTTPException(status_code=500, detail=f"Server error: {str(err)}")
|
|
@@ -2,7 +2,7 @@ import logging
|
|
|
2
2
|
from typing import Union
|
|
3
3
|
|
|
4
4
|
from discstore.adapters.inbound.cli_display import display_library_line, display_library_table
|
|
5
|
-
from discstore.
|
|
5
|
+
from discstore.commands import (
|
|
6
6
|
CliAddCommand,
|
|
7
7
|
CliEditCommand,
|
|
8
8
|
CliGetCommand,
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Optional, Union
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ValidationError
|
|
7
|
+
|
|
8
|
+
from discstore.commands import (
|
|
9
|
+
CliAddCommand,
|
|
10
|
+
CliEditCommand,
|
|
11
|
+
CliGetCommand,
|
|
12
|
+
CliListCommand,
|
|
13
|
+
CliListCommandModes,
|
|
14
|
+
CliRemoveCommand,
|
|
15
|
+
CliSearchCommand,
|
|
16
|
+
InteractiveCliCommand,
|
|
17
|
+
)
|
|
18
|
+
from jukebox.admin.commands import ApiCommand, SettingsResetCommand, SettingsSetCommand, SettingsShowCommand, UiCommand
|
|
19
|
+
from jukebox.shared.config_utils import add_verbose_arg, add_version_arg
|
|
20
|
+
|
|
21
|
+
LOGGER = logging.getLogger("discstore")
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"ApiCommand",
|
|
25
|
+
"CliAddCommand",
|
|
26
|
+
"CliEditCommand",
|
|
27
|
+
"CliGetCommand",
|
|
28
|
+
"CliListCommand",
|
|
29
|
+
"CliListCommandModes",
|
|
30
|
+
"CliRemoveCommand",
|
|
31
|
+
"CliSearchCommand",
|
|
32
|
+
"DiscStoreConfig",
|
|
33
|
+
"InteractiveCliCommand",
|
|
34
|
+
"SettingsResetCommand",
|
|
35
|
+
"SettingsSetCommand",
|
|
36
|
+
"SettingsShowCommand",
|
|
37
|
+
"UiCommand",
|
|
38
|
+
"add_from_current_arg",
|
|
39
|
+
"parse_config",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DiscStoreConfig(BaseModel):
|
|
44
|
+
library: Optional[str] = None
|
|
45
|
+
verbose: bool = False
|
|
46
|
+
|
|
47
|
+
command: Union[
|
|
48
|
+
ApiCommand,
|
|
49
|
+
InteractiveCliCommand,
|
|
50
|
+
CliAddCommand,
|
|
51
|
+
CliListCommand,
|
|
52
|
+
CliRemoveCommand,
|
|
53
|
+
CliEditCommand,
|
|
54
|
+
CliGetCommand,
|
|
55
|
+
CliSearchCommand,
|
|
56
|
+
SettingsResetCommand,
|
|
57
|
+
SettingsSetCommand,
|
|
58
|
+
SettingsShowCommand,
|
|
59
|
+
UiCommand,
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def add_from_current_arg(parser: argparse.ArgumentParser) -> None:
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"--from-current",
|
|
66
|
+
dest="use_current_tag",
|
|
67
|
+
action="store_true",
|
|
68
|
+
help="Resolve the tag ID from shared current-tag.txt state",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _build_library_command(command_name: str, args: argparse.Namespace):
|
|
73
|
+
if command_name == "add":
|
|
74
|
+
return CliAddCommand(
|
|
75
|
+
type="add",
|
|
76
|
+
tag=args.tag,
|
|
77
|
+
use_current_tag=args.use_current_tag,
|
|
78
|
+
uri=args.uri,
|
|
79
|
+
track=args.track,
|
|
80
|
+
artist=args.artist,
|
|
81
|
+
album=args.album,
|
|
82
|
+
)
|
|
83
|
+
if command_name == "list":
|
|
84
|
+
if args.positional_mode is not None:
|
|
85
|
+
print(
|
|
86
|
+
"warning: positional mode argument is deprecated; use --mode instead",
|
|
87
|
+
file=sys.stderr,
|
|
88
|
+
)
|
|
89
|
+
# if args.positional_mode is not None and args.mode is not None:
|
|
90
|
+
# raise ValueError("You must provide exactly one of: mode OR --mode argument")
|
|
91
|
+
return CliListCommand(type="list", mode=args.positional_mode if args.positional_mode else args.mode)
|
|
92
|
+
if command_name == "remove":
|
|
93
|
+
return CliRemoveCommand(type="remove", tag=args.tag, use_current_tag=args.use_current_tag)
|
|
94
|
+
if command_name == "edit":
|
|
95
|
+
return CliEditCommand(
|
|
96
|
+
type="edit",
|
|
97
|
+
tag=args.tag,
|
|
98
|
+
use_current_tag=args.use_current_tag,
|
|
99
|
+
uri=args.uri,
|
|
100
|
+
track=args.track,
|
|
101
|
+
artist=args.artist,
|
|
102
|
+
album=args.album,
|
|
103
|
+
)
|
|
104
|
+
if command_name == "get":
|
|
105
|
+
return CliGetCommand(type="get", tag=args.tag, use_current_tag=args.use_current_tag)
|
|
106
|
+
if command_name == "search":
|
|
107
|
+
return CliSearchCommand(type="search", query=args.query)
|
|
108
|
+
if command_name == "interactive":
|
|
109
|
+
return InteractiveCliCommand(type="interactive")
|
|
110
|
+
raise ValueError(f"Unsupported command: {command_name}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _build_admin_command(args: argparse.Namespace):
|
|
114
|
+
if args.command == "api":
|
|
115
|
+
return ApiCommand(type="api", port=args.port)
|
|
116
|
+
if args.command == "ui":
|
|
117
|
+
return UiCommand(type="ui", port=args.port)
|
|
118
|
+
if args.command != "settings":
|
|
119
|
+
raise ValueError(f"Unsupported admin command: {args.command}")
|
|
120
|
+
|
|
121
|
+
if args.settings_command == "show":
|
|
122
|
+
return SettingsShowCommand(type="settings_show", effective=args.effective, json_output=args.json_output)
|
|
123
|
+
if args.settings_command == "set":
|
|
124
|
+
return SettingsSetCommand(
|
|
125
|
+
type="settings_set",
|
|
126
|
+
dotted_path=args.dotted_path,
|
|
127
|
+
value=args.value,
|
|
128
|
+
json_output=args.json_output,
|
|
129
|
+
)
|
|
130
|
+
if args.settings_command == "reset":
|
|
131
|
+
return SettingsResetCommand(
|
|
132
|
+
type="settings_reset",
|
|
133
|
+
dotted_path=args.dotted_path,
|
|
134
|
+
json_output=args.json_output,
|
|
135
|
+
)
|
|
136
|
+
raise ValueError(f"Unsupported settings command: {args.settings_command}")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def parse_config() -> DiscStoreConfig:
|
|
140
|
+
parser = argparse.ArgumentParser(
|
|
141
|
+
prog="discstore",
|
|
142
|
+
description="Manage your disc collection for jukebox",
|
|
143
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
parser.add_argument(
|
|
147
|
+
"-l",
|
|
148
|
+
"--library",
|
|
149
|
+
default=None,
|
|
150
|
+
help="override the library JSON path for this process",
|
|
151
|
+
)
|
|
152
|
+
add_verbose_arg(parser)
|
|
153
|
+
add_version_arg(parser)
|
|
154
|
+
|
|
155
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
156
|
+
|
|
157
|
+
# CLI commands
|
|
158
|
+
add_parser = subparsers.add_parser("add", help="Add a disc")
|
|
159
|
+
add_from_current_arg(add_parser)
|
|
160
|
+
add_parser.add_argument("tag", nargs="?", help="Tag to be associated with the disc")
|
|
161
|
+
add_parser.add_argument("--uri", required=True, help="Path or URI of the media file")
|
|
162
|
+
add_parser.add_argument("--track", required=False, help="Name of the track")
|
|
163
|
+
add_parser.add_argument("--artist", required=False, help="Name of the artist or band")
|
|
164
|
+
add_parser.add_argument("--album", required=False, help="Name of the album")
|
|
165
|
+
add_parser.add_argument("--opts", required=False, help="Playback options for the discs")
|
|
166
|
+
|
|
167
|
+
list_parser = subparsers.add_parser("list", help="List all discs")
|
|
168
|
+
list_parser.add_argument("positional_mode", nargs="?", choices=["line", "table"], help=argparse.SUPPRESS)
|
|
169
|
+
list_parser.add_argument(
|
|
170
|
+
"--mode", choices=["line", "table"], default="table", help="Displaying mode (default: table)"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
remove_parser = subparsers.add_parser("remove", help="Remove a disc")
|
|
174
|
+
add_from_current_arg(remove_parser)
|
|
175
|
+
remove_parser.add_argument("tag", nargs="?", help="Tag to remove")
|
|
176
|
+
|
|
177
|
+
edit_parser = subparsers.add_parser("edit", help="Edit a disc (partial updates supported)")
|
|
178
|
+
add_from_current_arg(edit_parser)
|
|
179
|
+
edit_parser.add_argument("tag", nargs="?", help="Tag to be edited")
|
|
180
|
+
edit_parser.add_argument("--uri", required=False, help="Path or URI of the media file")
|
|
181
|
+
edit_parser.add_argument("--track", required=False, help="Name of the track")
|
|
182
|
+
edit_parser.add_argument("--artist", required=False, help="Name of the artist or band")
|
|
183
|
+
edit_parser.add_argument("--album", required=False, help="Name of the album")
|
|
184
|
+
edit_parser.add_argument("--opts", required=False, help="Playback options for the discs")
|
|
185
|
+
|
|
186
|
+
get_parser = subparsers.add_parser("get", help="Get a disc by tag ID")
|
|
187
|
+
add_from_current_arg(get_parser)
|
|
188
|
+
get_parser.add_argument("tag", nargs="?", help="Tag to retrieve")
|
|
189
|
+
|
|
190
|
+
search_parser = subparsers.add_parser("search", help="Search discs by query")
|
|
191
|
+
search_parser.add_argument("query", help="Search query (matches artist, album, track, playlist, or tag)")
|
|
192
|
+
|
|
193
|
+
# API commands
|
|
194
|
+
api_parser = subparsers.add_parser("api", help="Start an API server")
|
|
195
|
+
api_parser.add_argument("--port", type=int, default=None, help="override the configured API port")
|
|
196
|
+
|
|
197
|
+
# UI commands
|
|
198
|
+
ui_parser = subparsers.add_parser("ui", help="Start an UI server")
|
|
199
|
+
ui_parser.add_argument("--port", type=int, default=None, help="override the configured UI port")
|
|
200
|
+
|
|
201
|
+
# Interactive commands
|
|
202
|
+
_ = subparsers.add_parser("interactive", help="Run interactive CLI")
|
|
203
|
+
|
|
204
|
+
settings_parser = subparsers.add_parser("settings", help="Inspect application settings")
|
|
205
|
+
settings_subparsers = settings_parser.add_subparsers(dest="settings_command", required=True)
|
|
206
|
+
settings_show_parser = settings_subparsers.add_parser("show", help="Show persisted settings")
|
|
207
|
+
settings_show_parser.add_argument(
|
|
208
|
+
"--effective",
|
|
209
|
+
action="store_true",
|
|
210
|
+
help="show merged effective settings with provenance",
|
|
211
|
+
)
|
|
212
|
+
settings_show_parser.add_argument(
|
|
213
|
+
"--json",
|
|
214
|
+
dest="json_output",
|
|
215
|
+
action="store_true",
|
|
216
|
+
help="print the raw machine-readable payload",
|
|
217
|
+
)
|
|
218
|
+
settings_set_parser = settings_subparsers.add_parser("set", help="Set a persisted setting override")
|
|
219
|
+
settings_set_parser.add_argument("dotted_path", help="canonical dotted path to update")
|
|
220
|
+
settings_set_parser.add_argument("value", help="value to persist for the given path")
|
|
221
|
+
settings_set_parser.add_argument(
|
|
222
|
+
"--json",
|
|
223
|
+
dest="json_output",
|
|
224
|
+
action="store_true",
|
|
225
|
+
help="print the raw machine-readable payload",
|
|
226
|
+
)
|
|
227
|
+
settings_reset_parser = settings_subparsers.add_parser("reset", help="Remove a persisted setting override")
|
|
228
|
+
settings_reset_parser.add_argument("dotted_path", help="canonical dotted path to reset")
|
|
229
|
+
settings_reset_parser.add_argument(
|
|
230
|
+
"--json",
|
|
231
|
+
dest="json_output",
|
|
232
|
+
action="store_true",
|
|
233
|
+
help="print the raw machine-readable payload",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
args = parser.parse_args()
|
|
237
|
+
|
|
238
|
+
# Build command config
|
|
239
|
+
try:
|
|
240
|
+
command = (
|
|
241
|
+
_build_admin_command(args)
|
|
242
|
+
if args.command in {"api", "ui", "settings"}
|
|
243
|
+
else _build_library_command(args.command, args)
|
|
244
|
+
)
|
|
245
|
+
config = DiscStoreConfig(library=args.library, verbose=args.verbose, command=command)
|
|
246
|
+
except (ValidationError, ValueError) as err:
|
|
247
|
+
LOGGER.error("Config error: %s", err)
|
|
248
|
+
exit(1)
|
|
249
|
+
|
|
250
|
+
return config
|