data-sourcerer 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. data_sourcerer-0.1.0.dist-info/METADATA +52 -0
  2. data_sourcerer-0.1.0.dist-info/RECORD +95 -0
  3. data_sourcerer-0.1.0.dist-info/WHEEL +5 -0
  4. data_sourcerer-0.1.0.dist-info/entry_points.txt +2 -0
  5. data_sourcerer-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. data_sourcerer-0.1.0.dist-info/top_level.txt +1 -0
  7. sourcerer/__init__.py +15 -0
  8. sourcerer/domain/__init__.py +13 -0
  9. sourcerer/domain/access_credentials/__init__.py +7 -0
  10. sourcerer/domain/access_credentials/entities.py +53 -0
  11. sourcerer/domain/access_credentials/exceptions.py +17 -0
  12. sourcerer/domain/access_credentials/repositories.py +84 -0
  13. sourcerer/domain/access_credentials/services.py +91 -0
  14. sourcerer/domain/file_system/__init__.py +7 -0
  15. sourcerer/domain/file_system/entities.py +70 -0
  16. sourcerer/domain/file_system/exceptions.py +17 -0
  17. sourcerer/domain/file_system/services.py +64 -0
  18. sourcerer/domain/shared/__init__.py +6 -0
  19. sourcerer/domain/shared/entities.py +18 -0
  20. sourcerer/domain/storage_provider/__init__.py +7 -0
  21. sourcerer/domain/storage_provider/entities.py +86 -0
  22. sourcerer/domain/storage_provider/exceptions.py +17 -0
  23. sourcerer/domain/storage_provider/services.py +130 -0
  24. sourcerer/infrastructure/__init__.py +13 -0
  25. sourcerer/infrastructure/access_credentials/__init__.py +7 -0
  26. sourcerer/infrastructure/access_credentials/exceptions.py +16 -0
  27. sourcerer/infrastructure/access_credentials/registry.py +120 -0
  28. sourcerer/infrastructure/access_credentials/repositories.py +119 -0
  29. sourcerer/infrastructure/access_credentials/services.py +396 -0
  30. sourcerer/infrastructure/db/__init__.py +6 -0
  31. sourcerer/infrastructure/db/config.py +73 -0
  32. sourcerer/infrastructure/db/models.py +47 -0
  33. sourcerer/infrastructure/file_system/__init__.py +7 -0
  34. sourcerer/infrastructure/file_system/exceptions.py +89 -0
  35. sourcerer/infrastructure/file_system/services.py +147 -0
  36. sourcerer/infrastructure/storage_provider/__init__.py +7 -0
  37. sourcerer/infrastructure/storage_provider/exceptions.py +78 -0
  38. sourcerer/infrastructure/storage_provider/registry.py +84 -0
  39. sourcerer/infrastructure/storage_provider/services.py +509 -0
  40. sourcerer/infrastructure/utils.py +106 -0
  41. sourcerer/presentation/__init__.py +12 -0
  42. sourcerer/presentation/app.py +36 -0
  43. sourcerer/presentation/di_container.py +46 -0
  44. sourcerer/presentation/screens/__init__.py +0 -0
  45. sourcerer/presentation/screens/critical_error/__init__.py +0 -0
  46. sourcerer/presentation/screens/critical_error/main.py +78 -0
  47. sourcerer/presentation/screens/critical_error/styles.tcss +41 -0
  48. sourcerer/presentation/screens/file_system_finder/main.py +248 -0
  49. sourcerer/presentation/screens/file_system_finder/styles.tcss +44 -0
  50. sourcerer/presentation/screens/file_system_finder/widgets/__init__.py +0 -0
  51. sourcerer/presentation/screens/file_system_finder/widgets/file_system_navigator.py +810 -0
  52. sourcerer/presentation/screens/main/__init__.py +0 -0
  53. sourcerer/presentation/screens/main/main.py +469 -0
  54. sourcerer/presentation/screens/main/messages/__init__.py +0 -0
  55. sourcerer/presentation/screens/main/messages/delete_request.py +12 -0
  56. sourcerer/presentation/screens/main/messages/download_request.py +12 -0
  57. sourcerer/presentation/screens/main/messages/preview_request.py +10 -0
  58. sourcerer/presentation/screens/main/messages/resizing_rule.py +21 -0
  59. sourcerer/presentation/screens/main/messages/select_storage_item.py +11 -0
  60. sourcerer/presentation/screens/main/messages/uncheck_files_request.py +8 -0
  61. sourcerer/presentation/screens/main/messages/upload_request.py +10 -0
  62. sourcerer/presentation/screens/main/mixins/__init__.py +0 -0
  63. sourcerer/presentation/screens/main/mixins/resize_containers_watcher_mixin.py +144 -0
  64. sourcerer/presentation/screens/main/styles.tcss +32 -0
  65. sourcerer/presentation/screens/main/widgets/__init__.py +0 -0
  66. sourcerer/presentation/screens/main/widgets/gradient.py +45 -0
  67. sourcerer/presentation/screens/main/widgets/resizing_rule.py +67 -0
  68. sourcerer/presentation/screens/main/widgets/storage_content.py +691 -0
  69. sourcerer/presentation/screens/main/widgets/storage_list_sidebar.py +143 -0
  70. sourcerer/presentation/screens/preview_content/__init__.py +0 -0
  71. sourcerer/presentation/screens/preview_content/main.py +59 -0
  72. sourcerer/presentation/screens/preview_content/styles.tcss +26 -0
  73. sourcerer/presentation/screens/provider_creds_list/__init__.py +0 -0
  74. sourcerer/presentation/screens/provider_creds_list/main.py +164 -0
  75. sourcerer/presentation/screens/provider_creds_list/styles.tcss +65 -0
  76. sourcerer/presentation/screens/provider_creds_registration/__init__.py +0 -0
  77. sourcerer/presentation/screens/provider_creds_registration/main.py +264 -0
  78. sourcerer/presentation/screens/provider_creds_registration/styles.tcss +42 -0
  79. sourcerer/presentation/screens/question/__init__.py +0 -0
  80. sourcerer/presentation/screens/question/main.py +31 -0
  81. sourcerer/presentation/screens/question/styles.tcss +33 -0
  82. sourcerer/presentation/screens/shared/__init__.py +0 -0
  83. sourcerer/presentation/screens/shared/containers.py +13 -0
  84. sourcerer/presentation/screens/shared/widgets/__init__.py +0 -0
  85. sourcerer/presentation/screens/shared/widgets/button.py +54 -0
  86. sourcerer/presentation/screens/shared/widgets/labeled_input.py +80 -0
  87. sourcerer/presentation/screens/storage_action_progress/__init__.py +0 -0
  88. sourcerer/presentation/screens/storage_action_progress/main.py +476 -0
  89. sourcerer/presentation/screens/storage_action_progress/styles.tcss +43 -0
  90. sourcerer/presentation/settings.py +31 -0
  91. sourcerer/presentation/themes/__init__.py +0 -0
  92. sourcerer/presentation/themes/github_dark.py +21 -0
  93. sourcerer/presentation/utils.py +69 -0
  94. sourcerer/settings.py +72 -0
  95. sourcerer/utils.py +32 -0
@@ -0,0 +1,46 @@
1
+ """
2
+ Dependency injection container configuration for the Sourcerer application.
3
+
4
+ This module defines the dependency injection container that manages the application's
5
+ dependencies and their lifecycle. It provides a centralized way to configure
6
+ and access services, repositories, and other components throughout the application.
7
+ """
8
+
9
+ from pathlib import Path
10
+
11
+ from dependency_injector import containers, providers
12
+
13
+ from sourcerer.infrastructure.access_credentials.repositories import (
14
+ SQLAlchemyCredentialsRepository,
15
+ )
16
+ from sourcerer.infrastructure.db.config import Database
17
+ from sourcerer.infrastructure.file_system.services import FileSystemService
18
+ from sourcerer.settings import APP_DIR, DB_NAME
19
+
20
+ DB_URL = f"sqlite:////{APP_DIR}/{DB_NAME}"
21
+
22
+
23
+ class DiContainer(containers.DeclarativeContainer):
24
+ """
25
+ Dependency injection container for the Sourcerer application.
26
+
27
+ This container manages the application's dependencies including:
28
+ - Database configuration and connection
29
+ - Session factory for database operations
30
+ - Credentials repository for managing access credentials
31
+ - File system service for local file operations
32
+
33
+ The container uses the dependency_injector library to provide
34
+ a clean way to manage dependencies and their lifecycle.
35
+ """
36
+
37
+ config = providers.Configuration()
38
+
39
+ db = providers.Singleton(Database, db_url=DB_URL)
40
+ session_factory = providers.Factory(Database.session_factory, db=db)
41
+
42
+ credentials_repository = providers.Factory(
43
+ SQLAlchemyCredentialsRepository, session_factory
44
+ )
45
+
46
+ file_system_service = providers.Factory(FileSystemService, Path.home())
File without changes
@@ -0,0 +1,78 @@
1
+ import platform
2
+ import sys
3
+ import webbrowser
4
+
5
+ from textual import on
6
+ from textual.app import ComposeResult
7
+ from textual.containers import Horizontal, Container
8
+ from textual.screen import ModalScreen
9
+ from textual.widgets import Label, RichLog
10
+ from textual.css.query import NoMatches
11
+
12
+ from sourcerer import __version__
13
+ from sourcerer.presentation.screens.shared.widgets.button import Button
14
+
15
+
16
+ class CriticalErrorScreen(ModalScreen[bool]):
17
+ """Screen with a parameter."""
18
+
19
+ CSS_PATH = "styles.tcss"
20
+
21
+ def __init__(self, error: str, traceback: str) -> None:
22
+ self.error = error
23
+ self.traceback = traceback
24
+ super().__init__()
25
+
26
+ def compose(self) -> ComposeResult:
27
+ with Container(id="CriticalErrorScreen"):
28
+ yield Label(self.error)
29
+ yield RichLog(highlight=True, markup=True)
30
+ with Horizontal():
31
+ yield Button("Report", name="report")
32
+ yield Button("Dismiss", name="dismiss")
33
+
34
+
35
+ def on_mount(self) -> None:
36
+ """
37
+ Called when the screen is mounted.
38
+ """
39
+ self.query_one("#CriticalErrorScreen").border_title = "Critical Error"
40
+ try:
41
+ text_log = self.query_one(RichLog)
42
+ except NoMatches:
43
+ return
44
+
45
+ text_log.write(self.traceback)
46
+
47
+ @on(Button.Click)
48
+ def on_button_click(self, event: Button.Click) -> None:
49
+ """
50
+ Handle button click events.
51
+ """
52
+ if event.action == "report":
53
+ webbrowser.open(self._build_github_issue_url(), new=0, autoraise=True)
54
+ elif event.action == "dismiss":
55
+ self.dismiss() # type: ignore
56
+
57
+ def _build_github_issue_url(self) -> str:
58
+ """
59
+ Build the GitHub issue URL.
60
+ """
61
+
62
+ error_body = f"""
63
+ Operating System: {platform.system()}({platform.release()})
64
+ Python Version: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}
65
+ Sourcerer Version: {__version__}
66
+ Steps to Reproduce:
67
+
68
+
69
+
70
+ Traceback:
71
+ ```{self.traceback}```
72
+ """
73
+
74
+ return (
75
+ f"https://github.com/the-impact-craft/sourcerer/issues/new?"
76
+ f"title=Runtime issue:{self.error}&"
77
+ f"body={error_body}"
78
+ )
@@ -0,0 +1,41 @@
1
+ CriticalErrorScreen {
2
+ align: center middle;
3
+ content-align: center top;
4
+
5
+
6
+ & > Container {
7
+ padding: 1 2 0 2;
8
+ margin: 0 0;
9
+ width: 75%;
10
+ height: 25;
11
+ border: solid $text-secondary;
12
+ border-title-color: $text-secondary;
13
+
14
+ background: $background-lighten-1;
15
+
16
+ & > Label {
17
+ margin: 1;
18
+ text-align: center;
19
+ column-span: 2;
20
+ width: auto;
21
+ }
22
+
23
+ & > RichLog {
24
+ height: 16;
25
+ }
26
+
27
+ & > Horizontal {
28
+ align: center bottom;
29
+
30
+ & > Button {
31
+ width: auto;
32
+ margin-right: 2;
33
+ color: $text-secondary;
34
+ }
35
+ }
36
+ }
37
+
38
+ }
39
+
40
+
41
+
@@ -0,0 +1,248 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+ from typing import List, Callable
4
+
5
+ from dependency_injector.wiring import Provide
6
+ from textual import on
7
+ from textual.app import ComposeResult
8
+ from textual.binding import Binding
9
+ from textual.containers import Horizontal, Container
10
+ from textual.css.query import NoMatches
11
+ from textual.reactive import reactive
12
+ from textual.screen import ModalScreen
13
+ from textual.widgets import Static
14
+
15
+ from sourcerer.infrastructure.file_system.services import FileSystemService
16
+ from sourcerer.presentation.di_container import DiContainer
17
+ from sourcerer.presentation.screens.file_system_finder.widgets.file_system_navigator import (
18
+ FileSystemNavigator,
19
+ )
20
+ from sourcerer.presentation.screens.shared.widgets.button import Button
21
+
22
+
23
+ @dataclass
24
+ class FileSystemSelectionValidationRule:
25
+ action: Callable[[Path], bool]
26
+ error_message: str
27
+
28
+
29
+ class FileSystemNavigationModal(ModalScreen):
30
+ CONTAINER_ID = "file_system_view_container"
31
+ CSS_PATH = "styles.tcss"
32
+
33
+ BINDINGS = [
34
+ Binding("escape", "app.pop_screen", "Pop screen"),
35
+ ]
36
+
37
+ active_path: reactive[Path] = reactive(Path())
38
+
39
+ def __init__(
40
+ self,
41
+ file_system_service: FileSystemService = Provide[
42
+ DiContainer.file_system_service
43
+ ],
44
+ validation_rules: List[FileSystemSelectionValidationRule] | None = None,
45
+ *args,
46
+ **kwargs,
47
+ ):
48
+ """
49
+ Initialize a FileSystemNavigationModal with file system navigation configuration.
50
+
51
+ Arguments:
52
+ file_system_service (FileSystemService, optional): Service for file system operations.
53
+ Defaults to dependency injection from DiContainer.
54
+ work_dir (Path, optional): Working directory for file navigation.
55
+ Defaults to dependency injection from DiContainer configuration.
56
+ validation_rules (List[FileSystemSelectionValidationRule], optional): Rules for validating file selections.
57
+ Defaults to an empty list if not provided.
58
+ *args: Variable positional arguments passed to parent class constructor.
59
+ **kwargs: Variable keyword arguments passed to parent class constructor.
60
+
61
+ Raises:
62
+ TypeError: If validation rules are not instances of FileSystemSelectionValidationRule,
63
+ or if file_system_service is not a FileSystemService or work_dir is not a Path.
64
+
65
+ Notes:
66
+ - Validates input types before setting instance attributes
67
+ - Logs an error and raises TypeError for invalid input types
68
+ - Ensures type safety for file system navigation configuration
69
+ """
70
+ super().__init__(*args, **kwargs)
71
+
72
+ if not validation_rules:
73
+ validation_rules = []
74
+
75
+ if not all(
76
+ isinstance(rule, FileSystemSelectionValidationRule)
77
+ for rule in validation_rules
78
+ ):
79
+ self.log.error("Invalid validation rules provided")
80
+ raise TypeError(
81
+ "Each validation rule must be an instance of FileSystemSelectionValidationRule"
82
+ )
83
+
84
+ self.file_system_service = file_system_service
85
+ self.work_dir = file_system_service.work_dir
86
+ self.validation_rules = validation_rules
87
+
88
+ def compose(self) -> ComposeResult:
89
+ """
90
+ Compose the user interface for the file system navigation modal.
91
+
92
+ This method sets up the modal's layout with three primary components:
93
+ 1. A static label to display the current active path
94
+ 2. A file system navigator for browsing and selecting files/directories
95
+ 3. Control buttons for closing or applying the current selection
96
+
97
+ Returns:
98
+ ComposeResult: A generator yielding the modal's UI components, including a path label,
99
+ file system navigator, and control buttons.
100
+
101
+ Components:
102
+ - Static label with ID "active-path" for displaying the current path
103
+ - FileSystemNavigator configured with the current working directory and file system service
104
+ - Horizontal layout of "Close" and "Apply" buttons with the ID "controls"
105
+ """
106
+ with Container(id=self.CONTAINER_ID):
107
+ yield Static("", id="active-path")
108
+ yield FileSystemNavigator(
109
+ work_dir=self.work_dir, file_system_service=self.file_system_service
110
+ )
111
+ yield Horizontal(
112
+ Button("Close", name="close", id="close", classes="button"),
113
+ Button("Apply", name="apply", id="apply", classes="button"),
114
+ id="controls",
115
+ )
116
+
117
+ @on(Button.Click)
118
+ def on_button_click(self, event: Button.Click):
119
+ """
120
+ Handle button click events for the file system navigation modal.
121
+ This method is triggered when the user clicks either the "Close" or "Apply" button.
122
+ It determines the action to take based on the button clicked.
123
+
124
+ Args:
125
+ event (Button.Click): The event containing the button that was clicked.
126
+ """
127
+ if event.action == "close":
128
+ self.on_close()
129
+ else:
130
+ self.on_apply()
131
+
132
+ @on(FileSystemNavigator.ActivePathChanged)
133
+ def on_active_path_changed(self, event: FileSystemNavigator.ActivePathChanged):
134
+ """
135
+ Update the active path label when the file system navigator's active path changes.
136
+
137
+ This method is triggered by the `ActivePathChanged` event from the file system navigator. It updates
138
+ the displayed path label and sets the `active_path` attribute of the modal.
139
+
140
+ Arguments:
141
+ event (FileSystemNavigator.ActivePathChanged): Event containing the newly selected path
142
+
143
+ Behavior:
144
+ - Silently returns if the event path is empty
145
+ - Attempts to find the active path label with ID "active-path"
146
+ - If label is not found, silently returns
147
+ - Calculates the relative path from the working directory
148
+ - Appends "/" to the label if the path is a directory
149
+ - Updates the label with the calculated path
150
+ - Sets the `active_path` attribute to the selected path
151
+
152
+ Exceptions:
153
+ - Handles `NoMatches` exception if the active path label cannot be found
154
+ """
155
+ if not event.path:
156
+ return
157
+ try:
158
+ active_path_label: Static = self.query_one("#active-path") # type: ignore
159
+ except NoMatches:
160
+ return
161
+ label = str(event.path.relative_to(self.work_dir))
162
+ if event.path.is_dir():
163
+ label += "/"
164
+ active_path_label.update(label)
165
+ self.active_path = event.path
166
+
167
+ @on(FileSystemNavigator.ActivePathFileDoubleClicked)
168
+ def on_path_double_clicked(
169
+ self, event: FileSystemNavigator.ActivePathFileDoubleClicked
170
+ ):
171
+ """
172
+ Handle the event when a file is double-clicked in the file system navigator.
173
+
174
+ Validates the selected file path and either dismisses the modal with the selected path or displays an error
175
+ notification.
176
+
177
+ Arguments:
178
+ event (FileSystemNavigator.ActivePathFileDoubleClicked): The event containing the double-clicked file path.
179
+
180
+ Behavior:
181
+ - If no path is provided, the method returns without action.
182
+ - Validates the path using the `validate_path` method.
183
+ - If the path is invalid, displays an error notification with validation details.
184
+ - If the path is valid, dismisses the modal and returns the selected path.
185
+ """
186
+ if not event.path:
187
+ return
188
+ valid_status, details = self.validate_path(event.path)
189
+ if not valid_status:
190
+ self.notify(details, severity="error")
191
+ else:
192
+ self.dismiss(event.path)
193
+
194
+ def validate_path(self, path: Path) -> tuple[bool, str]:
195
+ """
196
+ Validate the given path against a set of predefined validation rules.
197
+
198
+ Iterates through the validation rules and checks each rule's action against the provided path.
199
+ If any rule fails or raises an exception, the path is considered invalid.
200
+
201
+ Arguments:
202
+ path (Path): The file system path to validate.
203
+
204
+ Returns:
205
+ tuple[bool, str]: A tuple containing:
206
+ - A boolean indicating whether the path is valid (True) or invalid (False)
207
+ - A string with error details if validation fails, otherwise an empty string
208
+
209
+ Notes:
210
+ - If no validation rules are defined, the path is considered valid by default.
211
+ - Stops validation on the first failed rule and returns its error message.
212
+ - Catches and handles any exceptions raised during rule validation.
213
+ """
214
+ for rule in self.validation_rules or []:
215
+ try:
216
+ if not rule.action(path):
217
+ return False, rule.error_message
218
+ except Exception:
219
+ return False, rule.error_message
220
+ return True, ""
221
+
222
+ def on_close(self):
223
+ """
224
+ Dismiss the file system navigation modal without selecting a path.
225
+
226
+ This method is triggered when the user clicks the close button or uses the designated close action.
227
+ It immediately closes the modal screen and returns None, indicating no file was selected.
228
+
229
+ """
230
+ self.dismiss(None)
231
+
232
+ def on_apply(self):
233
+ """
234
+ Handle the apply action in the file system navigation modal.
235
+
236
+ Validates the currently selected path and either dismisses the modal with the selected path
237
+ or displays an error notification if the path is invalid.
238
+
239
+ Side Effects:
240
+ - Calls `self.validate_path()` to check path validity
241
+ - Displays an error notification if path is invalid
242
+ - Dismisses the modal with the selected path if valid
243
+ """
244
+ valid_status, details = self.validate_path(self.active_path)
245
+ if not valid_status:
246
+ self.notify(details, severity="error")
247
+ else:
248
+ self.dismiss(self.active_path)
@@ -0,0 +1,44 @@
1
+
2
+ FileSystemNavigationModal {
3
+ align: center middle;
4
+ content-align: center top;
5
+
6
+ & > #file_system_view_container {
7
+ padding: 1 2 0 2;
8
+ margin: 0 0;
9
+ width: 90;
10
+ height: auto;
11
+ background: $surface;
12
+
13
+ & > #active-path {
14
+ margin-bottom: 1;
15
+ width: 1fr;
16
+ border: solid $block-cursor-blurred-background;
17
+
18
+ }
19
+
20
+ & > #controls {
21
+ align: center middle;
22
+ content-align: center top;
23
+ padding: 1 0 0 0;
24
+ height: 4;
25
+ width: 100%;
26
+ }
27
+ }
28
+
29
+ .dir-listing-folder {
30
+ padding: 0 0 0 0;
31
+ height: auto;
32
+ layout: grid;
33
+
34
+ & > .folder-name {
35
+ content-align: left middle;
36
+ }
37
+ }
38
+
39
+ .dir-listing-file {
40
+ padding: 0 0 0 0;
41
+ height: auto;
42
+
43
+ }
44
+ }