data-sourcerer 0.2.3__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
- {data_sourcerer-0.2.3.dist-info → data_sourcerer-0.4.0.dist-info}/METADATA +3 -1
- {data_sourcerer-0.2.3.dist-info → data_sourcerer-0.4.0.dist-info}/RECORD +50 -34
- sourcerer/__init__.py +1 -1
- sourcerer/domain/access_credentials/entities.py +3 -1
- sourcerer/domain/access_credentials/repositories.py +1 -1
- sourcerer/domain/storage/__init__.py +0 -0
- sourcerer/domain/storage/entities.py +27 -0
- sourcerer/domain/storage/repositories.py +31 -0
- sourcerer/domain/storage_provider/entities.py +1 -1
- sourcerer/infrastructure/access_credentials/repositories.py +3 -2
- sourcerer/infrastructure/access_credentials/services.py +9 -25
- sourcerer/infrastructure/db/models.py +33 -2
- sourcerer/infrastructure/storage/__init__.py +0 -0
- sourcerer/infrastructure/storage/repositories.py +72 -0
- sourcerer/infrastructure/storage/services.py +37 -0
- sourcerer/infrastructure/storage_provider/services/azure.py +1 -3
- sourcerer/infrastructure/storage_provider/services/gcp.py +2 -3
- sourcerer/infrastructure/storage_provider/services/s3.py +1 -2
- sourcerer/infrastructure/utils.py +2 -1
- sourcerer/presentation/di_container.py +15 -0
- sourcerer/presentation/screens/file_system_finder/main.py +5 -10
- sourcerer/presentation/screens/file_system_finder/widgets/file_system_navigator.py +16 -13
- sourcerer/presentation/screens/main/main.py +89 -9
- sourcerer/presentation/screens/main/messages/preview_request.py +1 -0
- sourcerer/presentation/screens/main/messages/select_storage_item.py +1 -0
- sourcerer/presentation/screens/main/mixins/resize_containers_watcher_mixin.py +2 -1
- sourcerer/presentation/screens/main/widgets/storage_content.py +197 -80
- sourcerer/presentation/screens/main/widgets/storage_list_sidebar.py +99 -31
- sourcerer/presentation/screens/preview_content/main.py +216 -17
- sourcerer/presentation/screens/preview_content/styles.tcss +39 -4
- sourcerer/presentation/screens/preview_content/text_area_style.py +60 -0
- sourcerer/presentation/screens/provider_creds_list/main.py +38 -13
- sourcerer/presentation/screens/provider_creds_registration/main.py +10 -7
- sourcerer/presentation/screens/shared/modal_screens.py +37 -0
- sourcerer/presentation/screens/shared/widgets/spinner.py +57 -0
- sourcerer/presentation/screens/storage_action_progress/main.py +3 -5
- sourcerer/presentation/screens/storages_list/__init__.py +0 -0
- sourcerer/presentation/screens/storages_list/main.py +184 -0
- sourcerer/presentation/screens/storages_list/messages/__init__.py +0 -0
- sourcerer/presentation/screens/storages_list/messages/reload_storages_request.py +8 -0
- sourcerer/presentation/screens/storages_list/styles.tcss +55 -0
- sourcerer/presentation/screens/storages_registration/__init__.py +0 -0
- sourcerer/presentation/screens/storages_registration/main.py +100 -0
- sourcerer/presentation/screens/storages_registration/styles.tcss +41 -0
- sourcerer/presentation/settings.py +29 -16
- sourcerer/presentation/utils.py +9 -1
- sourcerer/settings.py +2 -0
- sourcerer/presentation/screens/shared/widgets/loader.py +0 -31
- {data_sourcerer-0.2.3.dist-info → data_sourcerer-0.4.0.dist-info}/WHEEL +0 -0
- {data_sourcerer-0.2.3.dist-info → data_sourcerer-0.4.0.dist-info}/entry_points.txt +0 -0
- {data_sourcerer-0.2.3.dist-info → data_sourcerer-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -13,8 +13,11 @@ from dependency_injector import containers, providers
|
|
13
13
|
from sourcerer.infrastructure.access_credentials.repositories import (
|
14
14
|
SQLAlchemyCredentialsRepository,
|
15
15
|
)
|
16
|
+
from sourcerer.infrastructure.access_credentials.services import CredentialsService
|
16
17
|
from sourcerer.infrastructure.db.config import Database
|
17
18
|
from sourcerer.infrastructure.file_system.services import FileSystemService
|
19
|
+
from sourcerer.infrastructure.storage.repositories import SQLAlchemyStoragesRepository
|
20
|
+
from sourcerer.infrastructure.storage.services import StoragesService
|
18
21
|
from sourcerer.settings import APP_DIR, DB_NAME
|
19
22
|
|
20
23
|
DB_URL = f"sqlite:////{APP_DIR}/{DB_NAME}"
|
@@ -43,4 +46,16 @@ class DiContainer(containers.DeclarativeContainer):
|
|
43
46
|
SQLAlchemyCredentialsRepository, session_factory
|
44
47
|
)
|
45
48
|
|
49
|
+
storages_repository = providers.Factory(
|
50
|
+
SQLAlchemyStoragesRepository, session_factory
|
51
|
+
)
|
52
|
+
|
53
|
+
credentials_service = providers.Factory(
|
54
|
+
CredentialsService, repository=credentials_repository
|
55
|
+
)
|
56
|
+
storages_service = providers.Factory(
|
57
|
+
StoragesService,
|
58
|
+
repository=storages_repository,
|
59
|
+
)
|
60
|
+
|
46
61
|
file_system_service = providers.Factory(FileSystemService, Path.home())
|
@@ -5,11 +5,9 @@ from pathlib import Path
|
|
5
5
|
from dependency_injector.wiring import Provide
|
6
6
|
from textual import on
|
7
7
|
from textual.app import ComposeResult
|
8
|
-
from textual.binding import Binding
|
9
8
|
from textual.containers import Container, Horizontal
|
10
9
|
from textual.css.query import NoMatches
|
11
10
|
from textual.reactive import reactive
|
12
|
-
from textual.screen import ModalScreen
|
13
11
|
from textual.widgets import Static
|
14
12
|
|
15
13
|
from sourcerer.infrastructure.file_system.services import FileSystemService
|
@@ -17,6 +15,7 @@ from sourcerer.presentation.di_container import DiContainer
|
|
17
15
|
from sourcerer.presentation.screens.file_system_finder.widgets.file_system_navigator import (
|
18
16
|
FileSystemNavigator,
|
19
17
|
)
|
18
|
+
from sourcerer.presentation.screens.shared.modal_screens import ExitBoundModalScreen
|
20
19
|
from sourcerer.presentation.screens.shared.widgets.button import Button
|
21
20
|
|
22
21
|
|
@@ -26,21 +25,17 @@ class FileSystemSelectionValidationRule:
|
|
26
25
|
error_message: str
|
27
26
|
|
28
27
|
|
29
|
-
class FileSystemNavigationModal(
|
28
|
+
class FileSystemNavigationModal(ExitBoundModalScreen):
|
30
29
|
CONTAINER_ID = "file_system_view_container"
|
31
30
|
CSS_PATH = "styles.tcss"
|
32
31
|
|
33
|
-
BINDINGS = [
|
34
|
-
Binding("escape", "app.pop_screen", "Pop screen"),
|
35
|
-
]
|
36
|
-
|
37
32
|
active_path: reactive[Path] = reactive(Path())
|
38
33
|
|
39
34
|
def __init__(
|
40
35
|
self,
|
41
36
|
*args,
|
42
|
-
file_system_service: FileSystemService = Provide[
|
43
|
-
DiContainer.file_system_service
|
37
|
+
file_system_service: FileSystemService = Provide[ # type: ignore
|
38
|
+
DiContainer.file_system_service # type: ignore
|
44
39
|
],
|
45
40
|
validation_rules: list[FileSystemSelectionValidationRule] | None = None,
|
46
41
|
**kwargs,
|
@@ -125,7 +120,7 @@ class FileSystemNavigationModal(ModalScreen):
|
|
125
120
|
event (Button.Click): The event containing the button that was clicked.
|
126
121
|
"""
|
127
122
|
if event.action == "close":
|
128
|
-
self.
|
123
|
+
self.action_cancel_screen()
|
129
124
|
else:
|
130
125
|
self.on_apply()
|
131
126
|
|
@@ -21,6 +21,7 @@ from sourcerer.presentation.screens.shared.containers import (
|
|
21
21
|
ScrollHorizontalContainerWithNoBindings,
|
22
22
|
ScrollVerticalContainerWithNoBindings,
|
23
23
|
)
|
24
|
+
from sourcerer.presentation.settings import KeyBindings
|
24
25
|
from sourcerer.settings import DIRECTORY_ICON, DOUBLE_CLICK_THRESHOLD, FILE_ICON
|
25
26
|
|
26
27
|
|
@@ -48,7 +49,7 @@ class FileSystemWidget(Widget):
|
|
48
49
|
background: $block-cursor-blurred-background;
|
49
50
|
text-style: $block-cursor-blurred-text-style;
|
50
51
|
}
|
51
|
-
|
52
|
+
|
52
53
|
.folder-name {
|
53
54
|
text-overflow: ellipsis;
|
54
55
|
text-wrap: nowrap;
|
@@ -161,7 +162,7 @@ class FileSystemWidget(Widget):
|
|
161
162
|
Calls the `on_click` method if the pressed key is "enter", which may trigger
|
162
163
|
folder navigation or file opening depending on the widget's context.
|
163
164
|
"""
|
164
|
-
if event.key ==
|
165
|
+
if event.key == KeyBindings.ENTER.value:
|
165
166
|
self.on_file_select()
|
166
167
|
|
167
168
|
def on_file_select(self):
|
@@ -232,11 +233,13 @@ class FileSystemNavigator(ScrollHorizontalContainerWithNoBindings):
|
|
232
233
|
|
233
234
|
# Consolidate key binding data
|
234
235
|
BINDINGS: ClassVar[list[BindingType]] = [
|
235
|
-
Binding(
|
236
|
-
Binding(
|
237
|
-
Binding(
|
238
|
-
Binding(
|
239
|
-
Binding(
|
236
|
+
Binding(KeyBindings.ENTER.value, "select_cursor", "Select", show=False),
|
237
|
+
Binding(KeyBindings.ARROW_UP.value, "cursor_up", "Cursor up", show=False),
|
238
|
+
Binding(KeyBindings.ARROW_DOWN.value, "cursor_down", "Cursor down", show=False),
|
239
|
+
Binding(KeyBindings.ARROW_LEFT.value, "cursor_left", "Cursor left", show=False),
|
240
|
+
Binding(
|
241
|
+
KeyBindings.ARROW_RIGHT.value, "cursor_right", "Cursor right", show=False
|
242
|
+
),
|
240
243
|
]
|
241
244
|
|
242
245
|
MAIN_CONTAINER_ID: ClassVar[str] = "dirs_content"
|
@@ -306,9 +309,9 @@ class FileSystemNavigator(ScrollHorizontalContainerWithNoBindings):
|
|
306
309
|
)
|
307
310
|
self._focus_first_child(path_listing_container)
|
308
311
|
|
309
|
-
self.path_listing_containers_uuids[
|
310
|
-
|
311
|
-
|
312
|
+
self.path_listing_containers_uuids[
|
313
|
+
str(self.work_dir)
|
314
|
+
] = path_listing_container.id
|
312
315
|
|
313
316
|
def action_cursor_down(self) -> None:
|
314
317
|
"""
|
@@ -626,9 +629,9 @@ class FileSystemNavigator(ScrollHorizontalContainerWithNoBindings):
|
|
626
629
|
await self._mount_path_listing_container(path_listing_container)
|
627
630
|
|
628
631
|
if str(folder_path) not in self.path_listing_containers_uuids:
|
629
|
-
self.path_listing_containers_uuids[
|
630
|
-
|
631
|
-
|
632
|
+
self.path_listing_containers_uuids[
|
633
|
+
str(folder_path)
|
634
|
+
] = path_listing_container.id
|
632
635
|
|
633
636
|
@on(FileSystemWidget.Focus)
|
634
637
|
def on_folder_focus(self, event: FileSystemWidget.Focus):
|
@@ -1,20 +1,26 @@
|
|
1
|
+
import contextlib
|
1
2
|
import time
|
2
3
|
import traceback
|
3
4
|
from concurrent.futures import ThreadPoolExecutor
|
4
5
|
from pathlib import Path
|
6
|
+
from typing import ClassVar
|
5
7
|
|
8
|
+
from dependency_injector.wiring import Provide
|
6
9
|
from textual import on, work
|
7
10
|
from textual.app import App, ComposeResult
|
8
|
-
from textual.binding import Binding
|
11
|
+
from textual.binding import Binding, BindingType
|
9
12
|
from textual.containers import Horizontal
|
13
|
+
from textual.css.query import NoMatches
|
10
14
|
from textual.reactive import reactive
|
11
15
|
from textual.widgets import Footer
|
12
16
|
|
17
|
+
from sourcerer.domain.storage_provider.entities import Storage
|
13
18
|
from sourcerer.infrastructure.access_credentials.services import CredentialsService
|
14
19
|
from sourcerer.infrastructure.storage_provider.exceptions import (
|
15
20
|
ListStorageItemsError,
|
16
21
|
)
|
17
22
|
from sourcerer.infrastructure.utils import generate_uuid
|
23
|
+
from sourcerer.presentation.di_container import DiContainer
|
18
24
|
from sourcerer.presentation.screens.critical_error.main import CriticalErrorScreen
|
19
25
|
from sourcerer.presentation.screens.file_system_finder.main import (
|
20
26
|
FileSystemNavigationModal,
|
@@ -55,6 +61,8 @@ from sourcerer.presentation.screens.storage_action_progress.main import (
|
|
55
61
|
StorageActionProgressScreen,
|
56
62
|
UploadKey,
|
57
63
|
)
|
64
|
+
from sourcerer.presentation.screens.storages_list.main import StoragesListScreen
|
65
|
+
from sourcerer.presentation.settings import KeyBindings
|
58
66
|
from sourcerer.presentation.themes.github_dark import github_dark_theme
|
59
67
|
from sourcerer.presentation.utils import (
|
60
68
|
get_provider_service_by_access_credentials,
|
@@ -93,12 +101,29 @@ class Sourcerer(App, ResizeContainersWatcherMixin):
|
|
93
101
|
"""
|
94
102
|
|
95
103
|
CSS_PATH = "styles.tcss"
|
96
|
-
BINDINGS = [
|
104
|
+
BINDINGS: ClassVar[list[BindingType]] = [
|
105
|
+
Binding("ctrl+r", "registrations", "Registrations list"),
|
106
|
+
Binding("ctrl+s", "storages", "Storages list"),
|
107
|
+
Binding("ctrl+f", "find", show=False),
|
108
|
+
Binding(
|
109
|
+
KeyBindings.ARROW_LEFT.value, "focus_sidebar", "Focus sidebar", show=False
|
110
|
+
),
|
111
|
+
Binding(
|
112
|
+
KeyBindings.ARROW_RIGHT.value, "focus_content", "Focus content", show=False
|
113
|
+
),
|
114
|
+
]
|
97
115
|
is_storage_list_loading = reactive(False, recompose=True)
|
98
116
|
|
99
|
-
def __init__(
|
117
|
+
def __init__(
|
118
|
+
self,
|
119
|
+
credentials_service: CredentialsService = Provide[
|
120
|
+
DiContainer.credentials_service
|
121
|
+
],
|
122
|
+
*args,
|
123
|
+
**kwargs,
|
124
|
+
):
|
100
125
|
super().__init__(*args, **kwargs)
|
101
|
-
self.credentials_service =
|
126
|
+
self.credentials_service = credentials_service
|
102
127
|
self.storage_list_sidebar = StorageListSidebar(id="storage_list_sidebar")
|
103
128
|
self.storage_content = StorageContentContainer(id="storage_content_container")
|
104
129
|
self.load_percentage = 0
|
@@ -130,6 +155,25 @@ class Sourcerer(App, ResizeContainersWatcherMixin):
|
|
130
155
|
self.theme = "github-dark"
|
131
156
|
self.init_storages_list()
|
132
157
|
|
158
|
+
def action_find(self):
|
159
|
+
"""
|
160
|
+
Focus search input.
|
161
|
+
"""
|
162
|
+
with contextlib.suppress(NoMatches):
|
163
|
+
self.query_one(f"#{self.storage_content.search_input_id}").focus()
|
164
|
+
|
165
|
+
def action_focus_content(self):
|
166
|
+
"""
|
167
|
+
Focuses the storage content container.
|
168
|
+
"""
|
169
|
+
self.storage_content.focus()
|
170
|
+
|
171
|
+
def action_focus_sidebar(self):
|
172
|
+
"""
|
173
|
+
Focuses the storage list sidebar.
|
174
|
+
"""
|
175
|
+
self.storage_list_sidebar.focus()
|
176
|
+
|
133
177
|
def action_registrations(self):
|
134
178
|
"""
|
135
179
|
Opens the provider credentials list screen and refreshes the storage list.
|
@@ -140,7 +184,23 @@ class Sourcerer(App, ResizeContainersWatcherMixin):
|
|
140
184
|
This method is typically used to allow users to add their
|
141
185
|
cloud storage credentials, which will then be reflected in the storage
|
142
186
|
"""
|
143
|
-
self.app.push_screen(
|
187
|
+
self.app.push_screen(
|
188
|
+
ProviderCredsListScreen(), callback=self.modal_screen_callback
|
189
|
+
)
|
190
|
+
|
191
|
+
def action_storages(self):
|
192
|
+
self.app.push_screen(StoragesListScreen(), callback=self.modal_screen_callback)
|
193
|
+
|
194
|
+
def modal_screen_callback(self, requires_storage_refresh: bool | None = True):
|
195
|
+
"""
|
196
|
+
Callback for modal screens to refresh the storage list if required.
|
197
|
+
|
198
|
+
This method is called when a modal screen is closed. If the
|
199
|
+
`requires_storage_refresh` flag is set to True, it refreshes the
|
200
|
+
storage list by calling the `refresh_storages` method.
|
201
|
+
"""
|
202
|
+
if requires_storage_refresh:
|
203
|
+
self.refresh_storages()
|
144
204
|
|
145
205
|
def refresh_storages(self, *args, **kwargs):
|
146
206
|
"""
|
@@ -150,6 +210,7 @@ class Sourcerer(App, ResizeContainersWatcherMixin):
|
|
150
210
|
configurations.
|
151
211
|
"""
|
152
212
|
self.storage_list_sidebar.storages = {}
|
213
|
+
self.storage_list_sidebar.last_update_timestamp = time.time()
|
153
214
|
self.init_storages_list()
|
154
215
|
|
155
216
|
@work(thread=True)
|
@@ -206,7 +267,11 @@ class Sourcerer(App, ResizeContainersWatcherMixin):
|
|
206
267
|
5. Notify the user if an error occurs during the retrieval process.
|
207
268
|
"""
|
208
269
|
self.refresh_storage_content(
|
209
|
-
event.access_credentials_uuid,
|
270
|
+
event.access_credentials_uuid,
|
271
|
+
event.name,
|
272
|
+
event.path,
|
273
|
+
event.prefix,
|
274
|
+
event.focus_content,
|
210
275
|
)
|
211
276
|
|
212
277
|
@on(UploadRequest)
|
@@ -343,6 +408,7 @@ class Sourcerer(App, ResizeContainersWatcherMixin):
|
|
343
408
|
PreviewContentScreen(
|
344
409
|
storage_name=event.storage_name,
|
345
410
|
key=event.path,
|
411
|
+
file_size=event.size,
|
346
412
|
access_credentials_uuid=event.access_credentials_uuid,
|
347
413
|
)
|
348
414
|
)
|
@@ -381,7 +447,12 @@ class Sourcerer(App, ResizeContainersWatcherMixin):
|
|
381
447
|
self.uncheck_files_request(UncheckFilesRequest(keys=[]))
|
382
448
|
|
383
449
|
def refresh_storage_content(
|
384
|
-
self,
|
450
|
+
self,
|
451
|
+
access_credentials_uuid,
|
452
|
+
storage_name,
|
453
|
+
path,
|
454
|
+
prefix=None,
|
455
|
+
focus_content=False,
|
385
456
|
):
|
386
457
|
"""
|
387
458
|
Refreshes the storage content display with items from the specified storage path.
|
@@ -407,6 +478,7 @@ class Sourcerer(App, ResizeContainersWatcherMixin):
|
|
407
478
|
self.storage_content.storage_content = None
|
408
479
|
self.storage_content.selected_files = set()
|
409
480
|
self.storage_content.selected_files_n = 0
|
481
|
+
self.storage_content.focus_content = focus_content
|
410
482
|
|
411
483
|
provider_service = get_provider_service_by_access_uuid(
|
412
484
|
access_credentials_uuid, self.credentials_service
|
@@ -421,7 +493,7 @@ class Sourcerer(App, ResizeContainersWatcherMixin):
|
|
421
493
|
**params
|
422
494
|
)
|
423
495
|
except ListStorageItemsError as e:
|
424
|
-
self.notify_error(f"""Could not extract storage content \n{
|
496
|
+
self.notify_error(f"""Could not extract storage content \n{e}""")
|
425
497
|
|
426
498
|
def _upload_file(
|
427
499
|
self,
|
@@ -509,7 +581,15 @@ class Sourcerer(App, ResizeContainersWatcherMixin):
|
|
509
581
|
|
510
582
|
try:
|
511
583
|
storages = provider_service.list_storages()
|
512
|
-
|
584
|
+
storage_names = [storage.storage for storage in storages]
|
585
|
+
registered_storages = [
|
586
|
+
Storage(credentials.provider, storage.name, storage.created_at)
|
587
|
+
for storage in credentials.storages
|
588
|
+
if storage.name not in storage_names
|
589
|
+
]
|
590
|
+
self.storage_list_sidebar.storages[credentials.uuid] = (
|
591
|
+
storages + registered_storages
|
592
|
+
)
|
513
593
|
self.storage_list_sidebar.last_update_timestamp = time.time()
|
514
594
|
except Exception:
|
515
595
|
self.notify_error(f"Could not get storages list for {credentials.name}!")
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import time
|
2
|
+
from typing import ClassVar
|
2
3
|
|
3
4
|
from textual.events import MouseMove, MouseUp
|
4
5
|
|
@@ -28,7 +29,7 @@ class ResizeContainersWatcherMixin:
|
|
28
29
|
and is inherited from textual App.
|
29
30
|
"""
|
30
31
|
|
31
|
-
required_methods = ["query_one", "refresh"]
|
32
|
+
required_methods: ClassVar[list[str]] = ["query_one", "refresh"]
|
32
33
|
|
33
34
|
def __init_subclass__(cls, **kwargs):
|
34
35
|
super().__init_subclass__(**kwargs)
|