data-sourcerer 0.2.3__tar.gz → 0.4.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.
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/PKG-INFO +3 -1
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/README.md +2 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/pyproject.toml +3 -2
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/__init__.py +1 -1
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/access_credentials/entities.py +3 -1
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/access_credentials/repositories.py +1 -1
- data_sourcerer-0.4.0/sourcerer/domain/storage/entities.py +27 -0
- data_sourcerer-0.4.0/sourcerer/domain/storage/repositories.py +31 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/storage_provider/entities.py +1 -1
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/access_credentials/repositories.py +3 -2
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/access_credentials/services.py +9 -25
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/db/models.py +33 -2
- data_sourcerer-0.4.0/sourcerer/infrastructure/storage/repositories.py +72 -0
- data_sourcerer-0.4.0/sourcerer/infrastructure/storage/services.py +37 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/storage_provider/services/azure.py +1 -3
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/storage_provider/services/gcp.py +2 -3
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/storage_provider/services/s3.py +1 -2
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/utils.py +2 -1
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/di_container.py +15 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/file_system_finder/main.py +5 -10
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/file_system_finder/widgets/file_system_navigator.py +16 -13
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/main.py +89 -9
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/messages/preview_request.py +1 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/messages/select_storage_item.py +1 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/mixins/resize_containers_watcher_mixin.py +2 -1
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/widgets/storage_content.py +197 -80
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/widgets/storage_list_sidebar.py +99 -31
- data_sourcerer-0.4.0/sourcerer/presentation/screens/preview_content/main.py +269 -0
- data_sourcerer-0.4.0/sourcerer/presentation/screens/preview_content/styles.tcss +62 -0
- data_sourcerer-0.4.0/sourcerer/presentation/screens/preview_content/text_area_style.py +60 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/provider_creds_list/main.py +38 -13
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/provider_creds_registration/main.py +10 -7
- data_sourcerer-0.4.0/sourcerer/presentation/screens/shared/modal_screens.py +37 -0
- data_sourcerer-0.4.0/sourcerer/presentation/screens/shared/widgets/spinner.py +57 -0
- data_sourcerer-0.4.0/sourcerer/presentation/screens/storage_action_progress/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/storage_action_progress/main.py +3 -5
- data_sourcerer-0.4.0/sourcerer/presentation/screens/storages_list/__init__.py +0 -0
- data_sourcerer-0.4.0/sourcerer/presentation/screens/storages_list/main.py +184 -0
- data_sourcerer-0.4.0/sourcerer/presentation/screens/storages_list/messages/__init__.py +0 -0
- data_sourcerer-0.4.0/sourcerer/presentation/screens/storages_list/messages/reload_storages_request.py +8 -0
- data_sourcerer-0.4.0/sourcerer/presentation/screens/storages_list/styles.tcss +55 -0
- data_sourcerer-0.4.0/sourcerer/presentation/screens/storages_registration/__init__.py +0 -0
- data_sourcerer-0.4.0/sourcerer/presentation/screens/storages_registration/main.py +100 -0
- data_sourcerer-0.4.0/sourcerer/presentation/screens/storages_registration/styles.tcss +41 -0
- data_sourcerer-0.4.0/sourcerer/presentation/settings.py +44 -0
- data_sourcerer-0.4.0/sourcerer/presentation/themes/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/utils.py +9 -1
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/settings.py +2 -0
- data_sourcerer-0.2.3/sourcerer/presentation/screens/preview_content/main.py +0 -70
- data_sourcerer-0.2.3/sourcerer/presentation/screens/preview_content/styles.tcss +0 -27
- data_sourcerer-0.2.3/sourcerer/presentation/screens/shared/widgets/loader.py +0 -31
- data_sourcerer-0.2.3/sourcerer/presentation/settings.py +0 -31
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/.gitignore +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/LICENSE +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/access_credentials/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/access_credentials/exceptions.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/access_credentials/services.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/file_system/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/file_system/entities.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/file_system/exceptions.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/file_system/services.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/shared/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/shared/entities.py +0 -0
- {data_sourcerer-0.2.3/sourcerer/infrastructure/storage_provider/services → data_sourcerer-0.4.0/sourcerer/domain/storage}/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/storage_provider/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/storage_provider/exceptions.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/storage_provider/services.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/access_credentials/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/access_credentials/exceptions.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/access_credentials/registry.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/db/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/db/config.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/file_system/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/file_system/exceptions.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/file_system/services.py +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens → data_sourcerer-0.4.0/sourcerer/infrastructure/storage}/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/storage_provider/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/storage_provider/exceptions.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/infrastructure/storage_provider/registry.py +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/critical_error → data_sourcerer-0.4.0/sourcerer/infrastructure/storage_provider/services}/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/app.py +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/file_system_finder/widgets → data_sourcerer-0.4.0/sourcerer/presentation/screens}/__init__.py +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/main → data_sourcerer-0.4.0/sourcerer/presentation/screens/critical_error}/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/critical_error/main.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/critical_error/styles.tcss +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/file_system_finder/styles.tcss +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/main/messages → data_sourcerer-0.4.0/sourcerer/presentation/screens/file_system_finder/widgets}/__init__.py +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/main/mixins → data_sourcerer-0.4.0/sourcerer/presentation/screens/main}/__init__.py +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/main/widgets → data_sourcerer-0.4.0/sourcerer/presentation/screens/main/messages}/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/messages/delete_request.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/messages/download_request.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/messages/refresh_storages_list_request.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/messages/resizing_rule.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/messages/uncheck_files_request.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/messages/upload_request.py +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/preview_content → data_sourcerer-0.4.0/sourcerer/presentation/screens/main/mixins}/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/styles.tcss +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/provider_creds_list → data_sourcerer-0.4.0/sourcerer/presentation/screens/main/widgets}/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/widgets/gradient.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/main/widgets/resizing_rule.py +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/provider_creds_list/messages → data_sourcerer-0.4.0/sourcerer/presentation/screens/preview_content}/__init__.py +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/provider_creds_registration → data_sourcerer-0.4.0/sourcerer/presentation/screens/provider_creds_list}/__init__.py +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/question → data_sourcerer-0.4.0/sourcerer/presentation/screens/provider_creds_list/messages}/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/provider_creds_list/messages/reload_credentials_request.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/provider_creds_list/styles.tcss +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/shared → data_sourcerer-0.4.0/sourcerer/presentation/screens/provider_creds_registration}/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/provider_creds_registration/styles.tcss +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/shared/widgets → data_sourcerer-0.4.0/sourcerer/presentation/screens/question}/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/question/main.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/question/styles.tcss +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/screens/storage_action_progress → data_sourcerer-0.4.0/sourcerer/presentation/screens/shared}/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/shared/containers.py +0 -0
- {data_sourcerer-0.2.3/sourcerer/presentation/themes → data_sourcerer-0.4.0/sourcerer/presentation/screens/shared/widgets}/__init__.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/shared/widgets/button.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/shared/widgets/labeled_input.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/screens/storage_action_progress/styles.tcss +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/presentation/themes/github_dark.py +0 -0
- {data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: data-sourcerer
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.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
|
@@ -42,6 +42,8 @@ engineers to view and manage files across multiple cloud providers like
|
|
42
42
|
|
43
43
|
> Your terminal. Your storages. Your control.
|
44
44
|
|
45
|
+
[Demo page](https://the-impact-craft.github.io/sourcerer/)
|
46
|
+
|
45
47
|
---
|
46
48
|
|
47
49
|
## ✨ Features
|
@@ -1,7 +1,7 @@
|
|
1
1
|
[project]
|
2
2
|
|
3
3
|
name = "data-sourcerer"
|
4
|
-
version = "0.
|
4
|
+
version = "0.4.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
|
|
{data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/access_credentials/entities.py
RENAMED
@@ -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):
|
{data_sourcerer-0.2.3 → data_sourcerer-0.4.0}/sourcerer/domain/access_credentials/repositories.py
RENAMED
@@ -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
|
-
|
50
|
+
repository (BaseCredentialsRepository): Repository for storing credentials
|
58
51
|
"""
|
59
|
-
self.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: {
|
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: {
|
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)
|
@@ -10,7 +10,6 @@ from collections.abc import Callable
|
|
10
10
|
from pathlib import Path
|
11
11
|
from typing import Any
|
12
12
|
|
13
|
-
import humanize
|
14
13
|
from azure.mgmt.storage import StorageManagementClient
|
15
14
|
from azure.storage.blob import BlobServiceClient
|
16
15
|
from platformdirs import user_downloads_dir
|
@@ -38,7 +37,6 @@ from sourcerer.infrastructure.utils import generate_uuid, is_text_file
|
|
38
37
|
|
39
38
|
@storage_provider(StorageProvider.AzureStorage)
|
40
39
|
class AzureStorageProviderService(BaseStorageProviderService):
|
41
|
-
|
42
40
|
def __init__(self, credentials: Any):
|
43
41
|
"""
|
44
42
|
Initialize the service with Azure credentials.
|
@@ -137,7 +135,7 @@ class AzureStorageProviderService(BaseStorageProviderService):
|
|
137
135
|
File(
|
138
136
|
generate_uuid(),
|
139
137
|
remaining_path,
|
140
|
-
size=
|
138
|
+
size=blob.size,
|
141
139
|
date_modified=blob.last_modified,
|
142
140
|
is_text=is_text_file(blob.name),
|
143
141
|
)
|
@@ -9,7 +9,6 @@ from collections.abc import Callable
|
|
9
9
|
from pathlib import Path
|
10
10
|
from typing import Any
|
11
11
|
|
12
|
-
import humanize
|
13
12
|
from platformdirs import user_downloads_dir
|
14
13
|
|
15
14
|
from sourcerer.domain.shared.entities import StorageProvider
|
@@ -136,7 +135,7 @@ class GCPStorageProviderService(BaseStorageProviderService):
|
|
136
135
|
File(
|
137
136
|
generate_uuid(),
|
138
137
|
blob.name[len(path) :],
|
139
|
-
size=
|
138
|
+
size=blob.size,
|
140
139
|
date_modified=blob.updated.date(),
|
141
140
|
is_text=is_text_file(blob.name),
|
142
141
|
)
|
@@ -150,7 +149,7 @@ class GCPStorageProviderService(BaseStorageProviderService):
|
|
150
149
|
|
151
150
|
except Exception as ex:
|
152
151
|
raise ListStorageItemsError(
|
153
|
-
f"Failed to list items in {storage}: {
|
152
|
+
f"Failed to list items in {storage}: {ex}"
|
154
153
|
) from ex
|
155
154
|
|
156
155
|
def read_storage_item(self, storage: str, key: str) -> str:
|
@@ -10,7 +10,6 @@ from itertools import groupby
|
|
10
10
|
from pathlib import Path
|
11
11
|
from typing import Any
|
12
12
|
|
13
|
-
import humanize
|
14
13
|
from platformdirs import user_downloads_dir
|
15
14
|
|
16
15
|
from sourcerer.domain.shared.entities import StorageProvider
|
@@ -173,7 +172,7 @@ class S3ProviderService(BaseStorageProviderService):
|
|
173
172
|
File(
|
174
173
|
generate_uuid(),
|
175
174
|
i.get("Key").replace(path, ""),
|
176
|
-
|
175
|
+
i.get("Size"),
|
177
176
|
is_text_file(i.get("Key")),
|
178
177
|
i.get("LastModified"),
|
179
178
|
)
|
@@ -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())
|
@@ -5,11 +5,9 @@ from pathlib import Path
|
|
5
5
|
from dependency_injector.wiring import Provide
|
6
6
|
from textual import on
|
7
7
|
from textual.app import ComposeResult
|
8
|
-
from textual.binding import Binding
|
9
8
|
from textual.containers import Container, Horizontal
|
10
9
|
from textual.css.query import NoMatches
|
11
10
|
from textual.reactive import reactive
|
12
|
-
from textual.screen import ModalScreen
|
13
11
|
from textual.widgets import Static
|
14
12
|
|
15
13
|
from sourcerer.infrastructure.file_system.services import FileSystemService
|
@@ -17,6 +15,7 @@ from sourcerer.presentation.di_container import DiContainer
|
|
17
15
|
from sourcerer.presentation.screens.file_system_finder.widgets.file_system_navigator import (
|
18
16
|
FileSystemNavigator,
|
19
17
|
)
|
18
|
+
from sourcerer.presentation.screens.shared.modal_screens import ExitBoundModalScreen
|
20
19
|
from sourcerer.presentation.screens.shared.widgets.button import Button
|
21
20
|
|
22
21
|
|
@@ -26,21 +25,17 @@ class FileSystemSelectionValidationRule:
|
|
26
25
|
error_message: str
|
27
26
|
|
28
27
|
|
29
|
-
class FileSystemNavigationModal(
|
28
|
+
class FileSystemNavigationModal(ExitBoundModalScreen):
|
30
29
|
CONTAINER_ID = "file_system_view_container"
|
31
30
|
CSS_PATH = "styles.tcss"
|
32
31
|
|
33
|
-
BINDINGS = [
|
34
|
-
Binding("escape", "app.pop_screen", "Pop screen"),
|
35
|
-
]
|
36
|
-
|
37
32
|
active_path: reactive[Path] = reactive(Path())
|
38
33
|
|
39
34
|
def __init__(
|
40
35
|
self,
|
41
36
|
*args,
|
42
|
-
file_system_service: FileSystemService = Provide[
|
43
|
-
DiContainer.file_system_service
|
37
|
+
file_system_service: FileSystemService = Provide[ # type: ignore
|
38
|
+
DiContainer.file_system_service # type: ignore
|
44
39
|
],
|
45
40
|
validation_rules: list[FileSystemSelectionValidationRule] | None = None,
|
46
41
|
**kwargs,
|
@@ -125,7 +120,7 @@ class FileSystemNavigationModal(ModalScreen):
|
|
125
120
|
event (Button.Click): The event containing the button that was clicked.
|
126
121
|
"""
|
127
122
|
if event.action == "close":
|
128
|
-
self.
|
123
|
+
self.action_cancel_screen()
|
129
124
|
else:
|
130
125
|
self.on_apply()
|
131
126
|
|
@@ -21,6 +21,7 @@ from sourcerer.presentation.screens.shared.containers import (
|
|
21
21
|
ScrollHorizontalContainerWithNoBindings,
|
22
22
|
ScrollVerticalContainerWithNoBindings,
|
23
23
|
)
|
24
|
+
from sourcerer.presentation.settings import KeyBindings
|
24
25
|
from sourcerer.settings import DIRECTORY_ICON, DOUBLE_CLICK_THRESHOLD, FILE_ICON
|
25
26
|
|
26
27
|
|
@@ -48,7 +49,7 @@ class FileSystemWidget(Widget):
|
|
48
49
|
background: $block-cursor-blurred-background;
|
49
50
|
text-style: $block-cursor-blurred-text-style;
|
50
51
|
}
|
51
|
-
|
52
|
+
|
52
53
|
.folder-name {
|
53
54
|
text-overflow: ellipsis;
|
54
55
|
text-wrap: nowrap;
|
@@ -161,7 +162,7 @@ class FileSystemWidget(Widget):
|
|
161
162
|
Calls the `on_click` method if the pressed key is "enter", which may trigger
|
162
163
|
folder navigation or file opening depending on the widget's context.
|
163
164
|
"""
|
164
|
-
if event.key ==
|
165
|
+
if event.key == KeyBindings.ENTER.value:
|
165
166
|
self.on_file_select()
|
166
167
|
|
167
168
|
def on_file_select(self):
|
@@ -232,11 +233,13 @@ class FileSystemNavigator(ScrollHorizontalContainerWithNoBindings):
|
|
232
233
|
|
233
234
|
# Consolidate key binding data
|
234
235
|
BINDINGS: ClassVar[list[BindingType]] = [
|
235
|
-
Binding(
|
236
|
-
Binding(
|
237
|
-
Binding(
|
238
|
-
Binding(
|
239
|
-
Binding(
|
236
|
+
Binding(KeyBindings.ENTER.value, "select_cursor", "Select", show=False),
|
237
|
+
Binding(KeyBindings.ARROW_UP.value, "cursor_up", "Cursor up", show=False),
|
238
|
+
Binding(KeyBindings.ARROW_DOWN.value, "cursor_down", "Cursor down", show=False),
|
239
|
+
Binding(KeyBindings.ARROW_LEFT.value, "cursor_left", "Cursor left", show=False),
|
240
|
+
Binding(
|
241
|
+
KeyBindings.ARROW_RIGHT.value, "cursor_right", "Cursor right", show=False
|
242
|
+
),
|
240
243
|
]
|
241
244
|
|
242
245
|
MAIN_CONTAINER_ID: ClassVar[str] = "dirs_content"
|
@@ -306,9 +309,9 @@ class FileSystemNavigator(ScrollHorizontalContainerWithNoBindings):
|
|
306
309
|
)
|
307
310
|
self._focus_first_child(path_listing_container)
|
308
311
|
|
309
|
-
self.path_listing_containers_uuids[
|
310
|
-
|
311
|
-
|
312
|
+
self.path_listing_containers_uuids[
|
313
|
+
str(self.work_dir)
|
314
|
+
] = path_listing_container.id
|
312
315
|
|
313
316
|
def action_cursor_down(self) -> None:
|
314
317
|
"""
|
@@ -626,9 +629,9 @@ class FileSystemNavigator(ScrollHorizontalContainerWithNoBindings):
|
|
626
629
|
await self._mount_path_listing_container(path_listing_container)
|
627
630
|
|
628
631
|
if str(folder_path) not in self.path_listing_containers_uuids:
|
629
|
-
self.path_listing_containers_uuids[
|
630
|
-
|
631
|
-
|
632
|
+
self.path_listing_containers_uuids[
|
633
|
+
str(folder_path)
|
634
|
+
] = path_listing_container.id
|
632
635
|
|
633
636
|
@on(FileSystemWidget.Focus)
|
634
637
|
def on_folder_focus(self, event: FileSystemWidget.Focus):
|