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.
Files changed (54) hide show
  1. {data_sourcerer-0.1.0.dist-info → data_sourcerer-0.2.0.dist-info}/METADATA +25 -4
  2. {data_sourcerer-0.1.0.dist-info → data_sourcerer-0.2.0.dist-info}/RECORD +53 -50
  3. {data_sourcerer-0.1.0.dist-info → data_sourcerer-0.2.0.dist-info}/WHEEL +1 -1
  4. sourcerer/__init__.py +1 -1
  5. sourcerer/domain/access_credentials/entities.py +17 -0
  6. sourcerer/domain/access_credentials/exceptions.py +1 -1
  7. sourcerer/domain/access_credentials/repositories.py +1 -1
  8. sourcerer/domain/access_credentials/services.py +14 -2
  9. sourcerer/domain/file_system/exceptions.py +1 -1
  10. sourcerer/domain/file_system/services.py +2 -2
  11. sourcerer/domain/shared/entities.py +1 -0
  12. sourcerer/domain/storage_provider/entities.py +3 -4
  13. sourcerer/domain/storage_provider/exceptions.py +1 -1
  14. sourcerer/domain/storage_provider/services.py +13 -9
  15. sourcerer/infrastructure/access_credentials/exceptions.py +15 -2
  16. sourcerer/infrastructure/access_credentials/registry.py +3 -4
  17. sourcerer/infrastructure/access_credentials/services.py +141 -44
  18. sourcerer/infrastructure/db/models.py +1 -1
  19. sourcerer/infrastructure/file_system/exceptions.py +9 -9
  20. sourcerer/infrastructure/file_system/services.py +16 -16
  21. sourcerer/infrastructure/storage_provider/exceptions.py +28 -8
  22. sourcerer/infrastructure/storage_provider/registry.py +2 -3
  23. sourcerer/infrastructure/storage_provider/services/__init__.py +0 -0
  24. sourcerer/infrastructure/storage_provider/services/azure.py +261 -0
  25. sourcerer/infrastructure/storage_provider/services/gcp.py +277 -0
  26. sourcerer/infrastructure/storage_provider/services/s3.py +290 -0
  27. sourcerer/infrastructure/utils.py +2 -4
  28. sourcerer/presentation/screens/critical_error/main.py +3 -4
  29. sourcerer/presentation/screens/file_system_finder/main.py +4 -4
  30. sourcerer/presentation/screens/file_system_finder/widgets/file_system_navigator.py +12 -12
  31. sourcerer/presentation/screens/main/main.py +57 -33
  32. sourcerer/presentation/screens/main/messages/delete_request.py +1 -2
  33. sourcerer/presentation/screens/main/messages/download_request.py +1 -2
  34. sourcerer/presentation/screens/main/mixins/resize_containers_watcher_mixin.py +3 -3
  35. sourcerer/presentation/screens/main/widgets/gradient.py +2 -5
  36. sourcerer/presentation/screens/main/widgets/resizing_rule.py +1 -1
  37. sourcerer/presentation/screens/main/widgets/storage_content.py +12 -13
  38. sourcerer/presentation/screens/main/widgets/storage_list_sidebar.py +8 -6
  39. sourcerer/presentation/screens/preview_content/main.py +15 -4
  40. sourcerer/presentation/screens/preview_content/styles.tcss +2 -1
  41. sourcerer/presentation/screens/provider_creds_list/main.py +2 -2
  42. sourcerer/presentation/screens/provider_creds_registration/main.py +26 -11
  43. sourcerer/presentation/screens/question/main.py +1 -1
  44. sourcerer/presentation/screens/shared/containers.py +1 -1
  45. sourcerer/presentation/screens/shared/widgets/labeled_input.py +1 -1
  46. sourcerer/presentation/screens/storage_action_progress/main.py +34 -20
  47. sourcerer/presentation/screens/storage_action_progress/styles.tcss +11 -0
  48. sourcerer/presentation/utils.py +7 -3
  49. sourcerer/settings.py +4 -0
  50. sourcerer/utils.py +2 -2
  51. sourcerer/infrastructure/storage_provider/services.py +0 -509
  52. {data_sourcerer-0.1.0.dist-info → data_sourcerer-0.2.0.dist-info}/entry_points.txt +0 -0
  53. {data_sourcerer-0.1.0.dist-info → data_sourcerer-0.2.0.dist-info}/licenses/LICENSE +0 -0
  54. {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 Label, Static, Input, Checkbox
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
- try:
277
+ with contextlib.suppress(NoMatches):
277
278
  preview_button = self.query_one(Button)
278
- except NoMatches:
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
- with Center():
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 VerticalScroll, Horizontal, Container
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
- "azure": "⚪️",
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[Dict[str, List[Storage]]] = reactive({}, recompose=True)
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, storages in groupby(storages, key=lambda x: x.storage.storage[0]):
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 storages:
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 RichLog, LoadingIndicator
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
- ReadStorageItemsException,
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 ReadStorageItemsException:
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
- lexer = Syntax.guess_lexer(self.key, content)
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: 90;
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 Label, Checkbox
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 Select, Label
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.keys()
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.keys()]
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
- else:
185
- self.dismiss(auth_fields)
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: List[AuthField]) -> None:
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 (List[AuthField]): A list of AuthField objects containing key, label, and required attributes.
259
+ fields (list[AuthField]): A list of AuthField objects containing key, label, and required attributes.
245
260
 
246
261
  Returns:
247
262
  None
@@ -1,6 +1,6 @@
1
1
  from textual import on
2
2
  from textual.app import ComposeResult
3
- from textual.containers import Horizontal, Container
3
+ from textual.containers import Container, Horizontal
4
4
  from textual.screen import ModalScreen
5
5
  from textual.widgets import Label
6
6
 
@@ -1,4 +1,4 @@
1
- from textual.containers import ScrollableContainer, HorizontalScroll, VerticalScroll
1
+ from textual.containers import HorizontalScroll, ScrollableContainer, VerticalScroll
2
2
 
3
3
 
4
4
  class ScrollableContainerWithNoBindings(ScrollableContainer, inherit_bindings=False):
@@ -2,7 +2,7 @@ from dataclasses import dataclass
2
2
 
3
3
  from textual.app import ComposeResult
4
4
  from textual.containers import Container
5
- from textual.widgets import Label, Input, TextArea
5
+ from textual.widgets import Input, Label, TextArea
6
6
 
7
7
 
8
8
  class LabeledInput(Container):
@@ -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 Container, VerticalScroll, Horizontal, Center
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 ProgressBar, Label, Rule
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: List[UploadKey | DownloadKey | DeleteKey],
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 (List[Key]): List of keys representing files/folders to process
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
- 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
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
- try:
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 Exception:
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;
@@ -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
- auth_credentials = credentials_service().authenticate(credentials.credentials)
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, "r") as f:
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