gukebox 1.0.0.dev13__tar.gz → 1.0.1__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.
Files changed (123) hide show
  1. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/PKG-INFO +19 -21
  2. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/README.md +10 -7
  3. {gukebox-1.0.0.dev13/discstore/adapters/inbound → gukebox-1.0.1/jukebox/adapters/inbound/admin}/api/current_tag_router.py +13 -13
  4. {gukebox-1.0.0.dev13/discstore/adapters/inbound → gukebox-1.0.1/jukebox/adapters/inbound/admin}/api/discs_router.py +9 -11
  5. gukebox-1.0.1/jukebox/adapters/inbound/admin/api/models.py +48 -0
  6. {gukebox-1.0.0.dev13/discstore/adapters/inbound → gukebox-1.0.1/jukebox/adapters/inbound/admin}/api/settings_router.py +6 -6
  7. {gukebox-1.0.0.dev13/discstore/adapters/inbound → gukebox-1.0.1/jukebox/adapters/inbound/admin}/api_controller.py +13 -15
  8. {gukebox-1.0.0.dev13/discstore/adapters/inbound → gukebox-1.0.1/jukebox/adapters/inbound/admin}/cli_controller.py +26 -28
  9. {gukebox-1.0.0.dev13/discstore/adapters/inbound → gukebox-1.0.1/jukebox/adapters/inbound/admin}/cli_display.py +3 -5
  10. {gukebox-1.0.0.dev13/discstore/adapters/inbound → gukebox-1.0.1/jukebox/adapters/inbound/admin}/interactive_cli_controller.py +27 -27
  11. {gukebox-1.0.0.dev13/discstore/adapters/inbound → gukebox-1.0.1/jukebox/adapters/inbound/admin}/ui_controller.py +48 -197
  12. {gukebox-1.0.0.dev13/discstore/adapters/inbound → gukebox-1.0.1/jukebox/adapters/inbound/admin}/ui_pages/library.py +20 -20
  13. {gukebox-1.0.0.dev13/discstore/adapters/inbound → gukebox-1.0.1/jukebox/adapters/inbound/admin}/ui_pages/settings.py +10 -10
  14. {gukebox-1.0.0.dev13/discstore/adapters/inbound → gukebox-1.0.1/jukebox/adapters/inbound/admin}/ui_pages/sonos.py +24 -25
  15. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/adapters/inbound/config.py +11 -11
  16. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/adapters/outbound/json_library_adapter.py +5 -6
  17. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/adapters/outbound/players/sonos_player_adapter.py +5 -6
  18. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/adapters/outbound/readers/dryrun_reader_adapter.py +1 -2
  19. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/adapters/outbound/readers/pn532_reader_adapter.py +4 -7
  20. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/adapters/outbound/sonos_discovery_adapter.py +7 -7
  21. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/adapters/outbound/text_current_tag_adapter.py +3 -4
  22. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/admin/app.py +37 -31
  23. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/admin/cli_presentation.py +43 -46
  24. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/admin/command_handlers.py +11 -10
  25. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/admin/commands.py +17 -17
  26. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/admin/di_container.py +49 -16
  27. gukebox-1.0.0.dev13/discstore/command_handlers.py → gukebox-1.0.1/jukebox/admin/library_command_handlers.py +3 -2
  28. gukebox-1.0.0.dev13/discstore/commands.py → gukebox-1.0.1/jukebox/admin/library_commands.py +12 -12
  29. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/admin/pn532_command_handlers.py +8 -7
  30. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/admin/pn532_commands.py +2 -2
  31. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/di_container.py +29 -27
  32. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/entities/__init__.py +2 -0
  33. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/entities/current_tag_action.py +2 -2
  34. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/entities/disc.py +4 -6
  35. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/entities/library.py +1 -3
  36. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/entities/playback_action.py +2 -2
  37. gukebox-1.0.1/jukebox/domain/entities/playback_session.py +17 -0
  38. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/entities/tag_event.py +1 -3
  39. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/ports/reader_port.py +1 -2
  40. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/repositories/current_tag_repository.py +1 -2
  41. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/repositories/library_repository.py +1 -2
  42. gukebox-1.0.1/jukebox/domain/use_cases/handle_tag_event.py +145 -0
  43. {gukebox-1.0.0.dev13/discstore/domain/use_cases → gukebox-1.0.1/jukebox/domain/use_cases/library}/add_disc.py +2 -2
  44. {gukebox-1.0.0.dev13/discstore/domain/use_cases → gukebox-1.0.1/jukebox/domain/use_cases/library}/edit_disc.py +5 -7
  45. {gukebox-1.0.0.dev13/discstore/domain/use_cases → gukebox-1.0.1/jukebox/domain/use_cases/library}/get_current_tag_status.py +3 -5
  46. {gukebox-1.0.0.dev13/discstore/domain/use_cases → gukebox-1.0.1/jukebox/domain/use_cases/library}/get_disc.py +2 -2
  47. gukebox-1.0.1/jukebox/domain/use_cases/library/list_discs.py +10 -0
  48. {gukebox-1.0.0.dev13/discstore/domain/use_cases → gukebox-1.0.1/jukebox/domain/use_cases/library}/remove_disc.py +1 -1
  49. {gukebox-1.0.0.dev13/discstore/domain/use_cases → gukebox-1.0.1/jukebox/domain/use_cases/library}/resolve_tag_id.py +2 -4
  50. {gukebox-1.0.0.dev13/discstore/domain/use_cases → gukebox-1.0.1/jukebox/domain/use_cases/library}/search_discs.py +3 -5
  51. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/pn532/profiles.py +8 -8
  52. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/definitions.py +3 -2
  53. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/entities.py +50 -50
  54. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/file_settings_repository.py +2 -3
  55. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/migration.py +1 -2
  56. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/resolve.py +6 -6
  57. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/runtime_resolver.py +2 -2
  58. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/selected_sonos_group_repository.py +2 -4
  59. gukebox-1.0.1/jukebox/settings/types.py +4 -0
  60. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/validation_rules.py +3 -2
  61. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/view_utils.py +2 -2
  62. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/sonos/selection.py +8 -8
  63. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/sonos/service.py +3 -3
  64. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/pyproject.toml +15 -26
  65. gukebox-1.0.0.dev13/discstore/adapters/inbound/api/__init__.py +0 -25
  66. gukebox-1.0.0.dev13/discstore/adapters/inbound/api/models.py +0 -48
  67. gukebox-1.0.0.dev13/discstore/adapters/outbound/json_library_adapter.py +0 -3
  68. gukebox-1.0.0.dev13/discstore/adapters/outbound/text_current_tag_adapter.py +0 -3
  69. gukebox-1.0.0.dev13/discstore/di_container.py +0 -42
  70. gukebox-1.0.0.dev13/discstore/domain/entities/__init__.py +0 -6
  71. gukebox-1.0.0.dev13/discstore/domain/repositories/__init__.py +0 -4
  72. gukebox-1.0.0.dev13/discstore/domain/use_cases/__init__.py +0 -19
  73. gukebox-1.0.0.dev13/discstore/domain/use_cases/list_discs.py +0 -12
  74. gukebox-1.0.0.dev13/jukebox/domain/__init__.py +0 -0
  75. gukebox-1.0.0.dev13/jukebox/domain/entities/playback_session.py +0 -19
  76. gukebox-1.0.0.dev13/jukebox/domain/use_cases/handle_tag_event.py +0 -143
  77. gukebox-1.0.0.dev13/jukebox/settings/types.py +0 -4
  78. gukebox-1.0.0.dev13/jukebox/shared/__init__.py +0 -0
  79. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/LICENSE +0 -0
  80. {gukebox-1.0.0.dev13/discstore → gukebox-1.0.1/jukebox}/__init__.py +0 -0
  81. {gukebox-1.0.0.dev13/discstore → gukebox-1.0.1/jukebox}/adapters/__init__.py +0 -0
  82. {gukebox-1.0.0.dev13/discstore → gukebox-1.0.1/jukebox}/adapters/inbound/__init__.py +0 -0
  83. {gukebox-1.0.0.dev13/discstore/adapters/inbound/ui_pages → gukebox-1.0.1/jukebox/adapters/inbound/admin}/__init__.py +0 -0
  84. {gukebox-1.0.0.dev13/discstore/adapters/outbound → gukebox-1.0.1/jukebox/adapters/inbound/admin/api}/__init__.py +0 -0
  85. {gukebox-1.0.0.dev13/discstore/domain → gukebox-1.0.1/jukebox/adapters/inbound/admin/ui_pages}/__init__.py +0 -0
  86. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/adapters/inbound/cli_controller.py +0 -0
  87. {gukebox-1.0.0.dev13/jukebox → gukebox-1.0.1/jukebox/adapters/outbound}/__init__.py +0 -0
  88. {gukebox-1.0.0.dev13/jukebox/adapters → gukebox-1.0.1/jukebox/adapters/outbound/players}/__init__.py +0 -0
  89. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/adapters/outbound/players/dryrun_player_adapter.py +0 -0
  90. {gukebox-1.0.0.dev13/jukebox/adapters/inbound → gukebox-1.0.1/jukebox/adapters/outbound/readers}/__init__.py +0 -0
  91. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/admin/__init__.py +0 -0
  92. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/admin/services.py +0 -0
  93. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/admin/sonos_households.py +0 -0
  94. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/app.py +0 -0
  95. {gukebox-1.0.0.dev13/jukebox/adapters/outbound → gukebox-1.0.1/jukebox/domain}/__init__.py +0 -0
  96. {gukebox-1.0.0.dev13/discstore → gukebox-1.0.1/jukebox}/domain/entities/current_tag_status.py +0 -0
  97. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/errors.py +0 -0
  98. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/ports/__init__.py +0 -0
  99. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/ports/player_port.py +0 -0
  100. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/repositories/__init__.py +0 -0
  101. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/use_cases/__init__.py +0 -0
  102. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/use_cases/determine_action.py +0 -0
  103. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/domain/use_cases/determine_current_tag_action.py +0 -0
  104. {gukebox-1.0.0.dev13/jukebox/adapters/outbound/players → gukebox-1.0.1/jukebox/domain/use_cases/library}/__init__.py +0 -0
  105. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/pn532/__init__.py +0 -0
  106. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/__init__.py +0 -0
  107. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/dict_utils.py +0 -0
  108. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/errors.py +0 -0
  109. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/repositories.py +0 -0
  110. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/runtime_validation.py +0 -0
  111. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/service_protocols.py +0 -0
  112. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/settings/timing_validation.py +0 -0
  113. {gukebox-1.0.0.dev13/jukebox/adapters/outbound/readers → gukebox-1.0.1/jukebox/shared}/__init__.py +0 -0
  114. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/shared/config_utils.py +0 -0
  115. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/shared/dependency_messages.py +0 -0
  116. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/shared/logger.py +0 -0
  117. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/shared/terminal_ui.py +0 -0
  118. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/shared/timing.py +0 -0
  119. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/sonos/__init__.py +0 -0
  120. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/jukebox/sonos/discovery.py +0 -0
  121. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/pn532/__init__.py +0 -0
  122. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/pn532/pn532.py +0 -0
  123. {gukebox-1.0.0.dev13 → gukebox-1.0.1}/pn532/spi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: gukebox
3
- Version: 1.0.0.dev13
3
+ Version: 1.0.1
4
4
  Summary: A Jukebox to play music on speakers using 'CD' with NFC tag
5
5
  Keywords: jukebox,music,nfc
6
6
  Author: Gudsfile
@@ -29,27 +29,22 @@ Classifier: Development Status :: 4 - Beta
29
29
  Classifier: License :: OSI Approved :: MIT License
30
30
  Classifier: Operating System :: OS Independent
31
31
  Classifier: Programming Language :: Python :: 3 :: Only
32
- Classifier: Programming Language :: Python :: 3.9
33
- Classifier: Programming Language :: Python :: 3.10
34
32
  Classifier: Programming Language :: Python :: 3.11
35
33
  Classifier: Programming Language :: Python :: 3.12
36
34
  Classifier: Programming Language :: Python :: 3.13
37
- Requires-Dist: pydantic==2.12.5
35
+ Requires-Dist: pydantic==2.13.3
38
36
  Requires-Dist: questionary==2.1.1
39
- Requires-Dist: soco==0.30.14
40
- Requires-Dist: typer==0.23.2 ; python_full_version < '3.10'
41
- Requires-Dist: typer==0.24.1 ; python_full_version >= '3.10'
42
- Requires-Dist: fastapi==0.128.7 ; python_full_version < '3.10' and extra == 'api'
43
- Requires-Dist: fastapi==0.135.1 ; python_full_version >= '3.10' and extra == 'api'
44
- Requires-Dist: uvicorn==0.39.0 ; python_full_version < '3.10' and extra == 'api'
45
- Requires-Dist: uvicorn==0.41.0 ; python_full_version >= '3.10' and extra == 'api'
37
+ Requires-Dist: soco==0.31.0
38
+ Requires-Dist: typer==0.24.1
39
+ Requires-Dist: fastapi==0.135.1 ; extra == 'api'
40
+ Requires-Dist: uvicorn==0.41.0 ; extra == 'api'
46
41
  Requires-Dist: pyserial==3.5 ; extra == 'pn532'
47
42
  Requires-Dist: spidev==3.8 ; extra == 'pn532'
48
43
  Requires-Dist: lgpio==0.2.2.0 ; python_full_version < '3.13' and extra == 'pn532'
49
44
  Requires-Dist: gukebox[api] ; extra == 'ui'
50
- Requires-Dist: fastui==0.9.0 ; python_full_version >= '3.10' and extra == 'ui'
51
- Requires-Dist: python-multipart==0.0.22 ; python_full_version >= '3.10' and extra == 'ui'
52
- Requires-Python: >=3.9, <3.14
45
+ Requires-Dist: fastui==0.9.0 ; extra == 'ui'
46
+ Requires-Dist: python-multipart==0.0.27 ; extra == 'ui'
47
+ Requires-Python: >=3.11, <3.14
53
48
  Project-URL: Repository, https://github.com/Gudsfile/jukebox
54
49
  Provides-Extra: api
55
50
  Provides-Extra: pn532
@@ -91,13 +86,17 @@ Description-Content-Type: text/markdown
91
86
  - [The library file](#the-library-file)
92
87
  - [Developer setup](#developer-setup)
93
88
 
94
- ## Notes
89
+ ## Python Compatibility
95
90
 
96
- Python 3.7 is supported by Jukebox up to version 0.4.1.
91
+ Jukebox 1.0+ requires Python 3.11 or newer.
97
92
 
98
- Python 3.8 is supported by Jukebox up to version 0.5.4.
99
-
100
- The `ui` extension is only available for Python versions 3.10 and above.
93
+ | Python version | Compatible Jukebox versions | Notes |
94
+ |----------------|-----------------------------|-------|
95
+ | 3.7 | 0.4.0 0.4.1 | Legacy |
96
+ | 3.8 | 0.4.0 – 0.5.4 | Legacy |
97
+ | 3.9 – 3.10 | 0.4.0 – 0.9.0 (incl. 1.0.0.dev13) | Legacy |
98
+ | 3.11 – 3.12 | 0.4.0 – latest | Actively supported |
99
+ | 3.13 | 0.5.3 – latest | Actively supported (see installation notes) |
101
100
 
102
101
  ## Install
103
102
 
@@ -190,10 +189,9 @@ jukebox-admin settings show --effective
190
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:
191
190
 
192
191
  ```shell
193
- # Python 3.9+ required
194
192
  uv tool install gukebox[api]
195
193
 
196
- # Python 3.10+ required, ui includes the api extra
194
+ # ui includes the api extra
197
195
  uv tool install gukebox[ui]
198
196
  ```
199
197
 
@@ -33,13 +33,17 @@
33
33
  - [The library file](#the-library-file)
34
34
  - [Developer setup](#developer-setup)
35
35
 
36
- ## Notes
36
+ ## Python Compatibility
37
37
 
38
- Python 3.7 is supported by Jukebox up to version 0.4.1.
38
+ Jukebox 1.0+ requires Python 3.11 or newer.
39
39
 
40
- Python 3.8 is supported by Jukebox up to version 0.5.4.
41
-
42
- The `ui` extension is only available for Python versions 3.10 and above.
40
+ | Python version | Compatible Jukebox versions | Notes |
41
+ |----------------|-----------------------------|-------|
42
+ | 3.7 | 0.4.0 0.4.1 | Legacy |
43
+ | 3.8 | 0.4.0 – 0.5.4 | Legacy |
44
+ | 3.9 – 3.10 | 0.4.0 – 0.9.0 (incl. 1.0.0.dev13) | Legacy |
45
+ | 3.11 – 3.12 | 0.4.0 – latest | Actively supported |
46
+ | 3.13 | 0.5.3 – latest | Actively supported (see installation notes) |
43
47
 
44
48
  ## Install
45
49
 
@@ -132,10 +136,9 @@ jukebox-admin settings show --effective
132
136
  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:
133
137
 
134
138
  ```shell
135
- # Python 3.9+ required
136
139
  uv tool install gukebox[api]
137
140
 
138
- # Python 3.10+ required, ui includes the api extra
141
+ # ui includes the api extra
139
142
  uv tool install gukebox[ui]
140
143
  ```
141
144
 
@@ -1,21 +1,21 @@
1
- from typing import Any, Optional
1
+ from typing import Any
2
2
 
3
3
  from fastapi import APIRouter, HTTPException, Response, status
4
4
  from pydantic import ValidationError
5
5
 
6
- from discstore.adapters.inbound.api.models import (
6
+ from jukebox.adapters.inbound.admin.api.models import (
7
7
  CurrentTagDiscOutput,
8
8
  CurrentTagStatusOutput,
9
9
  DiscInput,
10
10
  DiscOutput,
11
11
  DiscPatchInput,
12
12
  )
13
- from discstore.domain.entities import CurrentTagStatus, Disc, DiscMetadata, DiscOption
14
- from discstore.domain.use_cases.add_disc import AddDisc
15
- from discstore.domain.use_cases.edit_disc import EditDisc
16
- from discstore.domain.use_cases.get_current_tag_status import GetCurrentTagStatus
17
- from discstore.domain.use_cases.get_disc import GetDisc
18
- from discstore.domain.use_cases.remove_disc import RemoveDisc
13
+ from jukebox.domain.entities import CurrentTagStatus, Disc, DiscMetadata, DiscOption
14
+ from jukebox.domain.use_cases.library.add_disc import AddDisc
15
+ from jukebox.domain.use_cases.library.edit_disc import EditDisc
16
+ from jukebox.domain.use_cases.library.get_current_tag_status import GetCurrentTagStatus
17
+ from jukebox.domain.use_cases.library.get_disc import GetDisc
18
+ from jukebox.domain.use_cases.library.remove_disc import RemoveDisc
19
19
 
20
20
 
21
21
  def build_current_tag_router(
@@ -27,11 +27,11 @@ def build_current_tag_router(
27
27
  ) -> APIRouter:
28
28
  router = APIRouter(prefix="/api/v1", tags=["current-tag"])
29
29
 
30
- def read_current_tag_status() -> Optional[CurrentTagStatus]:
30
+ def read_current_tag_status() -> CurrentTagStatus | None:
31
31
  return get_current_tag_status.execute()
32
32
 
33
33
  def ensure_expected_tag_id_matches(
34
- expected_tag_id: Optional[str], current_tag_status: Optional[CurrentTagStatus]
34
+ expected_tag_id: str | None, current_tag_status: CurrentTagStatus | None
35
35
  ) -> None:
36
36
  if expected_tag_id is None:
37
37
  return
@@ -84,7 +84,7 @@ def build_current_tag_router(
84
84
  )
85
85
  def create_current_tag_disc(
86
86
  disc: DiscInput,
87
- expected_tag_id: Optional[str] = None,
87
+ expected_tag_id: str | None = None,
88
88
  ) -> Any:
89
89
  current_tag_status = read_current_tag_status()
90
90
  ensure_expected_tag_id_matches(expected_tag_id, current_tag_status)
@@ -112,7 +112,7 @@ def build_current_tag_router(
112
112
  )
113
113
  def update_current_tag_disc(
114
114
  disc_patch: DiscPatchInput,
115
- expected_tag_id: Optional[str] = None,
115
+ expected_tag_id: str | None = None,
116
116
  ) -> Any:
117
117
  current_tag_status = read_current_tag_status()
118
118
  ensure_expected_tag_id_matches(expected_tag_id, current_tag_status)
@@ -147,7 +147,7 @@ def build_current_tag_router(
147
147
  },
148
148
  summary="Delete the current tag disc",
149
149
  )
150
- def delete_current_tag_disc(expected_tag_id: Optional[str] = None) -> Response:
150
+ def delete_current_tag_disc(expected_tag_id: str | None = None) -> Response:
151
151
  current_tag_status = read_current_tag_status()
152
152
  ensure_expected_tag_id_matches(expected_tag_id, current_tag_status)
153
153
  if current_tag_status is None:
@@ -1,15 +1,13 @@
1
- from typing import Dict
2
-
3
1
  from fastapi import APIRouter, HTTPException, Response, status
4
2
  from pydantic import ValidationError
5
3
 
6
- from discstore.adapters.inbound.api.models import DiscInput, DiscOutput, DiscPatchInput
7
- from discstore.domain.entities import Disc, DiscMetadata, DiscOption
8
- from discstore.domain.use_cases.add_disc import AddDisc
9
- from discstore.domain.use_cases.edit_disc import EditDisc
10
- from discstore.domain.use_cases.get_disc import GetDisc
11
- from discstore.domain.use_cases.list_discs import ListDiscs
12
- from discstore.domain.use_cases.remove_disc import RemoveDisc
4
+ from jukebox.adapters.inbound.admin.api.models import DiscInput, DiscOutput, DiscPatchInput
5
+ from jukebox.domain.entities import Disc, DiscMetadata, DiscOption
6
+ from jukebox.domain.use_cases.library.add_disc import AddDisc
7
+ from jukebox.domain.use_cases.library.edit_disc import EditDisc
8
+ from jukebox.domain.use_cases.library.get_disc import GetDisc
9
+ from jukebox.domain.use_cases.library.list_discs import ListDiscs
10
+ from jukebox.domain.use_cases.library.remove_disc import RemoveDisc
13
11
 
14
12
 
15
13
  def build_discs_router(
@@ -21,8 +19,8 @@ def build_discs_router(
21
19
  ) -> APIRouter:
22
20
  router = APIRouter(prefix="/api/v1", tags=["discs"])
23
21
 
24
- @router.get("/discs", response_model=Dict[str, DiscOutput], summary="List discs")
25
- def list_discs_route() -> Dict[str, Disc]:
22
+ @router.get("/discs", response_model=dict[str, DiscOutput], summary="List discs")
23
+ def list_discs_route() -> dict[str, Disc]:
26
24
  return list_discs.execute()
27
25
 
28
26
  @router.get("/discs/{tag_id}", response_model=DiscOutput, summary="Get a disc")
@@ -0,0 +1,48 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, RootModel
4
+
5
+ from jukebox.domain.entities import CurrentTagStatus, Disc
6
+
7
+
8
+ class DiscInput(Disc):
9
+ pass
10
+
11
+
12
+ class DiscOutput(Disc):
13
+ pass
14
+
15
+
16
+ class DiscPatchMetadataInput(BaseModel):
17
+ artist: str | None = None
18
+ album: str | None = None
19
+ track: str | None = None
20
+ playlist: str | None = None
21
+
22
+
23
+ class DiscPatchOptionInput(BaseModel):
24
+ shuffle: bool | None = None
25
+ is_test: bool | None = None
26
+
27
+
28
+ class DiscPatchInput(BaseModel):
29
+ uri: str | None = None
30
+ metadata: DiscPatchMetadataInput | None = None
31
+ option: DiscPatchOptionInput | None = None
32
+
33
+
34
+ class CurrentTagStatusOutput(CurrentTagStatus):
35
+ pass
36
+
37
+
38
+ class CurrentTagDiscOutput(BaseModel):
39
+ tag_id: str
40
+ disc: DiscOutput
41
+
42
+
43
+ class SettingsResetInput(BaseModel):
44
+ path: str
45
+
46
+
47
+ class SettingsPatchInput(RootModel[dict[str, Any]]):
48
+ pass
@@ -1,8 +1,8 @@
1
- from typing import Any, Dict, cast
1
+ from typing import Any, cast
2
2
 
3
3
  from fastapi import APIRouter, HTTPException
4
4
 
5
- from discstore.adapters.inbound.api.models import SettingsPatchInput, SettingsResetInput
5
+ from jukebox.adapters.inbound.admin.api.models import SettingsPatchInput, SettingsResetInput
6
6
  from jukebox.settings.errors import SettingsError
7
7
  from jukebox.settings.service_protocols import SettingsService
8
8
  from jukebox.settings.types import JsonObject
@@ -11,21 +11,21 @@ from jukebox.settings.types import JsonObject
11
11
  def build_settings_router(settings_service: SettingsService) -> APIRouter:
12
12
  router = APIRouter(prefix="/api/v1", tags=["settings"])
13
13
 
14
- @router.get("/settings", response_model=Dict[str, Any], summary="Get persisted settings")
14
+ @router.get("/settings", response_model=dict[str, Any], summary="Get persisted settings")
15
15
  def get_settings() -> JsonObject:
16
16
  try:
17
17
  return settings_service.get_persisted_settings_view()
18
18
  except Exception as err:
19
19
  raise HTTPException(status_code=500, detail=f"Server error: {str(err)}")
20
20
 
21
- @router.get("/settings/effective", response_model=Dict[str, Any], summary="Get effective settings")
21
+ @router.get("/settings/effective", response_model=dict[str, Any], summary="Get effective settings")
22
22
  def get_effective_settings() -> JsonObject:
23
23
  try:
24
24
  return settings_service.get_effective_settings_view()
25
25
  except Exception as err:
26
26
  raise HTTPException(status_code=500, detail=f"Server error: {str(err)}")
27
27
 
28
- @router.patch("/settings", response_model=Dict[str, Any], summary="Patch persisted settings")
28
+ @router.patch("/settings", response_model=dict[str, Any], summary="Patch persisted settings")
29
29
  def patch_settings(patch: SettingsPatchInput) -> JsonObject:
30
30
  try:
31
31
  return settings_service.patch_persisted_settings(cast(JsonObject, patch.root))
@@ -34,7 +34,7 @@ def build_settings_router(settings_service: SettingsService) -> APIRouter:
34
34
  except Exception as err:
35
35
  raise HTTPException(status_code=500, detail=f"Server error: {str(err)}")
36
36
 
37
- @router.post("/settings/reset", response_model=Dict[str, Any], summary="Reset a persisted setting")
37
+ @router.post("/settings/reset", response_model=dict[str, Any], summary="Reset a persisted setting")
38
38
  def reset_settings(payload: SettingsResetInput) -> JsonObject:
39
39
  try:
40
40
  return settings_service.reset_persisted_value(payload.path)
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  from pydantic import BaseModel
4
2
 
5
3
  from jukebox.shared.dependency_messages import optional_extra_dependency_message
@@ -7,9 +5,9 @@ from jukebox.shared.dependency_messages import optional_extra_dependency_message
7
5
  try:
8
6
  from fastapi import FastAPI, HTTPException
9
7
 
10
- from discstore.adapters.inbound.api.current_tag_router import build_current_tag_router
11
- from discstore.adapters.inbound.api.discs_router import build_discs_router
12
- from discstore.adapters.inbound.api.models import (
8
+ from jukebox.adapters.inbound.admin.api.current_tag_router import build_current_tag_router
9
+ from jukebox.adapters.inbound.admin.api.discs_router import build_discs_router
10
+ from jukebox.adapters.inbound.admin.api.models import (
13
11
  CurrentTagDiscOutput,
14
12
  CurrentTagStatusOutput,
15
13
  DiscInput,
@@ -18,19 +16,19 @@ try:
18
16
  SettingsPatchInput,
19
17
  SettingsResetInput,
20
18
  )
21
- from discstore.adapters.inbound.api.settings_router import build_settings_router
19
+ from jukebox.adapters.inbound.admin.api.settings_router import build_settings_router
22
20
  except ModuleNotFoundError as e:
23
21
  if e.name != "fastapi":
24
22
  raise
25
23
  raise ModuleNotFoundError(
26
24
  optional_extra_dependency_message("The `api_controller` module", "api", "jukebox-admin api")
27
25
  ) from e
28
- from discstore.domain.use_cases.add_disc import AddDisc
29
- from discstore.domain.use_cases.edit_disc import EditDisc
30
- from discstore.domain.use_cases.get_current_tag_status import GetCurrentTagStatus
31
- from discstore.domain.use_cases.get_disc import GetDisc
32
- from discstore.domain.use_cases.list_discs import ListDiscs
33
- from discstore.domain.use_cases.remove_disc import RemoveDisc
26
+ from jukebox.domain.use_cases.library.add_disc import AddDisc
27
+ from jukebox.domain.use_cases.library.edit_disc import EditDisc
28
+ from jukebox.domain.use_cases.library.get_current_tag_status import GetCurrentTagStatus
29
+ from jukebox.domain.use_cases.library.get_disc import GetDisc
30
+ from jukebox.domain.use_cases.library.list_discs import ListDiscs
31
+ from jukebox.domain.use_cases.library.remove_disc import RemoveDisc
34
32
  from jukebox.settings.entities import SelectedSonosGroupSettings
35
33
  from jukebox.settings.selected_sonos_group_repository import SettingsSelectedSonosGroupRepository
36
34
  from jukebox.settings.service_protocols import SettingsService
@@ -62,7 +60,7 @@ class SelectedSonosGroupOutput(SelectedSonosGroupSettings):
62
60
  class SonosSelectionMemberAvailabilityOutput(BaseModel):
63
61
  uid: str
64
62
  status: str
65
- speaker: Optional[SonosSpeakerOutput] = None
63
+ speaker: SonosSpeakerOutput | None = None
66
64
 
67
65
 
68
66
  class SonosSelectionAvailabilityOutput(BaseModel):
@@ -71,13 +69,13 @@ class SonosSelectionAvailabilityOutput(BaseModel):
71
69
 
72
70
 
73
71
  class SonosSelectionOutput(BaseModel):
74
- selected_group: Optional[SelectedSonosGroupOutput] = None
72
+ selected_group: SelectedSonosGroupOutput | None = None
75
73
  availability: SonosSelectionAvailabilityOutput
76
74
 
77
75
 
78
76
  class SonosSelectionInput(BaseModel):
79
77
  uids: list[str]
80
- coordinator_uid: Optional[str] = None
78
+ coordinator_uid: str | None = None
81
79
 
82
80
 
83
81
  class SonosSelectionUpdateOutput(BaseModel):
@@ -1,8 +1,7 @@
1
1
  import logging
2
- from typing import Union
3
2
 
4
- from discstore.adapters.inbound.cli_display import display_library_line, display_library_table
5
- from discstore.commands import (
3
+ from jukebox.adapters.inbound.admin.cli_display import display_library_line, display_library_table
4
+ from jukebox.admin.library_commands import (
6
5
  CliAddCommand,
7
6
  CliEditCommand,
8
7
  CliGetCommand,
@@ -10,14 +9,14 @@ from discstore.commands import (
10
9
  CliRemoveCommand,
11
10
  CliSearchCommand,
12
11
  )
13
- from discstore.domain.entities import Disc, DiscMetadata, DiscOption
14
- from discstore.domain.use_cases.add_disc import AddDisc
15
- from discstore.domain.use_cases.edit_disc import EditDisc
16
- from discstore.domain.use_cases.get_disc import GetDisc
17
- from discstore.domain.use_cases.list_discs import ListDiscs
18
- from discstore.domain.use_cases.remove_disc import RemoveDisc
19
- from discstore.domain.use_cases.resolve_tag_id import ResolveTagId
20
- from discstore.domain.use_cases.search_discs import SearchDiscs
12
+ from jukebox.domain.entities import Disc, DiscMetadata, DiscOption
13
+ from jukebox.domain.use_cases.library.add_disc import AddDisc
14
+ from jukebox.domain.use_cases.library.edit_disc import EditDisc
15
+ from jukebox.domain.use_cases.library.get_disc import GetDisc
16
+ from jukebox.domain.use_cases.library.list_discs import ListDiscs
17
+ from jukebox.domain.use_cases.library.remove_disc import RemoveDisc
18
+ from jukebox.domain.use_cases.library.resolve_tag_id import ResolveTagId
19
+ from jukebox.domain.use_cases.library.search_discs import SearchDiscs
21
20
 
22
21
  LOGGER = logging.getLogger("discstore")
23
22
 
@@ -43,24 +42,23 @@ class CLIController:
43
42
 
44
43
  def run(
45
44
  self,
46
- command: Union[
47
- CliAddCommand, CliListCommand, CliRemoveCommand, CliEditCommand, CliGetCommand, CliSearchCommand
48
- ],
45
+ command: CliAddCommand | CliListCommand | CliRemoveCommand | CliEditCommand | CliGetCommand | CliSearchCommand,
49
46
  ) -> None:
50
- if isinstance(command, CliAddCommand):
51
- self.add_disc_flow(command)
52
- elif isinstance(command, CliListCommand):
53
- self.list_discs_flow(command)
54
- elif isinstance(command, CliRemoveCommand):
55
- self.remove_disc_flow(command)
56
- elif isinstance(command, CliEditCommand):
57
- self.edit_disc_flow(command)
58
- elif isinstance(command, CliGetCommand):
59
- self.get_disc_flow(command)
60
- elif isinstance(command, CliSearchCommand):
61
- self.search_discs_flow(command)
62
- else:
63
- LOGGER.error("Command not implemented yet: command='%s'", command)
47
+ match command:
48
+ case CliAddCommand():
49
+ self.add_disc_flow(command)
50
+ case CliListCommand():
51
+ self.list_discs_flow(command)
52
+ case CliRemoveCommand():
53
+ self.remove_disc_flow(command)
54
+ case CliEditCommand():
55
+ self.edit_disc_flow(command)
56
+ case CliGetCommand():
57
+ self.get_disc_flow(command)
58
+ case CliSearchCommand():
59
+ self.search_discs_flow(command)
60
+ case _:
61
+ LOGGER.error("Command not implemented yet: command='%s'", command)
64
62
 
65
63
  def add_disc_flow(self, command: CliAddCommand) -> None:
66
64
  tag = self.resolve_tag_id.execute(command.tag, command.use_current_tag)
@@ -1,11 +1,9 @@
1
- from typing import Dict
2
-
3
- from discstore.domain.entities import Disc
1
+ from jukebox.domain.entities import Disc
4
2
 
5
3
  MAX_COL_WIDTH = 20
6
4
 
7
5
 
8
- def display_library_line(discs: Dict[str, Disc]) -> None:
6
+ def display_library_line(discs: dict[str, Disc]) -> None:
9
7
  if not discs:
10
8
  print("The library is empty")
11
9
  return
@@ -28,7 +26,7 @@ def truncate(text: str, max_length: int) -> str:
28
26
  return text[: max_length - 3] + "..."
29
27
 
30
28
 
31
- def display_library_table(discs: Dict[str, Disc]) -> None:
29
+ def display_library_table(discs: dict[str, Disc]) -> None:
32
30
  if not discs:
33
31
  print("The library is empty")
34
32
  return
@@ -1,13 +1,12 @@
1
1
  import logging
2
- from typing import Optional
3
2
 
4
- from discstore.adapters.inbound.cli_display import display_library_line, display_library_table
5
- from discstore.domain.entities import CurrentTagStatus, Disc, DiscMetadata, DiscOption
6
- from discstore.domain.use_cases.add_disc import AddDisc
7
- from discstore.domain.use_cases.edit_disc import EditDisc
8
- from discstore.domain.use_cases.get_current_tag_status import GetCurrentTagStatus
9
- from discstore.domain.use_cases.list_discs import ListDiscs
10
- from discstore.domain.use_cases.remove_disc import RemoveDisc
3
+ from jukebox.adapters.inbound.admin.cli_display import display_library_line, display_library_table
4
+ from jukebox.domain.entities import CurrentTagStatus, Disc, DiscMetadata, DiscOption
5
+ from jukebox.domain.use_cases.library.add_disc import AddDisc
6
+ from jukebox.domain.use_cases.library.edit_disc import EditDisc
7
+ from jukebox.domain.use_cases.library.get_current_tag_status import GetCurrentTagStatus
8
+ from jukebox.domain.use_cases.library.list_discs import ListDiscs
9
+ from jukebox.domain.use_cases.library.remove_disc import RemoveDisc
11
10
 
12
11
  LOGGER = logging.getLogger("discstore")
13
12
 
@@ -38,24 +37,25 @@ class InteractiveCLIController:
38
37
 
39
38
  def handle_command(self, command: str) -> None:
40
39
  try:
41
- if command == "add":
42
- self.add_disc_flow()
43
- elif command == "remove":
44
- self.remove_disc_flow()
45
- elif command == "list":
46
- self.list_discs_flow()
47
- elif command == "edit":
48
- self.edit_disc_flow()
49
- elif command == "current":
50
- self.current_tag_flow()
51
- elif command == "exit":
52
- print("See you soon!")
53
- exit(0)
54
- elif command == "help":
55
- print(self.help_message)
56
- else:
57
- print(f"Invalid command `{command}`")
58
- print(self.help_message)
40
+ match command:
41
+ case "add":
42
+ self.add_disc_flow()
43
+ case "remove":
44
+ self.remove_disc_flow()
45
+ case "list":
46
+ self.list_discs_flow()
47
+ case "edit":
48
+ self.edit_disc_flow()
49
+ case "current":
50
+ self.current_tag_flow()
51
+ case "exit":
52
+ print("See you soon!")
53
+ exit(0)
54
+ case "help":
55
+ print(self.help_message)
56
+ case _:
57
+ print(f"Invalid command `{command}`")
58
+ print(self.help_message)
59
59
  except Exception as err:
60
60
  print(f"Error: {err}")
61
61
  LOGGER.error("Error during handling command: %s", err)
@@ -112,7 +112,7 @@ class InteractiveCLIController:
112
112
  print(f"Tag ID : {current_tag_status.tag_id}")
113
113
  print(f"Known in library : {'yes' if current_tag_status.known_in_library else 'no'}")
114
114
 
115
- def _prompt_for_tag(self, current_tag_status: Optional[CurrentTagStatus], action: str) -> str:
115
+ def _prompt_for_tag(self, current_tag_status: CurrentTagStatus | None, action: str) -> str:
116
116
  default_tag = ""
117
117
  if current_tag_status is not None and (
118
118
  (action == "add" and not current_tag_status.known_in_library)