data-sourcerer 0.2.3__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/PKG-INFO +1 -1
  2. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/pyproject.toml +3 -2
  3. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/__init__.py +1 -1
  4. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/access_credentials/entities.py +3 -1
  5. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/access_credentials/repositories.py +1 -1
  6. data_sourcerer-0.3.0/sourcerer/domain/storage/entities.py +27 -0
  7. data_sourcerer-0.3.0/sourcerer/domain/storage/repositories.py +31 -0
  8. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/access_credentials/repositories.py +3 -2
  9. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/access_credentials/services.py +9 -25
  10. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/db/models.py +33 -2
  11. data_sourcerer-0.3.0/sourcerer/infrastructure/storage/repositories.py +72 -0
  12. data_sourcerer-0.3.0/sourcerer/infrastructure/storage/services.py +37 -0
  13. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/storage_provider/services/gcp.py +1 -1
  14. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/utils.py +2 -1
  15. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/di_container.py +15 -0
  16. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/file_system_finder/main.py +5 -4
  17. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/file_system_finder/widgets/file_system_navigator.py +16 -13
  18. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/main.py +63 -8
  19. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/messages/select_storage_item.py +1 -0
  20. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/mixins/resize_containers_watcher_mixin.py +2 -1
  21. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/widgets/storage_content.py +187 -77
  22. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/widgets/storage_list_sidebar.py +99 -31
  23. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/preview_content/main.py +15 -3
  24. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/provider_creds_list/main.py +29 -8
  25. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/provider_creds_registration/main.py +7 -4
  26. data_sourcerer-0.3.0/sourcerer/presentation/screens/shared/widgets/spinner.py +57 -0
  27. data_sourcerer-0.3.0/sourcerer/presentation/screens/storage_action_progress/__init__.py +0 -0
  28. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/storage_action_progress/main.py +3 -5
  29. data_sourcerer-0.3.0/sourcerer/presentation/screens/storages_list/__init__.py +0 -0
  30. data_sourcerer-0.3.0/sourcerer/presentation/screens/storages_list/main.py +180 -0
  31. data_sourcerer-0.3.0/sourcerer/presentation/screens/storages_list/messages/__init__.py +0 -0
  32. data_sourcerer-0.3.0/sourcerer/presentation/screens/storages_list/messages/reload_storages_request.py +8 -0
  33. data_sourcerer-0.3.0/sourcerer/presentation/screens/storages_list/styles.tcss +55 -0
  34. data_sourcerer-0.3.0/sourcerer/presentation/screens/storages_registration/__init__.py +0 -0
  35. data_sourcerer-0.3.0/sourcerer/presentation/screens/storages_registration/main.py +100 -0
  36. data_sourcerer-0.3.0/sourcerer/presentation/screens/storages_registration/styles.tcss +41 -0
  37. data_sourcerer-0.3.0/sourcerer/presentation/settings.py +44 -0
  38. data_sourcerer-0.3.0/sourcerer/presentation/themes/__init__.py +0 -0
  39. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/utils.py +9 -1
  40. data_sourcerer-0.2.3/sourcerer/presentation/screens/shared/widgets/loader.py +0 -31
  41. data_sourcerer-0.2.3/sourcerer/presentation/settings.py +0 -31
  42. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/.gitignore +0 -0
  43. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/LICENSE +0 -0
  44. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/README.md +0 -0
  45. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/__init__.py +0 -0
  46. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/access_credentials/__init__.py +0 -0
  47. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/access_credentials/exceptions.py +0 -0
  48. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/access_credentials/services.py +0 -0
  49. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/file_system/__init__.py +0 -0
  50. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/file_system/entities.py +0 -0
  51. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/file_system/exceptions.py +0 -0
  52. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/file_system/services.py +0 -0
  53. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/shared/__init__.py +0 -0
  54. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/shared/entities.py +0 -0
  55. {data_sourcerer-0.2.3/sourcerer/infrastructure/storage_provider/services → data_sourcerer-0.3.0/sourcerer/domain/storage}/__init__.py +0 -0
  56. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/storage_provider/__init__.py +0 -0
  57. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/storage_provider/entities.py +0 -0
  58. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/storage_provider/exceptions.py +0 -0
  59. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/domain/storage_provider/services.py +0 -0
  60. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/__init__.py +0 -0
  61. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/access_credentials/__init__.py +0 -0
  62. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/access_credentials/exceptions.py +0 -0
  63. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/access_credentials/registry.py +0 -0
  64. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/db/__init__.py +0 -0
  65. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/db/config.py +0 -0
  66. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/file_system/__init__.py +0 -0
  67. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/file_system/exceptions.py +0 -0
  68. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/file_system/services.py +0 -0
  69. {data_sourcerer-0.2.3/sourcerer/presentation/screens → data_sourcerer-0.3.0/sourcerer/infrastructure/storage}/__init__.py +0 -0
  70. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/storage_provider/__init__.py +0 -0
  71. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/storage_provider/exceptions.py +0 -0
  72. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/storage_provider/registry.py +0 -0
  73. {data_sourcerer-0.2.3/sourcerer/presentation/screens/critical_error → data_sourcerer-0.3.0/sourcerer/infrastructure/storage_provider/services}/__init__.py +0 -0
  74. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/storage_provider/services/azure.py +0 -0
  75. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/infrastructure/storage_provider/services/s3.py +0 -0
  76. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/__init__.py +0 -0
  77. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/app.py +0 -0
  78. {data_sourcerer-0.2.3/sourcerer/presentation/screens/file_system_finder/widgets → data_sourcerer-0.3.0/sourcerer/presentation/screens}/__init__.py +0 -0
  79. {data_sourcerer-0.2.3/sourcerer/presentation/screens/main → data_sourcerer-0.3.0/sourcerer/presentation/screens/critical_error}/__init__.py +0 -0
  80. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/critical_error/main.py +0 -0
  81. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/critical_error/styles.tcss +0 -0
  82. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/file_system_finder/styles.tcss +0 -0
  83. {data_sourcerer-0.2.3/sourcerer/presentation/screens/main/messages → data_sourcerer-0.3.0/sourcerer/presentation/screens/file_system_finder/widgets}/__init__.py +0 -0
  84. {data_sourcerer-0.2.3/sourcerer/presentation/screens/main/mixins → data_sourcerer-0.3.0/sourcerer/presentation/screens/main}/__init__.py +0 -0
  85. {data_sourcerer-0.2.3/sourcerer/presentation/screens/main/widgets → data_sourcerer-0.3.0/sourcerer/presentation/screens/main/messages}/__init__.py +0 -0
  86. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/messages/delete_request.py +0 -0
  87. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/messages/download_request.py +0 -0
  88. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/messages/preview_request.py +0 -0
  89. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/messages/refresh_storages_list_request.py +0 -0
  90. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/messages/resizing_rule.py +0 -0
  91. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/messages/uncheck_files_request.py +0 -0
  92. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/messages/upload_request.py +0 -0
  93. {data_sourcerer-0.2.3/sourcerer/presentation/screens/preview_content → data_sourcerer-0.3.0/sourcerer/presentation/screens/main/mixins}/__init__.py +0 -0
  94. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/styles.tcss +0 -0
  95. {data_sourcerer-0.2.3/sourcerer/presentation/screens/provider_creds_list → data_sourcerer-0.3.0/sourcerer/presentation/screens/main/widgets}/__init__.py +0 -0
  96. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/widgets/gradient.py +0 -0
  97. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/main/widgets/resizing_rule.py +0 -0
  98. {data_sourcerer-0.2.3/sourcerer/presentation/screens/provider_creds_list/messages → data_sourcerer-0.3.0/sourcerer/presentation/screens/preview_content}/__init__.py +0 -0
  99. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/preview_content/styles.tcss +0 -0
  100. {data_sourcerer-0.2.3/sourcerer/presentation/screens/provider_creds_registration → data_sourcerer-0.3.0/sourcerer/presentation/screens/provider_creds_list}/__init__.py +0 -0
  101. {data_sourcerer-0.2.3/sourcerer/presentation/screens/question → data_sourcerer-0.3.0/sourcerer/presentation/screens/provider_creds_list/messages}/__init__.py +0 -0
  102. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/provider_creds_list/messages/reload_credentials_request.py +0 -0
  103. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/provider_creds_list/styles.tcss +0 -0
  104. {data_sourcerer-0.2.3/sourcerer/presentation/screens/shared → data_sourcerer-0.3.0/sourcerer/presentation/screens/provider_creds_registration}/__init__.py +0 -0
  105. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/provider_creds_registration/styles.tcss +0 -0
  106. {data_sourcerer-0.2.3/sourcerer/presentation/screens/shared/widgets → data_sourcerer-0.3.0/sourcerer/presentation/screens/question}/__init__.py +0 -0
  107. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/question/main.py +0 -0
  108. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/question/styles.tcss +0 -0
  109. {data_sourcerer-0.2.3/sourcerer/presentation/screens/storage_action_progress → data_sourcerer-0.3.0/sourcerer/presentation/screens/shared}/__init__.py +0 -0
  110. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/shared/containers.py +0 -0
  111. {data_sourcerer-0.2.3/sourcerer/presentation/themes → data_sourcerer-0.3.0/sourcerer/presentation/screens/shared/widgets}/__init__.py +0 -0
  112. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/shared/widgets/button.py +0 -0
  113. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/shared/widgets/labeled_input.py +0 -0
  114. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/screens/storage_action_progress/styles.tcss +0 -0
  115. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/presentation/themes/github_dark.py +0 -0
  116. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/settings.py +0 -0
  117. {data_sourcerer-0.2.3 → data_sourcerer-0.3.0}/sourcerer/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: data-sourcerer
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: Sourcerer is a terminal cloud storage navigator.
5
5
  Author-email: Bohdana Kuzmenko <bohdana.kuzmenko.dev@gmail.com>
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
 
3
3
  name = "data-sourcerer"
4
- version = "0.2.3"
4
+ version = "0.3.0"
5
5
  description = "Sourcerer is a terminal cloud storage navigator."
6
6
  requires-python = ">=3.9"
7
7
 
@@ -92,8 +92,9 @@ select = [
92
92
  "C90", # mccabe (complexity)
93
93
  "B", # flake8-bugbear
94
94
  "SIM", # flake8-simplify
95
+ "Q", #flake8-quotes
96
+ "RUF",
95
97
  ]
96
-
97
98
  # Allow autofix for these rule sets
98
99
  fixable = ["ALL"]
99
100
 
@@ -12,4 +12,4 @@ The application is structured using a clean architecture approach with:
12
12
  - Presentation layer: User interface components
13
13
  """
14
14
 
15
- __version__ = "0.2.3"
15
+ __version__ = "0.3.0"
@@ -4,13 +4,14 @@ Access credentials entity classes.
4
4
  This module defines data classes representing access credentials
5
5
  used for authentication with various cloud providers.
6
6
  """
7
-
8
7
  from datetime import datetime
9
8
 
10
9
  import boto3
11
10
  from azure.identity import ClientSecretCredential
12
11
  from msgspec._core import Struct
13
12
 
13
+ from sourcerer.domain.storage.entities import Storage
14
+
14
15
 
15
16
  class Credentials(Struct):
16
17
  """
@@ -35,6 +36,7 @@ class Credentials(Struct):
35
36
  active: bool
36
37
  created_at: datetime | None = None
37
38
  updated_at: datetime | None = None
39
+ storages: list[Storage] = [] # noqa: RUF012
38
40
 
39
41
 
40
42
  class Boto3Credentials(Struct):
@@ -56,7 +56,7 @@ class BaseCredentialsRepository(metaclass=ABCMeta):
56
56
  raise NotImplementedError
57
57
 
58
58
  @abstractmethod
59
- def list(self, active_only: bool | None = None):
59
+ def list(self, active_only: bool | None = None) -> list[Credentials]:
60
60
  """List all credentials in the repository.
61
61
 
62
62
  Args:
@@ -0,0 +1,27 @@
1
+ """
2
+ Storage provider entity classes.
3
+
4
+ This module defines data classes representing cloud storage entities
5
+ such as storage containers, files, folders, and permissions.
6
+ """
7
+
8
+ from datetime import datetime
9
+
10
+ from msgspec._core import Struct
11
+
12
+
13
+ class Storage(Struct):
14
+ """
15
+ Represents a cloud storage container (bucket/container).
16
+
17
+ Attributes:
18
+ credentials_id (int): The ID of the associated credentials
19
+ name (str): The storage name/identifier (e.g., bucket name)
20
+ date_created (datetime): When the storage was created
21
+ """
22
+
23
+ name: str
24
+ uuid: str
25
+ date_created: datetime
26
+ credentials_id: int | None = None
27
+ credentials_name: str | None = None
@@ -0,0 +1,31 @@
1
+ from abc import ABCMeta, abstractmethod
2
+
3
+ from sourcerer.domain.storage.entities import Storage
4
+
5
+
6
+ class BaseStoragesRepository(metaclass=ABCMeta):
7
+ @abstractmethod
8
+ def create(self, storage: Storage) -> None:
9
+ """Create a new storage entry in the repository.
10
+ Args:
11
+ storage (Storage): The storage object to store
12
+ """
13
+ raise NotImplementedError()
14
+
15
+ @abstractmethod
16
+ def list(self, provider_id: int | None = None) -> list[Storage]:
17
+ """List all storage entries in the repository.
18
+ Args:
19
+ provider_id (int | None): The provider ID to filter by. If None, all entries are returned.
20
+ Returns:
21
+ List[Storage]: A list of storage entries
22
+ """
23
+ raise NotImplementedError()
24
+
25
+ @abstractmethod
26
+ def delete(self, uuid: str) -> None:
27
+ """Delete a storage entry by UUID.
28
+ Args:
29
+ uuid (str): The UUID of the storage entry to delete
30
+ """
31
+ raise NotImplementedError()
@@ -46,7 +46,7 @@ class SQLAlchemyCredentialsRepository(BaseCredentialsRepository):
46
46
  session.add(credentials)
47
47
  session.commit()
48
48
 
49
- def delete(self, uuid: str):
49
+ def delete(self, uuid: str) -> None:
50
50
  """
51
51
  Delete credentials from the database by UUID.
52
52
 
@@ -78,7 +78,7 @@ class SQLAlchemyCredentialsRepository(BaseCredentialsRepository):
78
78
  session.query(DBCredentials).filter(DBCredentials.uuid == uuid).first()
79
79
  )
80
80
 
81
- def list(self, active_only: bool | None = None):
81
+ def list(self, active_only: bool | None = None) -> list[Credentials]:
82
82
  """
83
83
  List all credentials in the repository.
84
84
 
@@ -103,6 +103,7 @@ class SQLAlchemyCredentialsRepository(BaseCredentialsRepository):
103
103
  credentials_type=credential.credentials_type,
104
104
  credentials=credential.credentials,
105
105
  active=credential.active,
106
+ storages=credential.storages,
106
107
  )
107
108
  for credential in credentials_query.all()
108
109
  ]
@@ -10,7 +10,6 @@ from abc import ABC
10
10
 
11
11
  import boto3
12
12
  from azure.identity import ClientSecretCredential
13
- from dependency_injector.wiring import Provide
14
13
  from google.cloud import storage
15
14
 
16
15
  from sourcerer.domain.access_credentials.entities import (
@@ -33,7 +32,6 @@ from sourcerer.infrastructure.access_credentials.registry import (
33
32
  access_credentials_method,
34
33
  )
35
34
  from sourcerer.infrastructure.utils import generate_uuid
36
- from sourcerer.presentation.di_container import DiContainer
37
35
 
38
36
 
39
37
  class CredentialsService:
@@ -44,21 +42,16 @@ class CredentialsService:
44
42
  and deactivating credentials.
45
43
  """
46
44
 
47
- def __init__(
48
- self,
49
- credentials_repo: BaseCredentialsRepository = Provide[
50
- DiContainer.credentials_repository
51
- ],
52
- ):
45
+ def __init__(self, repository: BaseCredentialsRepository):
53
46
  """
54
47
  Initialize the service with a credentials repository.
55
48
 
56
49
  Args:
57
- credentials_repo (BaseCredentialsRepository): Repository for storing credentials
50
+ repository (BaseCredentialsRepository): Repository for storing credentials
58
51
  """
59
- self.credentials_repo = credentials_repo
52
+ self.credentials_repo = repository
60
53
 
61
- def list(self, active_only=False):
54
+ def list(self, active_only=False) -> list[Credentials]:
62
55
  """
63
56
  List credentials.
64
57
 
@@ -119,12 +112,7 @@ class AccessCredentialsService(BaseAccessCredentialsService, ABC):
119
112
  access credential service implementations.
120
113
  """
121
114
 
122
- def __init__(
123
- self,
124
- credentials_repo: BaseCredentialsRepository = Provide[
125
- DiContainer.credentials_repository
126
- ],
127
- ):
115
+ def __init__(self, credentials_repo: BaseCredentialsRepository):
128
116
  """
129
117
  Initialize the service with a credentials repository.
130
118
 
@@ -408,11 +396,9 @@ class GCPCredentialsService(AccessCredentialsService):
408
396
  return storage.Client.from_service_account_info(service_acc_info)
409
397
 
410
398
  except json.JSONDecodeError as e:
411
- raise CredentialsAuthError(f"Invalid credentials format: {str(e)}") from e
399
+ raise CredentialsAuthError(f"Invalid credentials format: {e}") from e
412
400
  except Exception as e:
413
- raise CredentialsAuthError(
414
- f"Failed to authenticate with GCP: {str(e)}"
415
- ) from e
401
+ raise CredentialsAuthError(f"Failed to authenticate with GCP: {e}") from e
416
402
 
417
403
  @classmethod
418
404
  def auth_fields(cls) -> list[AuthField]:
@@ -484,11 +470,9 @@ class AzureClientSecretCredentialsService(AccessCredentialsService):
484
470
  )
485
471
 
486
472
  except json.JSONDecodeError as e:
487
- raise CredentialsAuthError(f"Invalid credentials format: {str(e)}") from e
473
+ raise CredentialsAuthError(f"Invalid credentials format: {e}") from e
488
474
  except Exception as e:
489
- raise CredentialsAuthError(
490
- f"Failed to authenticate with Azure: {str(e)}"
491
- ) from e
475
+ raise CredentialsAuthError(f"Failed to authenticate with Azure: {e}") from e
492
476
 
493
477
  @classmethod
494
478
  def auth_fields(cls) -> list[AuthField]:
@@ -7,8 +7,8 @@ and their relationships.
7
7
 
8
8
  from datetime import datetime
9
9
 
10
- from sqlalchemy import Boolean, Column, DateTime, Integer, String
11
- from sqlalchemy.orm import declarative_base
10
+ from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String
11
+ from sqlalchemy.orm import backref, declarative_base, relationship
12
12
  from sqlalchemy_utils.types.encrypted.encrypted_type import EncryptedType
13
13
 
14
14
  from sourcerer.settings import ENCRYPTION_KEY
@@ -45,3 +45,34 @@ class Credentials(Base):
45
45
  active = Column(Boolean, default=True)
46
46
  created_at = Column(DateTime, default=datetime.utcnow)
47
47
  updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
48
+
49
+
50
+ class Storage(Base):
51
+ """
52
+ SQLAlchemy model for storing storage information.
53
+
54
+ This model represents the storage table in the database,
55
+ storing information about different storage containers.
56
+
57
+ Attributes:
58
+ id (int): Primary key
59
+ uuid (str): Unique identifier for the storage
60
+ name (str): Name of the storage
61
+ credentials_id (int): Foreign key referencing the credentials table
62
+ created_at (datetime): Timestamp when the storage was created
63
+ """
64
+
65
+ __tablename__ = "storages"
66
+ id = Column(Integer, primary_key=True)
67
+ uuid = Column(String, unique=True, nullable=False)
68
+ name = Column(String, nullable=False)
69
+ credentials_id = Column(
70
+ Integer, ForeignKey("credentials.id", ondelete="CASCADE"), nullable=False
71
+ )
72
+ created_at = Column(DateTime, default=datetime.utcnow)
73
+
74
+ credentials = relationship(
75
+ "Credentials",
76
+ cascade="save-update",
77
+ backref=backref("storages", passive_deletes=True),
78
+ )
@@ -0,0 +1,72 @@
1
+ from sourcerer.domain.storage.entities import Storage
2
+ from sourcerer.domain.storage.repositories import BaseStoragesRepository
3
+ from sourcerer.infrastructure.db.models import Storage as DBStorage
4
+
5
+
6
+ class SQLAlchemyStoragesRepository(BaseStoragesRepository):
7
+ def __init__(self, db):
8
+ """
9
+ Initialize the repository with a database session factory.
10
+
11
+ Args:
12
+ db: Database session factory
13
+ """
14
+ self.db = db
15
+
16
+ def create(self, storage: Storage) -> None:
17
+ """
18
+ Create a new storage entity in the database.
19
+
20
+ Args:
21
+ storage (Storage): The storage entity to be created
22
+ """
23
+ entry = DBStorage(
24
+ uuid=storage.uuid,
25
+ name=storage.name,
26
+ credentials_id=storage.credentials_id,
27
+ created_at=storage.date_created,
28
+ )
29
+ with self.db() as session:
30
+ session.add(entry)
31
+ session.commit()
32
+
33
+ def list(self, provider_id: int | None = None) -> list[Storage]:
34
+ """
35
+ List all storages, optionally filtered by provider ID.
36
+
37
+ Args:
38
+ provider_id (int | None): The ID of the provider to filter by
39
+
40
+ Returns:
41
+ list[Storage]: List of storage entities
42
+ """
43
+ with self.db() as session:
44
+ query = session.query(DBStorage)
45
+ if provider_id is not None:
46
+ query = query.filter(DBStorage.credentials_id == provider_id)
47
+ return [
48
+ Storage(
49
+ name=storage.name,
50
+ uuid=storage.uuid,
51
+ credentials_id=storage.credentials_id,
52
+ date_created=storage.created_at,
53
+ credentials_name=storage.credentials and storage.credentials.name,
54
+ )
55
+ for storage in query.all()
56
+ ]
57
+
58
+ def delete(self, uuid: str) -> None:
59
+ """
60
+ Delete a storage entity by its UUID.
61
+
62
+ Args:
63
+ uuid (str): The UUID of the storage entity to be deleted
64
+ """
65
+ with self.db() as session:
66
+ storage = (
67
+ session.query(DBStorage).filter(DBStorage.uuid == uuid).one_or_none()
68
+ )
69
+ if storage is None:
70
+ return
71
+ session.delete(storage)
72
+ session.commit()
@@ -0,0 +1,37 @@
1
+ from sourcerer.domain.storage.entities import Storage
2
+ from sourcerer.domain.storage.repositories import BaseStoragesRepository
3
+
4
+
5
+ class StoragesService:
6
+ def __init__(
7
+ self,
8
+ repository: BaseStoragesRepository,
9
+ ):
10
+ self.repository = repository
11
+
12
+ def create(self, storage: Storage) -> None:
13
+ """
14
+ Create a new storage entity.
15
+
16
+ Args:
17
+ storage (Storage): The storage object to be created
18
+ """
19
+ self.repository.create(storage)
20
+
21
+ def list(self, provider_id: int | None = None) -> list[Storage]:
22
+ """
23
+ List all storage entities.
24
+
25
+ Args:
26
+ provider_id (int|None, optional): If provided, filter storage entities by provider ID
27
+ """
28
+ return self.repository.list(provider_id)
29
+
30
+ def delete(self, uuid: str) -> None:
31
+ """
32
+ Delete a storage entity by its UUID.
33
+
34
+ Args:
35
+ uuid (str): The UUID of the storage entity to be deleted
36
+ """
37
+ self.repository.delete(uuid)
@@ -150,7 +150,7 @@ class GCPStorageProviderService(BaseStorageProviderService):
150
150
 
151
151
  except Exception as ex:
152
152
  raise ListStorageItemsError(
153
- f"Failed to list items in {storage}: {str(ex)}"
153
+ f"Failed to list items in {storage}: {ex}"
154
154
  ) from ex
155
155
 
156
156
  def read_storage_item(self, storage: str, key: str) -> str:
@@ -8,6 +8,7 @@ import mimetypes
8
8
  import secrets
9
9
  import uuid
10
10
  from pathlib import Path
11
+ from typing import ClassVar
11
12
 
12
13
  from sourcerer.settings import TEXT_EXTENSIONS
13
14
 
@@ -83,7 +84,7 @@ class Singleton(type):
83
84
  Metaclass that implements the singleton pattern, ensuring only one instance of a class exists.
84
85
  """
85
86
 
86
- _instances = {}
87
+ _instances: ClassVar[dict["Singleton", type]] = {}
87
88
 
88
89
  def __call__(cls, *args, **kwargs):
89
90
  """
@@ -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())
@@ -1,11 +1,12 @@
1
1
  from collections.abc import Callable
2
2
  from dataclasses import dataclass
3
3
  from pathlib import Path
4
+ from typing import ClassVar
4
5
 
5
6
  from dependency_injector.wiring import Provide
6
7
  from textual import on
7
8
  from textual.app import ComposeResult
8
- from textual.binding import Binding
9
+ from textual.binding import Binding, BindingType
9
10
  from textual.containers import Container, Horizontal
10
11
  from textual.css.query import NoMatches
11
12
  from textual.reactive import reactive
@@ -30,7 +31,7 @@ class FileSystemNavigationModal(ModalScreen):
30
31
  CONTAINER_ID = "file_system_view_container"
31
32
  CSS_PATH = "styles.tcss"
32
33
 
33
- BINDINGS = [
34
+ BINDINGS: ClassVar[list[BindingType]] = [
34
35
  Binding("escape", "app.pop_screen", "Pop screen"),
35
36
  ]
36
37
 
@@ -39,8 +40,8 @@ class FileSystemNavigationModal(ModalScreen):
39
40
  def __init__(
40
41
  self,
41
42
  *args,
42
- file_system_service: FileSystemService = Provide[
43
- DiContainer.file_system_service
43
+ file_system_service: FileSystemService = Provide[ # type: ignore
44
+ DiContainer.file_system_service # type: ignore
44
45
  ],
45
46
  validation_rules: list[FileSystemSelectionValidationRule] | None = None,
46
47
  **kwargs,
@@ -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 == "enter":
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("enter", "select_cursor", "Select", show=False),
236
- Binding("up", "cursor_up", "Cursor up", show=False),
237
- Binding("down", "cursor_down", "Cursor down", show=False),
238
- Binding("left", "cursor_left", "Cursor left", show=False),
239
- Binding("right", "cursor_right", "Cursor right", show=False),
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[str(self.work_dir)] = (
310
- path_listing_container.id
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[str(folder_path)] = (
630
- path_listing_container.id
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):