data-sourcerer 0.1.0__py3-none-any.whl → 0.2.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.1.0.dist-info → data_sourcerer-0.2.0.dist-info}/METADATA +25 -4
- {data_sourcerer-0.1.0.dist-info → data_sourcerer-0.2.0.dist-info}/RECORD +53 -50
- {data_sourcerer-0.1.0.dist-info → data_sourcerer-0.2.0.dist-info}/WHEEL +1 -1
- sourcerer/__init__.py +1 -1
- sourcerer/domain/access_credentials/entities.py +17 -0
- sourcerer/domain/access_credentials/exceptions.py +1 -1
- sourcerer/domain/access_credentials/repositories.py +1 -1
- sourcerer/domain/access_credentials/services.py +14 -2
- sourcerer/domain/file_system/exceptions.py +1 -1
- sourcerer/domain/file_system/services.py +2 -2
- sourcerer/domain/shared/entities.py +1 -0
- sourcerer/domain/storage_provider/entities.py +3 -4
- sourcerer/domain/storage_provider/exceptions.py +1 -1
- sourcerer/domain/storage_provider/services.py +13 -9
- sourcerer/infrastructure/access_credentials/exceptions.py +15 -2
- sourcerer/infrastructure/access_credentials/registry.py +3 -4
- sourcerer/infrastructure/access_credentials/services.py +141 -44
- sourcerer/infrastructure/db/models.py +1 -1
- sourcerer/infrastructure/file_system/exceptions.py +9 -9
- sourcerer/infrastructure/file_system/services.py +16 -16
- sourcerer/infrastructure/storage_provider/exceptions.py +28 -8
- sourcerer/infrastructure/storage_provider/registry.py +2 -3
- sourcerer/infrastructure/storage_provider/services/__init__.py +0 -0
- sourcerer/infrastructure/storage_provider/services/azure.py +261 -0
- sourcerer/infrastructure/storage_provider/services/gcp.py +277 -0
- sourcerer/infrastructure/storage_provider/services/s3.py +290 -0
- sourcerer/infrastructure/utils.py +2 -4
- sourcerer/presentation/screens/critical_error/main.py +3 -4
- sourcerer/presentation/screens/file_system_finder/main.py +4 -4
- sourcerer/presentation/screens/file_system_finder/widgets/file_system_navigator.py +12 -12
- sourcerer/presentation/screens/main/main.py +57 -33
- sourcerer/presentation/screens/main/messages/delete_request.py +1 -2
- sourcerer/presentation/screens/main/messages/download_request.py +1 -2
- sourcerer/presentation/screens/main/mixins/resize_containers_watcher_mixin.py +3 -3
- sourcerer/presentation/screens/main/widgets/gradient.py +2 -5
- sourcerer/presentation/screens/main/widgets/resizing_rule.py +1 -1
- sourcerer/presentation/screens/main/widgets/storage_content.py +12 -13
- sourcerer/presentation/screens/main/widgets/storage_list_sidebar.py +8 -6
- sourcerer/presentation/screens/preview_content/main.py +15 -4
- sourcerer/presentation/screens/preview_content/styles.tcss +2 -1
- sourcerer/presentation/screens/provider_creds_list/main.py +2 -2
- sourcerer/presentation/screens/provider_creds_registration/main.py +26 -11
- sourcerer/presentation/screens/question/main.py +1 -1
- sourcerer/presentation/screens/shared/containers.py +1 -1
- sourcerer/presentation/screens/shared/widgets/labeled_input.py +1 -1
- sourcerer/presentation/screens/storage_action_progress/main.py +34 -20
- sourcerer/presentation/screens/storage_action_progress/styles.tcss +11 -0
- sourcerer/presentation/utils.py +7 -3
- sourcerer/settings.py +4 -0
- sourcerer/utils.py +2 -2
- sourcerer/infrastructure/storage_provider/services.py +0 -509
- {data_sourcerer-0.1.0.dist-info → data_sourcerer-0.2.0.dist-info}/entry_points.txt +0 -0
- {data_sourcerer-0.1.0.dist-info → data_sourcerer-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {data_sourcerer-0.1.0.dist-info → data_sourcerer-0.2.0.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ including files and folders. It handles file selection, navigation, and content
|
|
5
5
|
display with search functionality.
|
6
6
|
"""
|
7
7
|
|
8
|
+
import contextlib
|
8
9
|
import os.path
|
9
10
|
from dataclasses import dataclass
|
10
11
|
from enum import Enum, auto
|
@@ -12,17 +13,17 @@ from enum import Enum, auto
|
|
12
13
|
from textual import events, on
|
13
14
|
from textual.app import ComposeResult
|
14
15
|
from textual.containers import (
|
15
|
-
VerticalScroll,
|
16
|
-
Horizontal,
|
17
16
|
Center,
|
18
|
-
Middle,
|
19
17
|
Container,
|
18
|
+
Horizontal,
|
19
|
+
Middle,
|
20
20
|
Vertical,
|
21
|
+
VerticalScroll,
|
21
22
|
)
|
22
23
|
from textual.css.query import NoMatches
|
23
24
|
from textual.message import Message
|
24
25
|
from textual.reactive import reactive
|
25
|
-
from textual.widgets import
|
26
|
+
from textual.widgets import Checkbox, Input, Label, Static
|
26
27
|
|
27
28
|
from sourcerer.domain.storage_provider.entities import StorageContent
|
28
29
|
from sourcerer.presentation.screens.main.messages.delete_request import DeleteRequest
|
@@ -40,11 +41,11 @@ from sourcerer.presentation.screens.main.messages.upload_request import UploadRe
|
|
40
41
|
from sourcerer.presentation.screens.shared.widgets.button import Button
|
41
42
|
from sourcerer.presentation.settings import NO_DATA_LOGO
|
42
43
|
from sourcerer.settings import (
|
43
|
-
UPLOAD_ICON,
|
44
|
-
DOWNLOAD_ICON,
|
45
|
-
PREVIEW_ICON,
|
46
44
|
DIRECTORY_ICON,
|
45
|
+
DOWNLOAD_ICON,
|
47
46
|
FILE_ICON,
|
47
|
+
PREVIEW_ICON,
|
48
|
+
UPLOAD_ICON,
|
48
49
|
)
|
49
50
|
|
50
51
|
|
@@ -273,10 +274,9 @@ class FileItem(Horizontal):
|
|
273
274
|
def on_click(self, event: events.Click) -> None:
|
274
275
|
"""Handle click events to toggle file selection."""
|
275
276
|
preview_button = None
|
276
|
-
|
277
|
+
with contextlib.suppress(NoMatches):
|
277
278
|
preview_button = self.query_one(Button)
|
278
|
-
|
279
|
-
pass
|
279
|
+
|
280
280
|
if event.widget is preview_button:
|
281
281
|
self.post_message(self.Preview(self.file.key))
|
282
282
|
return
|
@@ -491,9 +491,8 @@ class StorageContentContainer(Vertical):
|
|
491
491
|
if not self.storage_content or (
|
492
492
|
not self.storage_content.files and not self.storage_content.folders
|
493
493
|
):
|
494
|
-
with Middle():
|
495
|
-
|
496
|
-
yield Static(NO_DATA_LOGO)
|
494
|
+
with Middle(), Center():
|
495
|
+
yield Static(NO_DATA_LOGO)
|
497
496
|
return
|
498
497
|
with Horizontal(classes="file_list_header"):
|
499
498
|
yield FileMetaLabel("Name", classes="file_name")
|
@@ -7,11 +7,10 @@ and selection of storage items.
|
|
7
7
|
|
8
8
|
from collections import namedtuple
|
9
9
|
from itertools import groupby
|
10
|
-
from typing import Dict, List
|
11
10
|
|
12
11
|
from textual import events
|
13
12
|
from textual.app import ComposeResult
|
14
|
-
from textual.containers import
|
13
|
+
from textual.containers import Container, Horizontal, VerticalScroll
|
15
14
|
from textual.reactive import reactive
|
16
15
|
from textual.widgets import Label, Rule
|
17
16
|
|
@@ -25,7 +24,7 @@ from sourcerer.presentation.screens.main.widgets.gradient import GradientWidget
|
|
25
24
|
STORAGE_ICONS = {
|
26
25
|
StorageProvider.S3: "🟠",
|
27
26
|
StorageProvider.GoogleCloudStorage: "🔵",
|
28
|
-
|
27
|
+
StorageProvider.AzureStorage: "⚪️",
|
29
28
|
}
|
30
29
|
"""Mapping of storage provider types to their display icons."""
|
31
30
|
|
@@ -84,7 +83,8 @@ class StorageListSidebar(VerticalScroll):
|
|
84
83
|
storages: Dictionary mapping provider types to lists of storage instances
|
85
84
|
"""
|
86
85
|
|
87
|
-
storages: reactive[
|
86
|
+
storages: reactive[dict[str, list[Storage]]] = reactive({})
|
87
|
+
last_update_timestamp: reactive[float] = reactive(0, recompose=True)
|
88
88
|
|
89
89
|
DEFAULT_CSS = """
|
90
90
|
StorageListSidebar {
|
@@ -125,7 +125,9 @@ class StorageListSidebar(VerticalScroll):
|
|
125
125
|
]
|
126
126
|
storages = sorted(storages, key=lambda x: x.storage.storage)
|
127
127
|
|
128
|
-
for letter,
|
128
|
+
for letter, storages_group in groupby(
|
129
|
+
storages, key=lambda x: x.storage.storage[0]
|
130
|
+
):
|
129
131
|
with Container(id=f"group-{letter}", classes="storage-group"):
|
130
132
|
yield Horizontal(
|
131
133
|
Rule(id="rule-left"),
|
@@ -133,7 +135,7 @@ class StorageListSidebar(VerticalScroll):
|
|
133
135
|
Rule(),
|
134
136
|
)
|
135
137
|
|
136
|
-
for item in
|
138
|
+
for item in storages_group:
|
137
139
|
yield StorageItem(
|
138
140
|
renderable=STORAGE_ICONS.get(item.storage.provider, "")
|
139
141
|
+ " "
|
@@ -1,13 +1,15 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
1
3
|
from rich.syntax import Syntax
|
2
4
|
from textual import on
|
3
5
|
from textual.app import ComposeResult
|
4
6
|
from textual.containers import Container, Horizontal
|
5
7
|
from textual.screen import ModalScreen
|
6
|
-
from textual.widgets import
|
8
|
+
from textual.widgets import LoadingIndicator, RichLog
|
7
9
|
|
8
10
|
from sourcerer.infrastructure.access_credentials.services import CredentialsService
|
9
11
|
from sourcerer.infrastructure.storage_provider.exceptions import (
|
10
|
-
|
12
|
+
ReadStorageItemsError,
|
11
13
|
)
|
12
14
|
from sourcerer.presentation.screens.shared.widgets.button import Button
|
13
15
|
from sourcerer.presentation.utils import get_provider_service_by_access_uuid
|
@@ -42,13 +44,22 @@ class PreviewContentScreen(ModalScreen):
|
|
42
44
|
return
|
43
45
|
try:
|
44
46
|
content = provider_service.read_storage_item(self.storage_name, self.key)
|
45
|
-
except
|
47
|
+
except ReadStorageItemsError:
|
46
48
|
self.notify("Could not read file :(", severity="error")
|
47
49
|
return
|
48
50
|
self.query_one("#loading").remove()
|
51
|
+
if content is None:
|
52
|
+
self.notify("Empty file", severity="warning")
|
53
|
+
return
|
49
54
|
|
50
55
|
text_log = self.query_one(RichLog)
|
51
|
-
|
56
|
+
|
57
|
+
extension = Path(self.key).suffix
|
58
|
+
match extension:
|
59
|
+
case ".tfstate":
|
60
|
+
lexer = "json"
|
61
|
+
case _:
|
62
|
+
lexer = Syntax.guess_lexer(self.key, content)
|
52
63
|
content = Syntax(content, lexer, line_numbers=True, theme="ansi_dark")
|
53
64
|
text_log.write(content)
|
54
65
|
|
@@ -12,13 +12,14 @@ PreviewContentScreen {
|
|
12
12
|
& > #PreviewContentScreen {
|
13
13
|
padding: 1 2 0 2;
|
14
14
|
margin: 0 0;
|
15
|
-
width:
|
15
|
+
width: 100;
|
16
16
|
height: 40;
|
17
17
|
border: solid $secondary-background;
|
18
18
|
border-title-color: $primary-lighten-2;
|
19
19
|
|
20
20
|
RichLog {
|
21
21
|
background: transparent;
|
22
|
+
width: 95;
|
22
23
|
}
|
23
24
|
|
24
25
|
}
|
@@ -7,13 +7,13 @@ from textual.containers import Container, Horizontal, VerticalScroll
|
|
7
7
|
from textual.message import Message
|
8
8
|
from textual.reactive import reactive
|
9
9
|
from textual.screen import ModalScreen
|
10
|
-
from textual.widgets import
|
10
|
+
from textual.widgets import Checkbox, Label
|
11
11
|
|
12
12
|
from sourcerer.domain.access_credentials.entities import Credentials
|
13
13
|
from sourcerer.infrastructure.access_credentials.services import CredentialsService
|
14
14
|
from sourcerer.presentation.screens.provider_creds_registration.main import (
|
15
|
-
ProviderCredsRegistrationScreen,
|
16
15
|
ProviderCredentialsEntry,
|
16
|
+
ProviderCredsRegistrationScreen,
|
17
17
|
)
|
18
18
|
from sourcerer.presentation.screens.shared.widgets.button import Button
|
19
19
|
|
@@ -1,17 +1,19 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
2
|
from enum import Enum
|
3
|
-
from typing import List
|
4
3
|
|
5
4
|
from dependency_injector.wiring import Provide
|
6
5
|
from textual import on
|
7
6
|
from textual.app import ComposeResult
|
8
7
|
from textual.containers import Container, Horizontal, VerticalScroll
|
9
8
|
from textual.screen import ModalScreen
|
10
|
-
from textual.widgets import
|
9
|
+
from textual.widgets import Label, Select
|
11
10
|
|
12
11
|
from sourcerer.domain.access_credentials.services import (
|
13
|
-
BaseAccessCredentialsService,
|
14
12
|
AuthField,
|
13
|
+
BaseAccessCredentialsService,
|
14
|
+
)
|
15
|
+
from sourcerer.infrastructure.access_credentials.exceptions import (
|
16
|
+
MissingAuthFieldsError,
|
15
17
|
)
|
16
18
|
from sourcerer.presentation.di_container import DiContainer
|
17
19
|
from sourcerer.presentation.screens.shared.widgets.button import Button
|
@@ -44,10 +46,10 @@ class ProviderCredsRegistrationScreen(ModalScreen):
|
|
44
46
|
|
45
47
|
def __init__(
|
46
48
|
self,
|
49
|
+
*args,
|
47
50
|
credentials_type_registry=Provide[
|
48
51
|
DiContainer.config.access_credential_method_registry
|
49
52
|
],
|
50
|
-
*args,
|
51
53
|
**kwargs,
|
52
54
|
):
|
53
55
|
super().__init__(*args, **kwargs)
|
@@ -68,7 +70,7 @@ class ProviderCredsRegistrationScreen(ModalScreen):
|
|
68
70
|
yield Select(
|
69
71
|
options=(
|
70
72
|
(provider, provider)
|
71
|
-
for provider in self.provider_credentials_settings
|
73
|
+
for provider in self.provider_credentials_settings
|
72
74
|
),
|
73
75
|
name=self.PROVIDERS_NAME,
|
74
76
|
id=self.PROVIDER_SELECTOR_ID,
|
@@ -108,7 +110,7 @@ class ProviderCredsRegistrationScreen(ModalScreen):
|
|
108
110
|
|
109
111
|
# If multiple authentication methods exist, display a selection dropdown
|
110
112
|
if len(auth_methods) > 1:
|
111
|
-
options = [(auth_type, auth_type) for auth_type in auth_methods
|
113
|
+
options = [(auth_type, auth_type) for auth_type in auth_methods]
|
112
114
|
await self.query_one(f"#{self.SETTINGS_CONTAINER_ID}").mount(
|
113
115
|
Container(
|
114
116
|
Label("Auth method:", classes="form_label"),
|
@@ -178,11 +180,22 @@ class ProviderCredsRegistrationScreen(ModalScreen):
|
|
178
180
|
if event.action == ControlsEnum.CANCEL.name:
|
179
181
|
self.dismiss()
|
180
182
|
elif event.action == ControlsEnum.CREATE.name:
|
183
|
+
if not self.auth_method:
|
184
|
+
self.notify("Please select provider and auth method", severity="error")
|
185
|
+
return
|
186
|
+
|
181
187
|
auth_fields = self._get_auth_fields()
|
182
188
|
if not auth_fields:
|
183
189
|
self.notify("Please select provider and auth method", severity="error")
|
184
|
-
|
185
|
-
|
190
|
+
return
|
191
|
+
|
192
|
+
try:
|
193
|
+
self.auth_method.validate_auth_fields_values(auth_fields.fields)
|
194
|
+
except MissingAuthFieldsError as e:
|
195
|
+
self.notify(str(e), severity="error")
|
196
|
+
return
|
197
|
+
|
198
|
+
self.dismiss(auth_fields)
|
186
199
|
|
187
200
|
def _get_auth_fields(self) -> ProviderCredentialsEntry | None:
|
188
201
|
"""
|
@@ -193,13 +206,15 @@ class ProviderCredsRegistrationScreen(ModalScreen):
|
|
193
206
|
"""
|
194
207
|
if not self.auth_method:
|
195
208
|
return
|
209
|
+
|
196
210
|
fields = {
|
197
211
|
input_field.get().name: input_field.get().value
|
198
212
|
for input_field in self.query_one(
|
199
213
|
f"#{self.CREDENTIALS_FIELDS_CONTAINER_ID}"
|
200
214
|
).children
|
201
|
-
if isinstance(input_field, LabeledInput)
|
215
|
+
if isinstance(input_field, LabeledInput) and input_field.get().value
|
202
216
|
}
|
217
|
+
|
203
218
|
auth_name = self.query_one("#auth_name").get().value or "default" # type: ignore
|
204
219
|
|
205
220
|
return ProviderCredentialsEntry(
|
@@ -235,13 +250,13 @@ class ProviderCredsRegistrationScreen(ModalScreen):
|
|
235
250
|
self.auth_method = provider_auth_class
|
236
251
|
await self._mount_credentials_fields(provider_auth_class.auth_fields())
|
237
252
|
|
238
|
-
async def _mount_credentials_fields(self, fields:
|
253
|
+
async def _mount_credentials_fields(self, fields: list[AuthField]) -> None:
|
239
254
|
"""
|
240
255
|
Mounts a container of labeled input fields for credentials onto the settings container
|
241
256
|
and sets focus on the first input field.
|
242
257
|
|
243
258
|
Args:
|
244
|
-
fields (
|
259
|
+
fields (list[AuthField]): A list of AuthField objects containing key, label, and required attributes.
|
245
260
|
|
246
261
|
Returns:
|
247
262
|
None
|
@@ -1,19 +1,23 @@
|
|
1
|
+
import contextlib
|
1
2
|
import os
|
2
3
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
3
4
|
from dataclasses import dataclass
|
4
5
|
from pathlib import Path
|
5
|
-
from typing import List
|
6
6
|
|
7
7
|
from rich.text import Text
|
8
8
|
from textual import on
|
9
9
|
from textual.app import ComposeResult
|
10
10
|
from textual.color import Gradient
|
11
|
-
from textual.containers import
|
11
|
+
from textual.containers import Center, Container, Horizontal, VerticalScroll
|
12
12
|
from textual.css.query import NoMatches
|
13
|
+
from textual.reactive import reactive
|
13
14
|
from textual.screen import ModalScreen
|
14
|
-
from textual.widgets import
|
15
|
+
from textual.widgets import Label, ProgressBar, Rule
|
15
16
|
|
16
17
|
from sourcerer.domain.storage_provider.services import BaseStorageProviderService
|
18
|
+
from sourcerer.infrastructure.storage_provider.exceptions import (
|
19
|
+
UploadStorageItemsError,
|
20
|
+
)
|
17
21
|
from sourcerer.presentation.screens.question.main import QuestionScreen
|
18
22
|
from sourcerer.presentation.screens.shared.widgets.button import Button
|
19
23
|
from sourcerer.settings import MAX_PARALLEL_DOWNLOADS
|
@@ -96,12 +100,14 @@ class StorageActionProgressScreen(ModalScreen):
|
|
96
100
|
|
97
101
|
CSS_PATH = "styles.tcss"
|
98
102
|
|
103
|
+
files_has_been_processed = reactive(False)
|
104
|
+
|
99
105
|
def __init__(
|
100
106
|
self,
|
101
107
|
storage_name: str,
|
102
108
|
path: str,
|
103
109
|
provider_service: BaseStorageProviderService | None,
|
104
|
-
keys:
|
110
|
+
keys: list[UploadKey | DownloadKey | DeleteKey],
|
105
111
|
action: str,
|
106
112
|
*args,
|
107
113
|
**kwargs,
|
@@ -113,7 +119,7 @@ class StorageActionProgressScreen(ModalScreen):
|
|
113
119
|
storage_name (str): Name of the storage being operated on
|
114
120
|
path (str): Path within the storage
|
115
121
|
provider_service (BaseStorageProviderService | None): Service for interacting with the storage provider
|
116
|
-
keys (
|
122
|
+
keys (list[Key]): List of keys representing files/folders to process
|
117
123
|
action (str): Type of action being performed ('download', 'upload', or 'delete')
|
118
124
|
*args: Additional positional arguments to pass to parent class
|
119
125
|
**kwargs: Additional keyword arguments to pass to parent class
|
@@ -124,7 +130,6 @@ class StorageActionProgressScreen(ModalScreen):
|
|
124
130
|
self.action = action
|
125
131
|
self.path = path
|
126
132
|
self.keys = keys
|
127
|
-
self.files_has_been_processed = False
|
128
133
|
self.active_worker = None
|
129
134
|
self.active_executor = None
|
130
135
|
|
@@ -401,12 +406,19 @@ class StorageActionProgressScreen(ModalScreen):
|
|
401
406
|
for key in self.keys:
|
402
407
|
source_path = Path(key.path)
|
403
408
|
if source_path.is_file():
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
409
|
+
try:
|
410
|
+
self.provider_service.upload_storage_item(
|
411
|
+
storage=self.storage_name,
|
412
|
+
storage_path=self.path,
|
413
|
+
source_path=key.path,
|
414
|
+
dest_path=key.dest_path, # type: ignore
|
415
|
+
)
|
416
|
+
progress_bar = self.query_one(f"#progress_bar_{key.uuid}")
|
417
|
+
progress_bar.advance(1) # type: ignore
|
418
|
+
except UploadStorageItemsError as e:
|
419
|
+
self.notify(f"Failed to upload {key.path}: {e}", severity="error")
|
420
|
+
finally:
|
421
|
+
self.files_has_been_processed = True
|
410
422
|
elif source_path.is_dir():
|
411
423
|
|
412
424
|
files_n = len([i for i in source_path.rglob("*") if i.is_file()])
|
@@ -457,20 +469,22 @@ class StorageActionProgressScreen(ModalScreen):
|
|
457
469
|
return
|
458
470
|
progress_bar = self.query_one(f"#progress_bar_{uuid}")
|
459
471
|
details_container = None
|
460
|
-
|
472
|
+
with contextlib.suppress(NoMatches):
|
461
473
|
details_container = self.query_one(f"#progress_file_details_{uuid}")
|
462
|
-
except NoMatches:
|
463
|
-
pass
|
464
474
|
if details_container:
|
465
475
|
details_container.update(Text(f"({rel_source})", overflow="ellipsis")) # type: ignore
|
466
476
|
try:
|
467
477
|
self.provider_service.upload_storage_item(
|
468
478
|
storage=self.storage_name,
|
479
|
+
storage_path=self.path,
|
469
480
|
source_path=source,
|
470
|
-
dest_path=
|
471
|
-
str(Path(self.path) / destination) if self.path else destination
|
472
|
-
),
|
481
|
+
dest_path=destination,
|
473
482
|
)
|
474
483
|
progress_bar.advance(1) # type: ignore
|
475
|
-
except
|
476
|
-
self.notify(f"Failed to upload {source}", severity="error")
|
484
|
+
except UploadStorageItemsError as e:
|
485
|
+
self.notify(f"Failed to upload {source}: {e}", severity="error")
|
486
|
+
|
487
|
+
def watch_files_has_been_processed(self):
|
488
|
+
if self.files_has_been_processed:
|
489
|
+
self.notify(f"{self.action.capitalize()} operation is completed")
|
490
|
+
self.query_one("#StorageActionProgress").add_class("success")
|
@@ -9,6 +9,17 @@ StorageActionProgressScreen {
|
|
9
9
|
content-align: center top;
|
10
10
|
|
11
11
|
|
12
|
+
& > #StorageActionProgress.success {
|
13
|
+
border: solid $success;
|
14
|
+
|
15
|
+
#controls {
|
16
|
+
Button {
|
17
|
+
border: solid $success;
|
18
|
+
}
|
19
|
+
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
12
23
|
& > #StorageActionProgress {
|
13
24
|
padding: 1 2 0 2;
|
14
25
|
margin: 0 0;
|
sourcerer/presentation/utils.py
CHANGED
@@ -6,6 +6,7 @@ particularly for retrieving and initializing storage provider services.
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
from sourcerer.domain.storage_provider.services import BaseStorageProviderService
|
9
|
+
from sourcerer.infrastructure.access_credentials.exceptions import CredentialsAuthError
|
9
10
|
from sourcerer.infrastructure.access_credentials.registry import (
|
10
11
|
access_credential_method_registry,
|
11
12
|
)
|
@@ -57,13 +58,16 @@ def get_provider_service_by_access_credentials(
|
|
57
58
|
)
|
58
59
|
|
59
60
|
if not credentials_service:
|
60
|
-
return
|
61
|
+
return None
|
61
62
|
|
62
63
|
provider_service_class = storage_provider_registry.get_by_provider(
|
63
64
|
credentials.provider
|
64
65
|
)
|
65
66
|
if not provider_service_class:
|
66
|
-
return
|
67
|
+
return None
|
67
68
|
|
68
|
-
|
69
|
+
try:
|
70
|
+
auth_credentials = credentials_service().authenticate(credentials.credentials)
|
71
|
+
except CredentialsAuthError:
|
72
|
+
return None
|
69
73
|
return provider_service_class(auth_credentials)
|
sourcerer/settings.py
CHANGED
@@ -23,6 +23,8 @@ DB_NAME = "sourcerer.db"
|
|
23
23
|
|
24
24
|
ENCRYPTION_KEY = get_encryption_key(APP_DIR)
|
25
25
|
|
26
|
+
MAX_PARALLEL_STORAGE_LIST_OPERATIONS = 3
|
27
|
+
|
26
28
|
# Maximum number of parallel download operations
|
27
29
|
MAX_PARALLEL_DOWNLOADS = 8
|
28
30
|
|
@@ -66,6 +68,8 @@ TEXT_EXTENSIONS = {
|
|
66
68
|
".cpp",
|
67
69
|
".go",
|
68
70
|
".rs",
|
71
|
+
".tfstate",
|
72
|
+
".tf",
|
69
73
|
}
|
70
74
|
|
71
75
|
|
sourcerer/utils.py
CHANGED
@@ -17,7 +17,7 @@ def get_encryption_key(path: Path):
|
|
17
17
|
|
18
18
|
# If key file exists, read the key from it
|
19
19
|
if os.path.exists(key_file_path):
|
20
|
-
with open(key_file_path, "
|
20
|
+
with open(key_file_path, encoding="utf-8") as f:
|
21
21
|
return f.read().strip()
|
22
22
|
|
23
23
|
# Otherwise, generate a new key and store it
|
@@ -26,7 +26,7 @@ def get_encryption_key(path: Path):
|
|
26
26
|
os.makedirs(os.path.dirname(key_file_path), exist_ok=True)
|
27
27
|
|
28
28
|
new_key = str(uuid.uuid4())
|
29
|
-
with open(key_file_path, "w") as f:
|
29
|
+
with open(key_file_path, "w", encoding="utf-8") as f:
|
30
30
|
f.write(new_key)
|
31
31
|
|
32
32
|
return new_key
|