gukebox 1.0.0.dev14__tar.gz → 1.0.2__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 (118) hide show
  1. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/PKG-INFO +2 -2
  2. {gukebox-1.0.0.dev14/discstore/adapters/inbound → gukebox-1.0.2/jukebox/adapters/inbound/admin}/api/current_tag_router.py +7 -7
  3. {gukebox-1.0.0.dev14/discstore/adapters/inbound → gukebox-1.0.2/jukebox/adapters/inbound/admin}/api/discs_router.py +7 -7
  4. {gukebox-1.0.0.dev14/discstore/adapters/inbound → gukebox-1.0.2/jukebox/adapters/inbound/admin}/api/models.py +1 -1
  5. {gukebox-1.0.0.dev14/discstore/adapters/inbound → gukebox-1.0.2/jukebox/adapters/inbound/admin}/api/settings_router.py +1 -1
  6. {gukebox-1.0.0.dev14/discstore/adapters/inbound → gukebox-1.0.2/jukebox/adapters/inbound/admin}/api_controller.py +10 -10
  7. {gukebox-1.0.0.dev14/discstore/adapters/inbound → gukebox-1.0.2/jukebox/adapters/inbound/admin}/cli_controller.py +10 -10
  8. {gukebox-1.0.0.dev14/discstore/adapters/inbound → gukebox-1.0.2/jukebox/adapters/inbound/admin}/cli_display.py +1 -1
  9. {gukebox-1.0.0.dev14/discstore/adapters/inbound → gukebox-1.0.2/jukebox/adapters/inbound/admin}/interactive_cli_controller.py +7 -7
  10. {gukebox-1.0.0.dev14/discstore/adapters/inbound → gukebox-1.0.2/jukebox/adapters/inbound/admin}/ui_controller.py +34 -177
  11. {gukebox-1.0.0.dev14/discstore/adapters/inbound → gukebox-1.0.2/jukebox/adapters/inbound/admin}/ui_pages/library.py +4 -4
  12. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/admin/app.py +10 -4
  13. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/admin/di_container.py +45 -10
  14. gukebox-1.0.0.dev14/discstore/command_handlers.py → gukebox-1.0.2/jukebox/admin/library_command_handlers.py +1 -1
  15. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/entities/__init__.py +2 -0
  16. {gukebox-1.0.0.dev14/discstore/domain/use_cases → gukebox-1.0.2/jukebox/domain/use_cases/library}/add_disc.py +2 -2
  17. {gukebox-1.0.0.dev14/discstore/domain/use_cases → gukebox-1.0.2/jukebox/domain/use_cases/library}/edit_disc.py +2 -2
  18. {gukebox-1.0.0.dev14/discstore/domain/use_cases → gukebox-1.0.2/jukebox/domain/use_cases/library}/get_current_tag_status.py +2 -2
  19. {gukebox-1.0.0.dev14/discstore/domain/use_cases → gukebox-1.0.2/jukebox/domain/use_cases/library}/get_disc.py +2 -2
  20. {gukebox-1.0.0.dev14/discstore/domain/use_cases → gukebox-1.0.2/jukebox/domain/use_cases/library}/list_discs.py +2 -2
  21. {gukebox-1.0.0.dev14/discstore/domain/use_cases → gukebox-1.0.2/jukebox/domain/use_cases/library}/remove_disc.py +1 -1
  22. {gukebox-1.0.0.dev14/discstore/domain/use_cases → gukebox-1.0.2/jukebox/domain/use_cases/library}/resolve_tag_id.py +1 -1
  23. {gukebox-1.0.0.dev14/discstore/domain/use_cases → gukebox-1.0.2/jukebox/domain/use_cases/library}/search_discs.py +2 -2
  24. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/pyproject.toml +3 -3
  25. gukebox-1.0.0.dev14/discstore/adapters/inbound/api/__init__.py +0 -25
  26. gukebox-1.0.0.dev14/discstore/adapters/outbound/json_library_adapter.py +0 -3
  27. gukebox-1.0.0.dev14/discstore/adapters/outbound/text_current_tag_adapter.py +0 -3
  28. gukebox-1.0.0.dev14/discstore/di_container.py +0 -42
  29. gukebox-1.0.0.dev14/discstore/domain/entities/__init__.py +0 -6
  30. gukebox-1.0.0.dev14/discstore/domain/repositories/__init__.py +0 -4
  31. gukebox-1.0.0.dev14/discstore/domain/use_cases/__init__.py +0 -19
  32. gukebox-1.0.0.dev14/jukebox/domain/__init__.py +0 -0
  33. gukebox-1.0.0.dev14/jukebox/shared/__init__.py +0 -0
  34. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/LICENSE +0 -0
  35. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/README.md +0 -0
  36. {gukebox-1.0.0.dev14/discstore → gukebox-1.0.2/jukebox}/__init__.py +0 -0
  37. {gukebox-1.0.0.dev14/discstore → gukebox-1.0.2/jukebox}/adapters/__init__.py +0 -0
  38. {gukebox-1.0.0.dev14/discstore → gukebox-1.0.2/jukebox}/adapters/inbound/__init__.py +0 -0
  39. {gukebox-1.0.0.dev14/discstore/adapters/inbound/ui_pages → gukebox-1.0.2/jukebox/adapters/inbound/admin}/__init__.py +0 -0
  40. {gukebox-1.0.0.dev14/discstore/adapters/outbound → gukebox-1.0.2/jukebox/adapters/inbound/admin/api}/__init__.py +0 -0
  41. {gukebox-1.0.0.dev14/discstore/domain → gukebox-1.0.2/jukebox/adapters/inbound/admin/ui_pages}/__init__.py +0 -0
  42. {gukebox-1.0.0.dev14/discstore/adapters/inbound → gukebox-1.0.2/jukebox/adapters/inbound/admin}/ui_pages/settings.py +0 -0
  43. {gukebox-1.0.0.dev14/discstore/adapters/inbound → gukebox-1.0.2/jukebox/adapters/inbound/admin}/ui_pages/sonos.py +0 -0
  44. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/adapters/inbound/cli_controller.py +0 -0
  45. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/adapters/inbound/config.py +0 -0
  46. {gukebox-1.0.0.dev14/jukebox → gukebox-1.0.2/jukebox/adapters/outbound}/__init__.py +0 -0
  47. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/adapters/outbound/json_library_adapter.py +0 -0
  48. {gukebox-1.0.0.dev14/jukebox/adapters → gukebox-1.0.2/jukebox/adapters/outbound/players}/__init__.py +0 -0
  49. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/adapters/outbound/players/dryrun_player_adapter.py +0 -0
  50. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/adapters/outbound/players/sonos_player_adapter.py +0 -0
  51. {gukebox-1.0.0.dev14/jukebox/adapters/inbound → gukebox-1.0.2/jukebox/adapters/outbound/readers}/__init__.py +0 -0
  52. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/adapters/outbound/readers/dryrun_reader_adapter.py +0 -0
  53. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/adapters/outbound/readers/pn532_reader_adapter.py +0 -0
  54. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/adapters/outbound/sonos_discovery_adapter.py +0 -0
  55. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/adapters/outbound/text_current_tag_adapter.py +0 -0
  56. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/admin/__init__.py +0 -0
  57. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/admin/cli_presentation.py +0 -0
  58. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/admin/command_handlers.py +0 -0
  59. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/admin/commands.py +0 -0
  60. /gukebox-1.0.0.dev14/discstore/commands.py → /gukebox-1.0.2/jukebox/admin/library_commands.py +0 -0
  61. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/admin/pn532_command_handlers.py +0 -0
  62. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/admin/pn532_commands.py +0 -0
  63. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/admin/services.py +0 -0
  64. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/admin/sonos_households.py +0 -0
  65. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/app.py +0 -0
  66. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/di_container.py +0 -0
  67. {gukebox-1.0.0.dev14/jukebox/adapters/outbound → gukebox-1.0.2/jukebox/domain}/__init__.py +0 -0
  68. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/entities/current_tag_action.py +0 -0
  69. {gukebox-1.0.0.dev14/discstore → gukebox-1.0.2/jukebox}/domain/entities/current_tag_status.py +0 -0
  70. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/entities/disc.py +0 -0
  71. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/entities/library.py +0 -0
  72. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/entities/playback_action.py +0 -0
  73. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/entities/playback_session.py +0 -0
  74. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/entities/tag_event.py +0 -0
  75. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/errors.py +0 -0
  76. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/ports/__init__.py +0 -0
  77. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/ports/player_port.py +0 -0
  78. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/ports/reader_port.py +0 -0
  79. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/repositories/__init__.py +0 -0
  80. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/repositories/current_tag_repository.py +0 -0
  81. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/repositories/library_repository.py +0 -0
  82. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/use_cases/__init__.py +0 -0
  83. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/use_cases/determine_action.py +0 -0
  84. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/use_cases/determine_current_tag_action.py +0 -0
  85. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/domain/use_cases/handle_tag_event.py +0 -0
  86. {gukebox-1.0.0.dev14/jukebox/adapters/outbound/players → gukebox-1.0.2/jukebox/domain/use_cases/library}/__init__.py +0 -0
  87. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/pn532/__init__.py +0 -0
  88. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/pn532/profiles.py +0 -0
  89. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/__init__.py +0 -0
  90. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/definitions.py +0 -0
  91. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/dict_utils.py +0 -0
  92. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/entities.py +0 -0
  93. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/errors.py +0 -0
  94. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/file_settings_repository.py +0 -0
  95. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/migration.py +0 -0
  96. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/repositories.py +0 -0
  97. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/resolve.py +0 -0
  98. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/runtime_resolver.py +0 -0
  99. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/runtime_validation.py +0 -0
  100. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/selected_sonos_group_repository.py +0 -0
  101. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/service_protocols.py +0 -0
  102. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/timing_validation.py +0 -0
  103. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/types.py +0 -0
  104. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/validation_rules.py +0 -0
  105. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/settings/view_utils.py +0 -0
  106. {gukebox-1.0.0.dev14/jukebox/adapters/outbound/readers → gukebox-1.0.2/jukebox/shared}/__init__.py +0 -0
  107. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/shared/config_utils.py +0 -0
  108. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/shared/dependency_messages.py +0 -0
  109. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/shared/logger.py +0 -0
  110. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/shared/terminal_ui.py +0 -0
  111. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/shared/timing.py +0 -0
  112. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/sonos/__init__.py +0 -0
  113. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/sonos/discovery.py +0 -0
  114. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/sonos/selection.py +0 -0
  115. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/jukebox/sonos/service.py +0 -0
  116. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/pn532/__init__.py +0 -0
  117. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/pn532/pn532.py +0 -0
  118. {gukebox-1.0.0.dev14 → gukebox-1.0.2}/pn532/spi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: gukebox
3
- Version: 1.0.0.dev14
3
+ Version: 1.0.2
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
@@ -43,7 +43,7 @@ Requires-Dist: spidev==3.8 ; extra == 'pn532'
43
43
  Requires-Dist: lgpio==0.2.2.0 ; python_full_version < '3.13' and extra == 'pn532'
44
44
  Requires-Dist: gukebox[api] ; extra == 'ui'
45
45
  Requires-Dist: fastui==0.9.0 ; extra == 'ui'
46
- Requires-Dist: python-multipart==0.0.26 ; extra == 'ui'
46
+ Requires-Dist: python-multipart==0.0.27 ; extra == 'ui'
47
47
  Requires-Python: >=3.11, <3.14
48
48
  Project-URL: Repository, https://github.com/Gudsfile/jukebox
49
49
  Provides-Extra: api
@@ -3,19 +3,19 @@ from typing import Any
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(
@@ -1,13 +1,13 @@
1
1
  from fastapi import APIRouter, HTTPException, Response, status
2
2
  from pydantic import ValidationError
3
3
 
4
- from discstore.adapters.inbound.api.models import DiscInput, DiscOutput, DiscPatchInput
5
- from discstore.domain.entities import 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_disc import GetDisc
9
- from discstore.domain.use_cases.list_discs import ListDiscs
10
- 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
11
11
 
12
12
 
13
13
  def build_discs_router(
@@ -2,7 +2,7 @@ from typing import Any
2
2
 
3
3
  from pydantic import BaseModel, RootModel
4
4
 
5
- from discstore.domain.entities import CurrentTagStatus, Disc
5
+ from jukebox.domain.entities import CurrentTagStatus, Disc
6
6
 
7
7
 
8
8
  class DiscInput(Disc):
@@ -2,7 +2,7 @@ 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
@@ -5,9 +5,9 @@ from jukebox.shared.dependency_messages import optional_extra_dependency_message
5
5
  try:
6
6
  from fastapi import FastAPI, HTTPException
7
7
 
8
- from discstore.adapters.inbound.api.current_tag_router import build_current_tag_router
9
- from discstore.adapters.inbound.api.discs_router import build_discs_router
10
- 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 (
11
11
  CurrentTagDiscOutput,
12
12
  CurrentTagStatusOutput,
13
13
  DiscInput,
@@ -16,19 +16,19 @@ try:
16
16
  SettingsPatchInput,
17
17
  SettingsResetInput,
18
18
  )
19
- from discstore.adapters.inbound.api.settings_router import build_settings_router
19
+ from jukebox.adapters.inbound.admin.api.settings_router import build_settings_router
20
20
  except ModuleNotFoundError as e:
21
21
  if e.name != "fastapi":
22
22
  raise
23
23
  raise ModuleNotFoundError(
24
24
  optional_extra_dependency_message("The `api_controller` module", "api", "jukebox-admin api")
25
25
  ) from e
26
- from discstore.domain.use_cases.add_disc import AddDisc
27
- from discstore.domain.use_cases.edit_disc import EditDisc
28
- from discstore.domain.use_cases.get_current_tag_status import GetCurrentTagStatus
29
- from discstore.domain.use_cases.get_disc import GetDisc
30
- from discstore.domain.use_cases.list_discs import ListDiscs
31
- 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
32
32
  from jukebox.settings.entities import SelectedSonosGroupSettings
33
33
  from jukebox.settings.selected_sonos_group_repository import SettingsSelectedSonosGroupRepository
34
34
  from jukebox.settings.service_protocols import SettingsService
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
 
3
- from discstore.adapters.inbound.cli_display import display_library_line, display_library_table
4
- 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 (
5
5
  CliAddCommand,
6
6
  CliEditCommand,
7
7
  CliGetCommand,
@@ -9,14 +9,14 @@ from discstore.commands import (
9
9
  CliRemoveCommand,
10
10
  CliSearchCommand,
11
11
  )
12
- from discstore.domain.entities import Disc, DiscMetadata, DiscOption
13
- from discstore.domain.use_cases.add_disc import AddDisc
14
- from discstore.domain.use_cases.edit_disc import EditDisc
15
- from discstore.domain.use_cases.get_disc import GetDisc
16
- from discstore.domain.use_cases.list_discs import ListDiscs
17
- from discstore.domain.use_cases.remove_disc import RemoveDisc
18
- from discstore.domain.use_cases.resolve_tag_id import ResolveTagId
19
- 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
20
20
 
21
21
  LOGGER = logging.getLogger("discstore")
22
22
 
@@ -1,4 +1,4 @@
1
- from discstore.domain.entities import Disc
1
+ from jukebox.domain.entities import Disc
2
2
 
3
3
  MAX_COL_WIDTH = 20
4
4
 
@@ -1,12 +1,12 @@
1
1
  import logging
2
2
 
3
- from discstore.adapters.inbound.cli_display import display_library_line, display_library_table
4
- from discstore.domain.entities import CurrentTagStatus, Disc, DiscMetadata, DiscOption
5
- from discstore.domain.use_cases.add_disc import AddDisc
6
- from discstore.domain.use_cases.edit_disc import EditDisc
7
- from discstore.domain.use_cases.get_current_tag_status import GetCurrentTagStatus
8
- from discstore.domain.use_cases.list_discs import ListDiscs
9
- 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
10
10
 
11
11
  LOGGER = logging.getLogger("discstore")
12
12
 
@@ -1,4 +1,3 @@
1
- from collections.abc import AsyncIterator
2
1
  from typing import Annotated
3
2
 
4
3
  from jukebox.shared.dependency_messages import optional_extra_dependency_message
@@ -17,25 +16,21 @@ except ModuleNotFoundError as e:
17
16
 
18
17
  from pydantic import BaseModel, Field
19
18
 
20
- from discstore.adapters.inbound.api_controller import APIController
21
- from discstore.adapters.inbound.ui_pages.library import DiscForm, DiscTable, LibraryUIPageBuilder
22
- from discstore.adapters.inbound.ui_pages.settings import SettingsUIPageBuilder
23
- from discstore.adapters.inbound.ui_pages.sonos import SonosSelectionForm, SonosUIPageBuilder
24
- from discstore.domain.entities import CurrentTagStatus, Disc, DiscMetadata, DiscOption
25
- from discstore.domain.use_cases.add_disc import AddDisc
26
- from discstore.domain.use_cases.edit_disc import EditDisc
27
- from discstore.domain.use_cases.get_current_tag_status import GetCurrentTagStatus
28
- from discstore.domain.use_cases.get_disc import GetDisc
29
- from discstore.domain.use_cases.list_discs import ListDiscs
30
- from discstore.domain.use_cases.remove_disc import RemoveDisc
31
- from jukebox.settings.definitions import (
32
- EditableSettingDisplay,
33
- get_setting_definition,
34
- )
19
+ from jukebox.adapters.inbound.admin.api_controller import APIController
20
+ from jukebox.adapters.inbound.admin.ui_pages.library import DiscForm, LibraryUIPageBuilder
21
+ from jukebox.adapters.inbound.admin.ui_pages.settings import SettingsUIPageBuilder
22
+ from jukebox.adapters.inbound.admin.ui_pages.sonos import SonosSelectionForm, SonosUIPageBuilder
23
+ from jukebox.domain.entities import Disc, DiscMetadata, DiscOption
24
+ from jukebox.domain.use_cases.library.add_disc import AddDisc
25
+ from jukebox.domain.use_cases.library.edit_disc import EditDisc
26
+ from jukebox.domain.use_cases.library.get_current_tag_status import GetCurrentTagStatus
27
+ from jukebox.domain.use_cases.library.get_disc import GetDisc
28
+ from jukebox.domain.use_cases.library.list_discs import ListDiscs
29
+ from jukebox.domain.use_cases.library.remove_disc import RemoveDisc
30
+ from jukebox.settings.definitions import get_setting_definition
35
31
  from jukebox.settings.errors import SettingsError
36
32
  from jukebox.settings.selected_sonos_group_repository import SettingsSelectedSonosGroupRepository
37
33
  from jukebox.settings.service_protocols import SettingsService
38
- from jukebox.settings.types import JsonObject, JsonValue
39
34
  from jukebox.sonos.discovery import SonosDiscoveryError
40
35
  from jukebox.sonos.selection import SaveSonosSelection
41
36
  from jukebox.sonos.service import SonosService
@@ -81,12 +76,12 @@ class UIController(APIController):
81
76
 
82
77
  @self.app.get("/api/ui/", response_model=FastUI, response_model_exclude_none=True)
83
78
  def list_discs(toast: str | None = None) -> list[AnyComponent]:
84
- return self._build_index_page_components(toast=toast)
79
+ return self.library_pages.build_index_page_components(toast=toast)
85
80
 
86
81
  @self.app.get("/api/ui/current-tag-banner/events")
87
82
  async def get_current_tag_banner_events(request: Request) -> StreamingResponse:
88
83
  return StreamingResponse(
89
- self._current_tag_banner_event_stream(request),
84
+ self.library_pages.current_tag_banner_event_stream(request),
90
85
  media_type="text/event-stream",
91
86
  headers={
92
87
  "Cache-Control": "no-cache",
@@ -97,9 +92,11 @@ class UIController(APIController):
97
92
 
98
93
  @self.app.get("/api/ui/discs/new", response_model=FastUI, response_model_exclude_none=True)
99
94
  def new_disc_form(prefill: str | None = None) -> list[AnyComponent]:
100
- return self._build_form_page_components(
95
+ return self.library_pages.build_form_page_components(
101
96
  title="Add disc",
102
- form_components=self._build_new_disc_form_components(prefill_current=(prefill == "current")),
97
+ form_components=self.library_pages.build_new_disc_form_components(
98
+ prefill_current=(prefill == "current")
99
+ ),
103
100
  )
104
101
 
105
102
  @self.app.post("/api/ui/discs", response_model=FastUI, response_model_exclude_none=True)
@@ -124,9 +121,9 @@ class UIController(APIController):
124
121
 
125
122
  @self.app.get("/api/ui/discs/{tag_id}/edit", response_model=FastUI, response_model_exclude_none=True)
126
123
  def edit_disc_form(tag_id: str) -> list[AnyComponent]:
127
- return self._build_form_page_components(
124
+ return self.library_pages.build_form_page_components(
128
125
  title=f"Edit disc {tag_id}",
129
- form_components=self._build_edit_disc_form_components(tag_id),
126
+ form_components=self.library_pages.build_edit_disc_form_components(tag_id),
130
127
  )
131
128
 
132
129
  @self.app.post("/api/ui/discs/{tag_id}", response_model=FastUI, response_model_exclude_none=True)
@@ -166,9 +163,9 @@ class UIController(APIController):
166
163
 
167
164
  @self.app.get("/api/ui/discs/{tag_id}/delete", response_model=FastUI, response_model_exclude_none=True)
168
165
  def delete_disc_confirmation(tag_id: str) -> list[AnyComponent]:
169
- return self._build_form_page_components(
166
+ return self.library_pages.build_form_page_components(
170
167
  title=f"Delete disc {tag_id}",
171
- form_components=self._build_delete_disc_form_components(tag_id),
168
+ form_components=self.library_pages.build_delete_disc_form_components(tag_id),
172
169
  )
173
170
 
174
171
  # Fast-UI buttons and forms do not support the DELETE method directly. So we cannot call DELETE on
@@ -186,11 +183,11 @@ class UIController(APIController):
186
183
 
187
184
  @self.app.get("/api/ui/settings", response_model=FastUI, response_model_exclude_none=True)
188
185
  def settings_page(toast: str | None = None, toast_message: str | None = None) -> list[AnyComponent]:
189
- return self._build_settings_page_components(toast=toast, toast_message=toast_message)
186
+ return self.settings_pages.build_settings_page_components(toast=toast, toast_message=toast_message)
190
187
 
191
188
  @self.app.get("/api/ui/settings/{setting_path}/edit", response_model=FastUI, response_model_exclude_none=True)
192
189
  def edit_setting_form(setting_path: str) -> list[AnyComponent]:
193
- return self._build_settings_edit_page_components(setting_path)
190
+ return self.settings_pages.build_settings_edit_page_components(setting_path)
194
191
 
195
192
  @self.app.post("/api/ui/settings/{setting_path}", response_model=FastUI, response_model_exclude_none=True)
196
193
  async def update_setting(
@@ -202,13 +199,15 @@ class UIController(APIController):
202
199
  raise HTTPException(status_code=404, detail=f"Unknown setting path: {setting_path}")
203
200
 
204
201
  try:
205
- patch = self._build_settings_patch(setting_path, form.value)
202
+ patch = self.settings_pages.build_settings_patch(setting_path, form.value)
206
203
  result = self.settings_service.patch_persisted_settings(patch)
207
204
  except ValueError as err:
208
205
  raise self._field_validation_error("value", str(err))
209
206
  except SettingsError as err:
210
- if self._persisted_value_matches(setting_path, self._lookup_optional_dotted_path(patch, setting_path)):
211
- return self._build_settings_success_response(
207
+ if self.settings_pages.persisted_value_matches(
208
+ setting_path, self.settings_pages.lookup_optional_dotted_path(patch, setting_path)
209
+ ):
210
+ return self.settings_pages.build_settings_success_response(
212
211
  "Settings saved, but effective settings are still unavailable."
213
212
  )
214
213
  raise self._field_validation_error("value", str(err))
@@ -217,15 +216,15 @@ class UIController(APIController):
217
216
  except Exception as err:
218
217
  raise HTTPException(status_code=500, detail=f"Server error: {str(err)}")
219
218
 
220
- return self._build_settings_success_response(str(result["message"]))
219
+ return self.settings_pages.build_settings_success_response(str(result["message"]))
221
220
 
222
221
  @self.app.post("/api/ui/settings/{setting_path}/reset", response_model=FastUI, response_model_exclude_none=True)
223
222
  async def reset_setting(setting_path: str) -> list[AnyComponent]:
224
- return self._reset_setting(setting_path)
223
+ return self.settings_pages.reset_setting(setting_path)
225
224
 
226
225
  @self.app.get("/api/ui/sonos", response_model=FastUI, response_model_exclude_none=True)
227
226
  def sonos_page(toast: str | None = None, toast_message: str | None = None) -> list[AnyComponent]:
228
- return self._build_sonos_page_components(toast=toast, toast_message=toast_message)
227
+ return self.sonos_pages.build_sonos_page_components(toast=toast, toast_message=toast_message)
229
228
 
230
229
  @self.app.get("/api/ui/sonos/edit", response_model=FastUI, response_model_exclude_none=True)
231
230
  def edit_sonos_form(
@@ -236,7 +235,7 @@ class UIController(APIController):
236
235
  field_errors = None
237
236
  if error_message:
238
237
  field_errors = {self._sonos_field_name_for_error(error_message): error_message}
239
- return self._build_sonos_edit_page_components(
238
+ return self.sonos_pages.build_sonos_edit_page_components(
240
239
  error_message=error_message,
241
240
  field_errors=field_errors,
242
241
  submitted_uids=uids,
@@ -292,38 +291,6 @@ class UIController(APIController):
292
291
  c.FireEvent(event=GoToEvent(url=f"/?toast={toast_event_name}")),
293
292
  ]
294
293
 
295
- def _build_settings_success_response(self, message: str) -> list[AnyComponent]:
296
- return self.settings_pages.build_settings_success_response(message)
297
-
298
- def _reset_setting(self, setting_path: str) -> list[AnyComponent]:
299
- return self.settings_pages.reset_setting(setting_path)
300
-
301
- def _build_sonos_page_components(
302
- self,
303
- toast: str | None = None,
304
- toast_message: str | None = None,
305
- error_message: str | None = None,
306
- ) -> list[AnyComponent]:
307
- return self.sonos_pages.build_sonos_page_components(
308
- toast=toast,
309
- toast_message=toast_message,
310
- error_message=error_message,
311
- )
312
-
313
- def _build_sonos_edit_page_components(
314
- self,
315
- error_message: str | None = None,
316
- field_errors: dict[str, str] | None = None,
317
- submitted_uids: list[str] | None = None,
318
- submitted_coordinator_uid: str | None = None,
319
- ) -> list[AnyComponent]:
320
- return self.sonos_pages.build_sonos_edit_page_components(
321
- error_message=error_message,
322
- field_errors=field_errors,
323
- submitted_uids=submitted_uids,
324
- submitted_coordinator_uid=submitted_coordinator_uid,
325
- )
326
-
327
294
  def _reset_sonos_selection(self) -> list[AnyComponent]:
328
295
  selected_group_repository = SettingsSelectedSonosGroupRepository(self.settings_service)
329
296
  try:
@@ -333,7 +300,7 @@ class UIController(APIController):
333
300
  return self.sonos_pages.build_sonos_success_response(
334
301
  "Sonos selection cleared, but effective settings are still unavailable."
335
302
  )
336
- return self._build_sonos_page_components(error_message=str(err))
303
+ return self.sonos_pages.build_sonos_page_components(error_message=str(err))
337
304
  except HTTPException:
338
305
  raise
339
306
  except Exception as err:
@@ -372,116 +339,6 @@ class UIController(APIController):
372
339
 
373
340
  return f"{prefix}{speaker.name} [{speaker.uid}]"
374
341
 
375
- def _build_index_page_components(self, toast: str | None = None) -> list[AnyComponent]:
376
- return self.library_pages.build_index_page_components(toast=toast)
377
-
378
- def _build_settings_page_components(
379
- self,
380
- toast: str | None = None,
381
- toast_message: str | None = None,
382
- ) -> list[AnyComponent]:
383
- return self.settings_pages.build_settings_page_components(toast=toast, toast_message=toast_message)
384
-
385
- def _build_settings_section_components(
386
- self,
387
- section: str,
388
- settings: list[EditableSettingDisplay],
389
- ) -> list[AnyComponent]:
390
- return self.settings_pages.build_settings_section_components(section, settings)
391
-
392
- def _build_settings_row(self, setting: EditableSettingDisplay, index: int) -> AnyComponent:
393
- return self.settings_pages.build_settings_row(setting, index)
394
-
395
- def _build_settings_edit_page_components(
396
- self,
397
- setting_path: str,
398
- reset_error: str | None = None,
399
- ) -> list[AnyComponent]:
400
- return self.settings_pages.build_settings_edit_page_components(setting_path, reset_error=reset_error)
401
-
402
- def _build_settings_edit_form(self, setting: EditableSettingDisplay) -> AnyComponent:
403
- return self.settings_pages.build_settings_edit_form(setting)
404
-
405
- def _build_settings_reset_form(self, setting_path: str) -> AnyComponent:
406
- return self.settings_pages.build_settings_reset_form(setting_path)
407
-
408
- def _get_settings_displays(self) -> tuple[list[EditableSettingDisplay], str | None]:
409
- return self.settings_pages.get_settings_displays()
410
-
411
- def _build_settings_badges(self, setting: EditableSettingDisplay) -> list[AnyComponent]:
412
- return self.settings_pages.build_settings_badges(setting)
413
-
414
- def _build_settings_value_summary(self, setting: EditableSettingDisplay) -> AnyComponent:
415
- return self.settings_pages.build_settings_value_summary(setting)
416
-
417
- def _build_settings_value_cell(self, label: str, value: str) -> AnyComponent:
418
- return self.settings_pages.build_settings_value_cell(label, value)
419
-
420
- def _build_settings_edit_guidance(self, setting: EditableSettingDisplay) -> str:
421
- return self.settings_pages.build_settings_edit_guidance(setting)
422
-
423
- def _build_settings_patch(self, setting_path: str, raw_value: str) -> JsonObject:
424
- return self.settings_pages.build_settings_patch(setting_path, raw_value)
425
-
426
- def _build_dotted_patch(self, dotted_path: str, value: JsonValue) -> JsonObject:
427
- return self.settings_pages.build_dotted_patch(dotted_path, value)
428
-
429
- def _persisted_value_matches(self, dotted_path: str, expected_value: object) -> bool:
430
- return self.settings_pages.persisted_value_matches(dotted_path, expected_value)
431
-
432
- def _has_persisted_value(self, dotted_path: str) -> bool:
433
- return self.settings_pages.has_persisted_value(dotted_path)
434
-
435
- def _lookup_optional_dotted_path(self, root: JsonObject, dotted_path: str) -> object:
436
- return self.settings_pages.lookup_optional_dotted_path(root, dotted_path)
437
-
438
- def _format_settings_display_value(self, setting_path: str, value: object) -> str:
439
- return self.settings_pages.format_settings_display_value(setting_path, value)
440
-
441
- def _format_settings_provenance(self, provenance: str) -> str:
442
- return self.settings_pages.format_settings_provenance(provenance)
443
-
444
- def _build_form_page_components(self, title: str, form_components: list[AnyComponent]) -> list[AnyComponent]:
445
- return self.library_pages.build_form_page_components(title=title, form_components=form_components)
446
-
447
- def _build_current_tag_banner_components(self, current_tag_status: CurrentTagStatus | None) -> list[AnyComponent]:
448
- return self.library_pages.build_current_tag_banner_components(current_tag_status)
449
-
450
- def _build_disc_library_components(self, discs: list[DiscTable]) -> list[AnyComponent]:
451
- return self.library_pages.build_disc_library_components(discs)
452
-
453
- def _build_disc_library_header(self) -> AnyComponent:
454
- return self.library_pages._build_disc_library_header()
455
-
456
- def _build_disc_library_row(self, disc: DiscTable) -> AnyComponent:
457
- return self.library_pages._build_disc_library_row(disc)
458
-
459
- def _build_disc_header_cell(self, label: str, class_name: str) -> AnyComponent:
460
- return self.library_pages._build_disc_header_cell(label, class_name)
461
-
462
- def _build_disc_value_cell(self, label: str, value: str | None, class_name: str) -> AnyComponent:
463
- return self.library_pages._build_disc_value_cell(label, value, class_name)
464
-
465
- def _build_new_disc_form_components(self, prefill_current: bool) -> list[AnyComponent]:
466
- return self.library_pages.build_new_disc_form_components(prefill_current)
467
-
468
- def _build_edit_disc_form_components(self, tag_id: str) -> list[AnyComponent]:
469
- return self.library_pages.build_edit_disc_form_components(tag_id)
470
-
471
- def _build_delete_disc_form_components(self, tag_id: str) -> list[AnyComponent]:
472
- return self.library_pages.build_delete_disc_form_components(tag_id)
473
-
474
- async def _current_tag_banner_event_stream(
475
- self,
476
- request: Request,
477
- poll_interval_seconds: float = 0.5,
478
- ) -> AsyncIterator[bytes]:
479
- async for payload in self.library_pages.current_tag_banner_event_stream(request, poll_interval_seconds):
480
- yield payload
481
-
482
- def _serialize_current_tag_components(self, components: list[AnyComponent]) -> str:
483
- return self.library_pages.serialize_current_tag_components(components)
484
-
485
342
  @staticmethod
486
343
  def _sonos_field_name_for_error(message: str) -> str:
487
344
  if "coordinator" in message:
@@ -8,10 +8,10 @@ from fastui import components as c
8
8
  from fastui.events import BackEvent, GoToEvent, PageEvent
9
9
  from pydantic import BaseModel, Field
10
10
 
11
- from discstore.domain.entities import CurrentTagStatus, DiscMetadata, DiscOption
12
- from discstore.domain.use_cases.get_current_tag_status import GetCurrentTagStatus
13
- from discstore.domain.use_cases.get_disc import GetDisc
14
- from discstore.domain.use_cases.list_discs import ListDiscs
11
+ from jukebox.domain.entities import CurrentTagStatus, DiscMetadata, DiscOption
12
+ from jukebox.domain.use_cases.library.get_current_tag_status import GetCurrentTagStatus
13
+ from jukebox.domain.use_cases.library.get_disc import GetDisc
14
+ from jukebox.domain.use_cases.library.list_discs import ListDiscs
15
15
 
16
16
 
17
17
  class DiscTable(DiscMetadata, DiscOption):
@@ -5,8 +5,8 @@ from typing import Annotated
5
5
  import typer
6
6
  from pydantic import ValidationError
7
7
 
8
- from discstore.command_handlers import execute_library_command
9
- from discstore.commands import (
8
+ from jukebox.admin.library_command_handlers import execute_library_command
9
+ from jukebox.admin.library_commands import (
10
10
  CliAddCommand,
11
11
  CliEditCommand,
12
12
  CliGetCommand,
@@ -16,7 +16,6 @@ from discstore.commands import (
16
16
  CliSearchCommand,
17
17
  InteractiveCliCommand,
18
18
  )
19
- from discstore.di_container import build_cli_controller, build_interactive_cli_controller
20
19
  from jukebox.settings.errors import SettingsError
21
20
  from jukebox.shared.config_utils import get_package_version
22
21
  from jukebox.shared.logger import set_logger
@@ -41,7 +40,14 @@ from .commands import (
41
40
  is_settings_command,
42
41
  is_sonos_command,
43
42
  )
44
- from .di_container import build_admin_api_app, build_admin_services, build_admin_ui_app, build_settings_service
43
+ from .di_container import (
44
+ build_admin_api_app,
45
+ build_admin_services,
46
+ build_admin_ui_app,
47
+ build_cli_controller,
48
+ build_interactive_cli_controller,
49
+ build_settings_service,
50
+ )
45
51
  from .pn532_command_handlers import execute_pn532_command
46
52
  from .pn532_commands import Pn532ProbeCommand, Pn532ProfilesCommand, Pn532SelectCommand, is_pn532_command
47
53
  from .sonos_households import GroupedSonosHousehold
@@ -1,12 +1,14 @@
1
- from discstore.adapters.outbound.json_library_adapter import JsonLibraryAdapter
2
- from discstore.adapters.outbound.text_current_tag_adapter import TextCurrentTagAdapter
3
- from discstore.domain.use_cases.add_disc import AddDisc
4
- from discstore.domain.use_cases.edit_disc import EditDisc
5
- from discstore.domain.use_cases.get_current_tag_status import GetCurrentTagStatus
6
- from discstore.domain.use_cases.get_disc import GetDisc
7
- from discstore.domain.use_cases.list_discs import ListDiscs
8
- from discstore.domain.use_cases.remove_disc import RemoveDisc
1
+ from jukebox.adapters.outbound.json_library_adapter import JsonLibraryAdapter
9
2
  from jukebox.adapters.outbound.sonos_discovery_adapter import SoCoSonosDiscoveryAdapter
3
+ from jukebox.adapters.outbound.text_current_tag_adapter import TextCurrentTagAdapter
4
+ from jukebox.domain.use_cases.library.add_disc import AddDisc
5
+ from jukebox.domain.use_cases.library.edit_disc import EditDisc
6
+ from jukebox.domain.use_cases.library.get_current_tag_status import GetCurrentTagStatus
7
+ from jukebox.domain.use_cases.library.get_disc import GetDisc
8
+ from jukebox.domain.use_cases.library.list_discs import ListDiscs
9
+ from jukebox.domain.use_cases.library.remove_disc import RemoveDisc
10
+ from jukebox.domain.use_cases.library.resolve_tag_id import ResolveTagId
11
+ from jukebox.domain.use_cases.library.search_discs import SearchDiscs
10
12
  from jukebox.settings.file_settings_repository import FileSettingsRepository
11
13
  from jukebox.settings.resolve import SettingsService as SettingsServiceImpl
12
14
  from jukebox.settings.resolve import build_environment_settings_overrides
@@ -56,7 +58,7 @@ def build_admin_api_app(library_path: str, services: AdminServices):
56
58
  repository = JsonLibraryAdapter(library_path)
57
59
  current_tag_repository = TextCurrentTagAdapter(get_current_tag_path(library_path))
58
60
 
59
- from discstore.adapters.inbound.api_controller import APIController
61
+ from jukebox.adapters.inbound.admin.api_controller import APIController
60
62
 
61
63
  return APIController(
62
64
  AddDisc(repository),
@@ -74,7 +76,7 @@ def build_admin_ui_app(library_path: str, services: AdminServices):
74
76
  repository = JsonLibraryAdapter(library_path)
75
77
  current_tag_repository = TextCurrentTagAdapter(get_current_tag_path(library_path))
76
78
 
77
- from discstore.adapters.inbound.ui_controller import UIController
79
+ from jukebox.adapters.inbound.admin.ui_controller import UIController
78
80
 
79
81
  return UIController(
80
82
  AddDisc(repository),
@@ -90,3 +92,36 @@ def build_admin_ui_app(library_path: str, services: AdminServices):
90
92
 
91
93
  def build_sonos_service() -> SonosService:
92
94
  return DefaultSonosService(SoCoSonosDiscoveryAdapter())
95
+
96
+
97
+ def build_cli_controller(library_path: str):
98
+ repository = JsonLibraryAdapter(library_path)
99
+ current_tag_repository = TextCurrentTagAdapter(get_current_tag_path(library_path))
100
+ get_current_tag_status = GetCurrentTagStatus(current_tag_repository, repository)
101
+
102
+ from jukebox.adapters.inbound.admin.cli_controller import CLIController
103
+
104
+ return CLIController(
105
+ AddDisc(repository),
106
+ ListDiscs(repository),
107
+ RemoveDisc(repository),
108
+ EditDisc(repository),
109
+ GetDisc(repository),
110
+ SearchDiscs(repository),
111
+ ResolveTagId(get_current_tag_status),
112
+ )
113
+
114
+
115
+ def build_interactive_cli_controller(library_path: str):
116
+ repository = JsonLibraryAdapter(library_path)
117
+ current_tag_repository = TextCurrentTagAdapter(get_current_tag_path(library_path))
118
+
119
+ from jukebox.adapters.inbound.admin.interactive_cli_controller import InteractiveCLIController
120
+
121
+ return InteractiveCLIController(
122
+ AddDisc(repository),
123
+ ListDiscs(repository),
124
+ RemoveDisc(repository),
125
+ EditDisc(repository),
126
+ GetCurrentTagStatus(current_tag_repository, repository),
127
+ )
@@ -3,7 +3,7 @@ from typing import Protocol
3
3
 
4
4
  from jukebox.settings.service_protocols import SettingsService
5
5
 
6
- from .commands import InteractiveCliCommand
6
+ from .library_commands import InteractiveCliCommand
7
7
 
8
8
 
9
9
  class LibraryController(Protocol):