data-sourcerer 0.1.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.
Files changed (95) hide show
  1. data_sourcerer-0.1.0.dist-info/METADATA +52 -0
  2. data_sourcerer-0.1.0.dist-info/RECORD +95 -0
  3. data_sourcerer-0.1.0.dist-info/WHEEL +5 -0
  4. data_sourcerer-0.1.0.dist-info/entry_points.txt +2 -0
  5. data_sourcerer-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. data_sourcerer-0.1.0.dist-info/top_level.txt +1 -0
  7. sourcerer/__init__.py +15 -0
  8. sourcerer/domain/__init__.py +13 -0
  9. sourcerer/domain/access_credentials/__init__.py +7 -0
  10. sourcerer/domain/access_credentials/entities.py +53 -0
  11. sourcerer/domain/access_credentials/exceptions.py +17 -0
  12. sourcerer/domain/access_credentials/repositories.py +84 -0
  13. sourcerer/domain/access_credentials/services.py +91 -0
  14. sourcerer/domain/file_system/__init__.py +7 -0
  15. sourcerer/domain/file_system/entities.py +70 -0
  16. sourcerer/domain/file_system/exceptions.py +17 -0
  17. sourcerer/domain/file_system/services.py +64 -0
  18. sourcerer/domain/shared/__init__.py +6 -0
  19. sourcerer/domain/shared/entities.py +18 -0
  20. sourcerer/domain/storage_provider/__init__.py +7 -0
  21. sourcerer/domain/storage_provider/entities.py +86 -0
  22. sourcerer/domain/storage_provider/exceptions.py +17 -0
  23. sourcerer/domain/storage_provider/services.py +130 -0
  24. sourcerer/infrastructure/__init__.py +13 -0
  25. sourcerer/infrastructure/access_credentials/__init__.py +7 -0
  26. sourcerer/infrastructure/access_credentials/exceptions.py +16 -0
  27. sourcerer/infrastructure/access_credentials/registry.py +120 -0
  28. sourcerer/infrastructure/access_credentials/repositories.py +119 -0
  29. sourcerer/infrastructure/access_credentials/services.py +396 -0
  30. sourcerer/infrastructure/db/__init__.py +6 -0
  31. sourcerer/infrastructure/db/config.py +73 -0
  32. sourcerer/infrastructure/db/models.py +47 -0
  33. sourcerer/infrastructure/file_system/__init__.py +7 -0
  34. sourcerer/infrastructure/file_system/exceptions.py +89 -0
  35. sourcerer/infrastructure/file_system/services.py +147 -0
  36. sourcerer/infrastructure/storage_provider/__init__.py +7 -0
  37. sourcerer/infrastructure/storage_provider/exceptions.py +78 -0
  38. sourcerer/infrastructure/storage_provider/registry.py +84 -0
  39. sourcerer/infrastructure/storage_provider/services.py +509 -0
  40. sourcerer/infrastructure/utils.py +106 -0
  41. sourcerer/presentation/__init__.py +12 -0
  42. sourcerer/presentation/app.py +36 -0
  43. sourcerer/presentation/di_container.py +46 -0
  44. sourcerer/presentation/screens/__init__.py +0 -0
  45. sourcerer/presentation/screens/critical_error/__init__.py +0 -0
  46. sourcerer/presentation/screens/critical_error/main.py +78 -0
  47. sourcerer/presentation/screens/critical_error/styles.tcss +41 -0
  48. sourcerer/presentation/screens/file_system_finder/main.py +248 -0
  49. sourcerer/presentation/screens/file_system_finder/styles.tcss +44 -0
  50. sourcerer/presentation/screens/file_system_finder/widgets/__init__.py +0 -0
  51. sourcerer/presentation/screens/file_system_finder/widgets/file_system_navigator.py +810 -0
  52. sourcerer/presentation/screens/main/__init__.py +0 -0
  53. sourcerer/presentation/screens/main/main.py +469 -0
  54. sourcerer/presentation/screens/main/messages/__init__.py +0 -0
  55. sourcerer/presentation/screens/main/messages/delete_request.py +12 -0
  56. sourcerer/presentation/screens/main/messages/download_request.py +12 -0
  57. sourcerer/presentation/screens/main/messages/preview_request.py +10 -0
  58. sourcerer/presentation/screens/main/messages/resizing_rule.py +21 -0
  59. sourcerer/presentation/screens/main/messages/select_storage_item.py +11 -0
  60. sourcerer/presentation/screens/main/messages/uncheck_files_request.py +8 -0
  61. sourcerer/presentation/screens/main/messages/upload_request.py +10 -0
  62. sourcerer/presentation/screens/main/mixins/__init__.py +0 -0
  63. sourcerer/presentation/screens/main/mixins/resize_containers_watcher_mixin.py +144 -0
  64. sourcerer/presentation/screens/main/styles.tcss +32 -0
  65. sourcerer/presentation/screens/main/widgets/__init__.py +0 -0
  66. sourcerer/presentation/screens/main/widgets/gradient.py +45 -0
  67. sourcerer/presentation/screens/main/widgets/resizing_rule.py +67 -0
  68. sourcerer/presentation/screens/main/widgets/storage_content.py +691 -0
  69. sourcerer/presentation/screens/main/widgets/storage_list_sidebar.py +143 -0
  70. sourcerer/presentation/screens/preview_content/__init__.py +0 -0
  71. sourcerer/presentation/screens/preview_content/main.py +59 -0
  72. sourcerer/presentation/screens/preview_content/styles.tcss +26 -0
  73. sourcerer/presentation/screens/provider_creds_list/__init__.py +0 -0
  74. sourcerer/presentation/screens/provider_creds_list/main.py +164 -0
  75. sourcerer/presentation/screens/provider_creds_list/styles.tcss +65 -0
  76. sourcerer/presentation/screens/provider_creds_registration/__init__.py +0 -0
  77. sourcerer/presentation/screens/provider_creds_registration/main.py +264 -0
  78. sourcerer/presentation/screens/provider_creds_registration/styles.tcss +42 -0
  79. sourcerer/presentation/screens/question/__init__.py +0 -0
  80. sourcerer/presentation/screens/question/main.py +31 -0
  81. sourcerer/presentation/screens/question/styles.tcss +33 -0
  82. sourcerer/presentation/screens/shared/__init__.py +0 -0
  83. sourcerer/presentation/screens/shared/containers.py +13 -0
  84. sourcerer/presentation/screens/shared/widgets/__init__.py +0 -0
  85. sourcerer/presentation/screens/shared/widgets/button.py +54 -0
  86. sourcerer/presentation/screens/shared/widgets/labeled_input.py +80 -0
  87. sourcerer/presentation/screens/storage_action_progress/__init__.py +0 -0
  88. sourcerer/presentation/screens/storage_action_progress/main.py +476 -0
  89. sourcerer/presentation/screens/storage_action_progress/styles.tcss +43 -0
  90. sourcerer/presentation/settings.py +31 -0
  91. sourcerer/presentation/themes/__init__.py +0 -0
  92. sourcerer/presentation/themes/github_dark.py +21 -0
  93. sourcerer/presentation/utils.py +69 -0
  94. sourcerer/settings.py +72 -0
  95. sourcerer/utils.py +32 -0
@@ -0,0 +1,476 @@
1
+ import os
2
+ from concurrent.futures import ThreadPoolExecutor, as_completed
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import List
6
+
7
+ from rich.text import Text
8
+ from textual import on
9
+ from textual.app import ComposeResult
10
+ from textual.color import Gradient
11
+ from textual.containers import Container, VerticalScroll, Horizontal, Center
12
+ from textual.css.query import NoMatches
13
+ from textual.screen import ModalScreen
14
+ from textual.widgets import ProgressBar, Label, Rule
15
+
16
+ from sourcerer.domain.storage_provider.services import BaseStorageProviderService
17
+ from sourcerer.presentation.screens.question.main import QuestionScreen
18
+ from sourcerer.presentation.screens.shared.widgets.button import Button
19
+ from sourcerer.settings import MAX_PARALLEL_DOWNLOADS
20
+
21
+ gradient = Gradient.from_colors(
22
+ "#881177",
23
+ "#aa3355",
24
+ "#cc6666",
25
+ "#ee9944",
26
+ "#eedd00",
27
+ "#99dd55",
28
+ "#44dd88",
29
+ "#22ccbb",
30
+ "#00bbcc",
31
+ "#0099cc",
32
+ "#3366bb",
33
+ "#663399",
34
+ )
35
+
36
+ gradient2 = Gradient.from_colors(
37
+ "#aa3355",
38
+ "#663399",
39
+ "#0099cc",
40
+ )
41
+
42
+
43
+ @dataclass
44
+ class Key:
45
+ """
46
+ Base class for representing a key in storage operations.
47
+
48
+ Attributes:
49
+ display_name (str): Name to display in the UI
50
+ uuid (str): Unique identifier for the key
51
+ path (str): Path to the file or directory
52
+ """
53
+
54
+ display_name: str
55
+ uuid: str
56
+ path: Path
57
+
58
+
59
+ class DownloadKey(Key):
60
+ """
61
+ Represents a key for download operations.
62
+
63
+ Inherits all attributes from the base Key class.
64
+ """
65
+
66
+
67
+ class DeleteKey(Key):
68
+ """
69
+ Represents a key for delete operations.
70
+
71
+ Inherits all attributes from the base Key class.
72
+ """
73
+
74
+
75
+ @dataclass
76
+ class UploadKey(Key):
77
+ """
78
+ Represents a key for upload operations.
79
+
80
+ Inherits all attributes from the base Key class and adds destination path.
81
+
82
+ Attributes:
83
+ dest_path (str): Destination path for the uploaded file
84
+ """
85
+
86
+ dest_path: str
87
+
88
+
89
+ class StorageActionProgressScreen(ModalScreen):
90
+ """
91
+ A modal screen that displays progress for storage operations (download, upload, delete).
92
+
93
+ This screen shows progress bars for the overall operation and for individual files,
94
+ allowing users to monitor and cancel operations in progress.
95
+ """
96
+
97
+ CSS_PATH = "styles.tcss"
98
+
99
+ def __init__(
100
+ self,
101
+ storage_name: str,
102
+ path: str,
103
+ provider_service: BaseStorageProviderService | None,
104
+ keys: List[UploadKey | DownloadKey | DeleteKey],
105
+ action: str,
106
+ *args,
107
+ **kwargs,
108
+ ):
109
+ """
110
+ Initialize the storage action progress screen.
111
+
112
+ Args:
113
+ storage_name (str): Name of the storage being operated on
114
+ path (str): Path within the storage
115
+ provider_service (BaseStorageProviderService | None): Service for interacting with the storage provider
116
+ keys (List[Key]): List of keys representing files/folders to process
117
+ action (str): Type of action being performed ('download', 'upload', or 'delete')
118
+ *args: Additional positional arguments to pass to parent class
119
+ **kwargs: Additional keyword arguments to pass to parent class
120
+ """
121
+ super().__init__(*args, **kwargs)
122
+ self.storage_name = storage_name
123
+ self.provider_service = provider_service
124
+ self.action = action
125
+ self.path = path
126
+ self.keys = keys
127
+ self.files_has_been_processed = False
128
+ self.active_worker = None
129
+ self.active_executor = None
130
+
131
+ def compose(self) -> ComposeResult:
132
+ """
133
+ Compose the UI elements of the screen.
134
+
135
+ Creates a container with a main progress bar, individual progress bars for each file,
136
+ and a cancel button.
137
+
138
+ Returns:
139
+ ComposeResult: The composed UI elements
140
+ """
141
+ with Container(id="StorageActionProgress"):
142
+ with Center():
143
+ yield ProgressBar(
144
+ show_eta=False,
145
+ total=len(self.keys),
146
+ id="progress_bar",
147
+ gradient=gradient2,
148
+ )
149
+
150
+ yield Rule()
151
+
152
+ with VerticalScroll(id="progress_files"):
153
+ for key in self.keys:
154
+ with Horizontal(
155
+ classes="progress_file", id=f"progress_files_{key.uuid}"
156
+ ):
157
+ yield Label(
158
+ Text(key.display_name, overflow="ellipsis"),
159
+ id=f"progress_file_{key.uuid}",
160
+ classes="label",
161
+ ).with_tooltip(key.display_name)
162
+ yield ProgressBar(
163
+ total=1,
164
+ show_percentage=False,
165
+ id=f"progress_bar_{key.uuid}",
166
+ gradient=gradient2,
167
+ )
168
+ if Path(key.path).is_dir():
169
+ with Horizontal(classes="progress_file_details"):
170
+ yield Label(
171
+ "",
172
+ id=f"progress_file_details_{key.uuid}",
173
+ classes="label",
174
+ )
175
+ with Horizontal(id="controls"):
176
+ yield Button("Cancel", name="cancel")
177
+
178
+ def on_mount(self) -> None:
179
+ """
180
+ Handle the mount event when the screen is first displayed.
181
+
182
+ Sets the border title and starts the appropriate worker thread based on the action type
183
+ (download, delete, or upload).
184
+ """
185
+ self.query_one("#StorageActionProgress").border_title = (
186
+ f"{self.action.capitalize()} {len(self.keys)} files from {self.storage_name}"
187
+ )
188
+
189
+ if self.action == "download":
190
+ self.active_worker = self.run_worker(self.download_files, thread=True)
191
+ elif self.action == "delete":
192
+ self.action_worker = self.run_worker(self.delete_files, thread=True)
193
+ elif self.action == "upload":
194
+ self.action_worker = self.run_worker(self.upload_files, thread=True)
195
+
196
+ @on(Button.Click)
197
+ def on_button_click(self, event: Button.Click) -> None:
198
+ """
199
+ Handle button click events.
200
+
201
+ Processes the cancel button click by either dismissing the screen if processing is complete
202
+ or showing a confirmation dialog if processing is still in progress.
203
+
204
+ Args:
205
+ event (Button.Click): The button click event
206
+ """
207
+ if event.action != "cancel":
208
+ return
209
+
210
+ if self.files_has_been_processed:
211
+ self.dismiss()
212
+ return
213
+
214
+ self.app.push_screen(
215
+ QuestionScreen("Are you sure you want to cancel process?"),
216
+ callback=self.exit_callback,
217
+ )
218
+
219
+ def exit_callback(self, result):
220
+ """
221
+ Callback function to handle the result of the confirmation dialog.
222
+ If the user confirms, it cancels the ongoing operation and dismisses the screen.
223
+ Args:
224
+ result (bool): The result of the confirmation dialog
225
+
226
+ """
227
+ if not result:
228
+ return
229
+ if self.active_executor:
230
+ self.active_executor.shutdown(cancel_futures=True)
231
+ self.active_executor = None
232
+ if self.active_worker:
233
+ self.active_worker.cancel()
234
+ self.active_worker = None
235
+ self.dismiss()
236
+
237
+ async def download_files(self):
238
+ """
239
+ Download files from storage and update progress bars.
240
+ This method handles the download of multiple files, updating progress bars
241
+ and handling various error conditions that might occur during the process.
242
+ """
243
+ main_progress_bar = self.query_one("#progress_bar")
244
+ failed_downloads = []
245
+
246
+ with ThreadPoolExecutor(max_workers=MAX_PARALLEL_DOWNLOADS) as executor:
247
+ self.active_executor = executor
248
+ futures = [
249
+ executor.submit(
250
+ self.download_file,
251
+ os.path.join(self.path, key.path) if self.path else key.path,
252
+ key.uuid,
253
+ main_progress_bar,
254
+ )
255
+ for key in self.keys
256
+ ]
257
+
258
+ for future in as_completed(futures):
259
+ if future.exception():
260
+ failed_downloads.append(future)
261
+ self.files_has_been_processed = True
262
+
263
+ self.active_executor = None
264
+ if failed_downloads:
265
+ self.notify(
266
+ f"Failed to download {len(failed_downloads)} files", severity="error"
267
+ )
268
+
269
+ def download_file(self, key, uuid, main_progress_bar):
270
+ """
271
+ Download a file from storage and update progress bars.
272
+
273
+ This method handles the download of a single file, updating progress bars
274
+ and handling various error conditions that might occur during the process.
275
+
276
+ Args:
277
+ key (str): The key/path of the file to download
278
+ uuid (str): Unique identifier for the file
279
+ main_progress_bar (ProgressBar): The main progress bar to update
280
+ """
281
+
282
+ if not self.provider_service:
283
+ self.notify(f"Failed to download {key}", severity="error")
284
+ return
285
+
286
+ def progress_callback(progress_bar, chunk):
287
+ progress_bar.advance(chunk)
288
+
289
+ progress_bar = self.query_one(f"#progress_bar_{uuid}")
290
+
291
+ try:
292
+ # Step 1: Get file size
293
+ try:
294
+ file_size = self.provider_service.get_file_size(self.storage_name, key)
295
+ progress_bar.total = file_size # type: ignore
296
+ except Exception as ex:
297
+ self.notify(
298
+ f"Failed to get file size for {key}: {str(ex)}", severity="error"
299
+ )
300
+ self.log.error(f"Error getting file size: {ex}")
301
+ return
302
+
303
+ # Step 2: Download the file
304
+ try:
305
+ self.provider_service.download_storage_item(
306
+ self.storage_name,
307
+ key,
308
+ lambda chunk: progress_callback(progress_bar, chunk),
309
+ )
310
+ except Exception as ex:
311
+ self.notify(f"Failed to download {key}: {str(ex)}", severity="error")
312
+ self.log.error(f"Error downloading file: {ex}")
313
+ return
314
+
315
+ # Step 3: Ensure progress bar is complete
316
+ if progress_bar.progress != progress_bar.total: # type: ignore
317
+ try:
318
+ progress_bar.progress = file_size # type: ignore
319
+ except Exception as ex:
320
+ self.log.error(f"Error updating progress bar: {ex}")
321
+ # Non-critical error, continue execution
322
+ except Exception as ex:
323
+ # Catch any unexpected exceptions
324
+ self.notify(
325
+ f"Unexpected error downloading {key}: {str(ex)}", severity="error"
326
+ )
327
+ self.log.error(f"Unexpected error: {ex}")
328
+ finally:
329
+ main_progress_bar.advance(1)
330
+
331
+ async def delete_files(self):
332
+ """
333
+ Delete files from storage and update progress bars.
334
+
335
+ This method handles the deletion of multiple files, updating progress bars
336
+ and handling various error conditions that might occur during the process.
337
+ """
338
+ main_progress_bar = self.query_one("#progress_bar")
339
+ failed_downloads = []
340
+
341
+ with ThreadPoolExecutor(max_workers=MAX_PARALLEL_DOWNLOADS) as executor:
342
+ self.active_executor = executor
343
+ futures = [
344
+ executor.submit(
345
+ self.delete_file,
346
+ os.path.join(self.path, key.path) if self.path else key.path,
347
+ key.uuid,
348
+ main_progress_bar,
349
+ )
350
+ for key in self.keys
351
+ ]
352
+
353
+ for future in as_completed(futures):
354
+ if future.exception():
355
+ failed_downloads.append(future)
356
+ self.files_has_been_processed = True
357
+
358
+ self.active_executor = None
359
+
360
+ def delete_file(self, key, uuid, main_progress_bar):
361
+ """
362
+ Delete a file from storage and update progress bars.
363
+
364
+ This method handles the deletion of a single file, updating progress bars
365
+ and handling various error conditions that might occur during the process.
366
+
367
+ Args:
368
+ key (str): The key/path of the file to delete
369
+ uuid (str): Unique identifier for the file
370
+ main_progress_bar (ProgressBar): The main progress bar to update
371
+ """ ""
372
+ if not self.provider_service:
373
+ self.notify(f"Failed to delete {key}", severity="error")
374
+ main_progress_bar.advance(1)
375
+ return
376
+
377
+ progress_bar = self.query_one(f"#progress_bar_{uuid}")
378
+ try:
379
+ progress_bar.total = 1 # type: ignore
380
+ self.provider_service.delete_storage_item(self.storage_name, key)
381
+ progress_bar.advance(1) # type: ignore
382
+ except Exception:
383
+ self.notify(f"Failed to delete {key}", severity="error")
384
+ raise
385
+ finally:
386
+ main_progress_bar.advance(1)
387
+
388
+ async def upload_files(self):
389
+ """
390
+ Upload files to storage and update progress bars.
391
+
392
+ This method handles the upload of files, updating progress bars and
393
+ handling various error conditions that might occur during the process.
394
+ """
395
+ main_progress_bar = self.query_one("#progress_bar")
396
+ failed_downloads = []
397
+ if not self.provider_service:
398
+ self.notify("Failed to upload files", severity="error")
399
+ return
400
+
401
+ for key in self.keys:
402
+ source_path = Path(key.path)
403
+ if source_path.is_file():
404
+ self.provider_service.upload_storage_item(
405
+ storage=self.storage_name,
406
+ source_path=key.path,
407
+ dest_path=str(Path(self.path) / key.dest_path) if self.path else key.dest_path, # type: ignore
408
+ )
409
+ self.files_has_been_processed = True
410
+ elif source_path.is_dir():
411
+
412
+ files_n = len([i for i in source_path.rglob("*") if i.is_file()])
413
+ progress_bar = self.query_one(f"#progress_bar_{key.uuid}")
414
+ progress_bar.total = files_n # type: ignore
415
+ with ThreadPoolExecutor(max_workers=MAX_PARALLEL_DOWNLOADS) as executor:
416
+ self.active_executor = executor
417
+ futures = [
418
+ executor.submit(
419
+ self.upload_file,
420
+ obj,
421
+ Path(obj).relative_to(source_path),
422
+ os.path.join(
423
+ source_path.name,
424
+ str(Path(obj).relative_to(source_path)),
425
+ ),
426
+ key.uuid,
427
+ )
428
+ for obj in source_path.rglob("*")
429
+ if obj.is_file()
430
+ ]
431
+
432
+ for future in as_completed(futures):
433
+ if future.exception():
434
+ failed_downloads.append(future)
435
+ self.files_has_been_processed = True
436
+ self.active_executor = None
437
+ try:
438
+ self.query_one(f"#progress_file_details_{key.uuid}").remove()
439
+ except Exception:
440
+ self.log(f"Failed to remove progress details for {key.uuid}")
441
+ main_progress_bar.advance(1) # type: ignore
442
+
443
+ def upload_file(self, source, rel_source, destination, uuid):
444
+ """
445
+ Upload a file to storage and update progress bars.
446
+
447
+ This method handles the upload of a single file, updating progress bars
448
+ and handling various error conditions that might occur during the process.
449
+ Args:
450
+ source (str): The source path of the file to upload
451
+ rel_source (str): The relative path of the file in the source directory
452
+ destination (str): The destination path in the storage
453
+ uuid (str): Unique identifier for the file
454
+ """
455
+ if not self.provider_service:
456
+ self.notify(f"Failed to upload {source}", severity="error")
457
+ return
458
+ progress_bar = self.query_one(f"#progress_bar_{uuid}")
459
+ details_container = None
460
+ try:
461
+ details_container = self.query_one(f"#progress_file_details_{uuid}")
462
+ except NoMatches:
463
+ pass
464
+ if details_container:
465
+ details_container.update(Text(f"({rel_source})", overflow="ellipsis")) # type: ignore
466
+ try:
467
+ self.provider_service.upload_storage_item(
468
+ storage=self.storage_name,
469
+ source_path=source,
470
+ dest_path=(
471
+ str(Path(self.path) / destination) if self.path else destination
472
+ ),
473
+ )
474
+ progress_bar.advance(1) # type: ignore
475
+ except Exception:
476
+ self.notify(f"Failed to upload {source}", severity="error")
@@ -0,0 +1,43 @@
1
+
2
+ Container {
3
+ height: auto;
4
+ }
5
+
6
+
7
+ StorageActionProgressScreen {
8
+ align: center middle;
9
+ content-align: center top;
10
+
11
+
12
+ & > #StorageActionProgress {
13
+ padding: 1 2 0 2;
14
+ margin: 0 0;
15
+ width: 70;
16
+ height: 25;
17
+ border: solid $secondary-background;
18
+ border-title-color: $primary-lighten-2;
19
+
20
+ #progress_files {
21
+ padding-top: 1;
22
+ }
23
+
24
+ .progress_file_details {
25
+ align-horizontal: right;
26
+ color: $panel-lighten-3;
27
+ padding-right: 2;
28
+ }
29
+
30
+ .progress_file {
31
+ height: auto;
32
+
33
+ Label {
34
+ width: 30;
35
+ text-overflow: ellipsis;
36
+ text-wrap: nowrap;
37
+ padding-right: 1;
38
+ }
39
+ }
40
+
41
+ }
42
+ }
43
+
@@ -0,0 +1,31 @@
1
+ """Application settings and constants.
2
+
3
+ This module contains various configuration constants and settings used throughout
4
+ the application, including UI elements and display configurations.
5
+ """
6
+
7
+ NO_DATA_LOGO = """
8
+ ### ###
9
+ ### ###
10
+ #### ####
11
+ #### ####
12
+ #######################
13
+ ###### ######### ######
14
+ ###### ######### ######
15
+ ###################################
16
+ ###################################
17
+ ### ####################### ###
18
+ ### ####################### ###
19
+ ### ### ### ###
20
+ ####### #######
21
+ ####### #######
22
+
23
+
24
+ ### ## ## #
25
+ #### ## #### ###### #### #### #####
26
+ ## ## ## ## ## ## ## #### # ####
27
+ ## #### ## ## ## ## ## ## # ## ##
28
+ ## ## #### ##### ###### ### ### #
29
+ """
30
+
31
+ MIN_SECTION_DIMENSION = 10
File without changes
@@ -0,0 +1,21 @@
1
+ from textual.theme import Theme
2
+
3
+ github_dark_theme = Theme(
4
+ name="github-dark",
5
+ primary="#FFA656",
6
+ secondary="#81A1C1",
7
+ accent="#B48EAD",
8
+ foreground="#CDD9E5",
9
+ background="#1C2128",
10
+ success="#A3BE8C",
11
+ warning="#EBCB8B",
12
+ error="#BF616A",
13
+ surface="#22272E",
14
+ panel="#434C5E",
15
+ dark=True,
16
+ variables={
17
+ "block-cursor-text-style": "none",
18
+ "footer-key-foreground": "#88C0D0",
19
+ "input-selection-background": "#81a1c1 35%",
20
+ },
21
+ )
@@ -0,0 +1,69 @@
1
+ """
2
+ Utility functions for the presentation layer.
3
+
4
+ This module provides helper functions for the presentation layer,
5
+ particularly for retrieving and initializing storage provider services.
6
+ """
7
+
8
+ from sourcerer.domain.storage_provider.services import BaseStorageProviderService
9
+ from sourcerer.infrastructure.access_credentials.registry import (
10
+ access_credential_method_registry,
11
+ )
12
+ from sourcerer.infrastructure.storage_provider.registry import storage_provider_registry
13
+
14
+
15
+ def get_provider_service_by_access_uuid(
16
+ uuid, credentials_service
17
+ ) -> BaseStorageProviderService | None:
18
+ """
19
+ Retrieves the provider service associated with the given access credentials UUID.
20
+
21
+ Args:
22
+ uuid (str): The UUID of the access credentials.
23
+ credentials_service: Credentials service
24
+
25
+ Returns:
26
+ The provider service instance corresponding to the access credentials.
27
+ """
28
+ access_credentials = credentials_service.get(uuid)
29
+ return get_provider_service_by_access_credentials(access_credentials)
30
+
31
+
32
+ def get_provider_service_by_access_credentials(
33
+ credentials,
34
+ ) -> BaseStorageProviderService | None:
35
+ """
36
+ Retrieves a storage provider service instance using the given access credentials.
37
+
38
+ Args:
39
+ credentials: An object containing provider and credentials type information.
40
+
41
+ Returns:
42
+ An instance of the storage provider service if both the credentials service
43
+ and provider service class are found; otherwise, returns None.
44
+
45
+ Flow:
46
+ 1. Fetch the credentials service using the provider and credentials type.
47
+ 2. If the credentials service is not found, return None.
48
+ 3. Fetch the provider service class using the provider.
49
+ 4. If the provider service class is not found, return None.
50
+ 5. Authenticate the credentials using the credentials service.
51
+ 6. Return an instance of the provider service class initialized with the
52
+ authenticated credentials.
53
+ """
54
+
55
+ credentials_service = access_credential_method_registry.get_by_provider_and_name(
56
+ credentials.provider, credentials.credentials_type
57
+ )
58
+
59
+ if not credentials_service:
60
+ return
61
+
62
+ provider_service_class = storage_provider_registry.get_by_provider(
63
+ credentials.provider
64
+ )
65
+ if not provider_service_class:
66
+ return
67
+
68
+ auth_credentials = credentials_service().authenticate(credentials.credentials)
69
+ return provider_service_class(auth_credentials)