data-sourcerer 0.3.0__py3-none-any.whl → 0.5.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 (55) hide show
  1. {data_sourcerer-0.3.0.dist-info → data_sourcerer-0.5.0.dist-info}/METADATA +11 -8
  2. {data_sourcerer-0.3.0.dist-info → data_sourcerer-0.5.0.dist-info}/RECORD +55 -34
  3. sourcerer/__init__.py +3 -1
  4. sourcerer/domain/package_meta/__init__.py +0 -0
  5. sourcerer/domain/package_meta/entities.py +9 -0
  6. sourcerer/domain/package_meta/services.py +9 -0
  7. sourcerer/domain/settings/__init__.py +0 -0
  8. sourcerer/domain/settings/entities.py +11 -0
  9. sourcerer/domain/settings/repositories.py +20 -0
  10. sourcerer/domain/settings/services.py +19 -0
  11. sourcerer/domain/storage_provider/entities.py +1 -1
  12. sourcerer/infrastructure/db/models.py +23 -0
  13. sourcerer/infrastructure/package_meta/__init__.py +0 -0
  14. sourcerer/infrastructure/package_meta/services.py +26 -0
  15. sourcerer/infrastructure/settings/__init__.py +0 -0
  16. sourcerer/infrastructure/settings/repositories.py +59 -0
  17. sourcerer/infrastructure/settings/services.py +16 -0
  18. sourcerer/infrastructure/storage_provider/services/azure.py +1 -3
  19. sourcerer/infrastructure/storage_provider/services/gcp.py +1 -3
  20. sourcerer/infrastructure/storage_provider/services/s3.py +1 -2
  21. sourcerer/infrastructure/utils.py +1 -0
  22. sourcerer/presentation/di_container.py +13 -0
  23. sourcerer/presentation/screens/about/__init__.py +0 -0
  24. sourcerer/presentation/screens/about/main.py +60 -0
  25. sourcerer/presentation/screens/about/styles.tcss +32 -0
  26. sourcerer/presentation/screens/file_system_finder/__init__.py +0 -0
  27. sourcerer/presentation/screens/file_system_finder/main.py +3 -9
  28. sourcerer/presentation/screens/main/main.py +116 -8
  29. sourcerer/presentation/screens/main/messages/preview_request.py +1 -0
  30. sourcerer/presentation/screens/main/styles.tcss +13 -4
  31. sourcerer/presentation/screens/main/widgets/storage_content.py +10 -3
  32. sourcerer/presentation/screens/main/widgets/storage_list_sidebar.py +102 -18
  33. sourcerer/presentation/screens/preview_content/main.py +202 -15
  34. sourcerer/presentation/screens/preview_content/styles.tcss +39 -4
  35. sourcerer/presentation/screens/preview_content/text_area_style.py +60 -0
  36. sourcerer/presentation/screens/provider_creds_list/main.py +23 -9
  37. sourcerer/presentation/screens/provider_creds_list/styles.tcss +9 -0
  38. sourcerer/presentation/screens/provider_creds_registration/main.py +3 -3
  39. sourcerer/presentation/screens/settings/__init__.py +0 -0
  40. sourcerer/presentation/screens/settings/main.py +70 -0
  41. sourcerer/presentation/screens/settings/styles.tcss +44 -0
  42. sourcerer/presentation/screens/shared/modal_screens.py +37 -0
  43. sourcerer/presentation/screens/shared/widgets/button.py +11 -0
  44. sourcerer/presentation/screens/shared/widgets/labeled_input.py +1 -3
  45. sourcerer/presentation/screens/storage_action_progress/main.py +1 -2
  46. sourcerer/presentation/screens/storages_list/main.py +24 -9
  47. sourcerer/presentation/screens/storages_list/styles.tcss +7 -0
  48. sourcerer/presentation/screens/storages_registration/main.py +3 -3
  49. sourcerer/presentation/settings.py +1 -0
  50. sourcerer/presentation/utils.py +1 -0
  51. sourcerer/settings.py +2 -0
  52. sourcerer/utils.py +19 -1
  53. {data_sourcerer-0.3.0.dist-info → data_sourcerer-0.5.0.dist-info}/WHEEL +0 -0
  54. {data_sourcerer-0.3.0.dist-info → data_sourcerer-0.5.0.dist-info}/entry_points.txt +0 -0
  55. {data_sourcerer-0.3.0.dist-info → data_sourcerer-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,37 @@
1
+ from typing import ClassVar
2
+
3
+ from textual.binding import Binding, BindingType
4
+ from textual.screen import ModalScreen
5
+
6
+
7
+ class ExitBoundModalScreen(ModalScreen):
8
+ """
9
+ A base class for modal screens that can be exited.
10
+ It provides a method to exit the screen and a flag to indicate if the screen should be exited.
11
+ """
12
+
13
+ BINDINGS: ClassVar[list[BindingType]] = [
14
+ Binding("escape", "cancel_screen", "Pop screen"),
15
+ ]
16
+
17
+ def action_cancel_screen(self):
18
+ """
19
+ Action to exit the screen.
20
+ """
21
+ self.dismiss()
22
+
23
+
24
+ class RefreshTriggerableModalScreen(ExitBoundModalScreen):
25
+ """
26
+ A base class for modal screens that can be refreshed.
27
+ It provides a method to refresh the screen and a flag to indicate if the screen should be refreshed.
28
+ """
29
+
30
+ def __init__(self, *args, **kwargs):
31
+ super().__init__(*args, **kwargs)
32
+ self._requires_storage_refresh = False
33
+
34
+ def action_cancel_screen(self):
35
+ requires_storage_refresh = self._requires_storage_refresh
36
+ self._requires_storage_refresh = False
37
+ self.dismiss(requires_storage_refresh)
@@ -28,6 +28,7 @@ class Button(Label):
28
28
  }
29
29
  }
30
30
  """
31
+ can_focus = True
31
32
 
32
33
  @dataclass
33
34
  class Click(Message):
@@ -52,3 +53,13 @@ class Button(Label):
52
53
  _: An instance of events.Click representing the click event.
53
54
  """
54
55
  self.post_message(self.Click(self.name)) # type: ignore
56
+
57
+ def on_key(self, event: events.Key) -> None:
58
+ """
59
+ Handle key events to trigger click action when the button is focused and activated.
60
+
61
+ Args:
62
+ event (events.Key): The key event to handle.
63
+ """
64
+ if event.key == "enter":
65
+ self.post_message(self.Click(self.name)) # type: ignore
@@ -77,8 +77,6 @@ class LabeledInput(Container):
77
77
  """
78
78
  input_area = self.query_one(".form_input")
79
79
  text = (
80
- input_area.document.text
81
- if isinstance(input_area, TextArea)
82
- else input_area.value # type: ignore
80
+ input_area.document.text if isinstance(input_area, TextArea) else input_area.value # type: ignore
83
81
  )
84
82
  return self.Value(name=self.key, value=text)
@@ -369,7 +369,7 @@ class StorageActionProgressScreen(ModalScreen):
369
369
  key (str): The key/path of the file to delete
370
370
  uuid (str): Unique identifier for the file
371
371
  main_progress_bar (ProgressBar): The main progress bar to update
372
- """ ""
372
+ """
373
373
  if not self.provider_service:
374
374
  self.notify(f"Failed to delete {key}", severity="error")
375
375
  main_progress_bar.advance(1)
@@ -418,7 +418,6 @@ class StorageActionProgressScreen(ModalScreen):
418
418
  finally:
419
419
  self.files_has_been_processed = True
420
420
  elif source_path.is_dir():
421
-
422
421
  files_n = len([i for i in source_path.rglob("*") if i.is_file()])
423
422
  progress_bar = self.query_one(f"#progress_bar_{key.uuid}", ProgressBar)
424
423
  progress_bar.total = files_n
@@ -1,13 +1,14 @@
1
1
  import datetime
2
2
  import uuid
3
3
  from enum import Enum
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
9
+ from textual.binding import Binding, BindingType
8
10
  from textual.containers import Container, Horizontal, VerticalScroll
9
11
  from textual.reactive import reactive
10
- from textual.screen import ModalScreen
11
12
  from textual.widgets import Label
12
13
 
13
14
  from sourcerer.domain.storage.entities import Storage
@@ -15,6 +16,9 @@ from sourcerer.infrastructure.access_credentials.services import CredentialsServ
15
16
  from sourcerer.infrastructure.storage.services import StoragesService
16
17
  from sourcerer.presentation.di_container import DiContainer
17
18
  from sourcerer.presentation.screens.question.main import QuestionScreen
19
+ from sourcerer.presentation.screens.shared.modal_screens import (
20
+ RefreshTriggerableModalScreen,
21
+ )
18
22
  from sourcerer.presentation.screens.shared.widgets.button import Button
19
23
  from sourcerer.presentation.screens.storages_list.messages.reload_storages_request import (
20
24
  ReloadStoragesRequest,
@@ -74,12 +78,17 @@ class StorageRow(Horizontal):
74
78
  self.post_message(ReloadStoragesRequest())
75
79
 
76
80
 
77
- class StoragesListScreen(ModalScreen):
81
+ class StoragesListScreen(RefreshTriggerableModalScreen):
78
82
  CSS_PATH = "styles.tcss"
79
83
 
80
84
  MAIN_CONTAINER_ID = "StoragesListScreen"
81
85
  SETTINGS_CONTAINER_ID = "settings"
82
86
 
87
+ BINDINGS: ClassVar[list[BindingType]] = [
88
+ *RefreshTriggerableModalScreen.BINDINGS,
89
+ Binding("ctrl+n", "add_storage", "Add new storage"),
90
+ ]
91
+
83
92
  storages_list = reactive([], recompose=True)
84
93
 
85
94
  def __init__(
@@ -102,6 +111,7 @@ class StoragesListScreen(ModalScreen):
102
111
  "+Add new storage",
103
112
  name=ControlsEnum.ADD_STORAGE.name,
104
113
  classes="add_storage_button",
114
+ id="add_storage_button",
105
115
  ),
106
116
  id="right-top",
107
117
  )
@@ -119,13 +129,15 @@ class StoragesListScreen(ModalScreen):
119
129
  """
120
130
  Initialize the screen by refreshing the credentials list when the screen is composed.
121
131
  """
122
- self.refresh_storages_list()
132
+ self.refresh_storages_list(set_refresh_flag=False)
123
133
 
124
- def refresh_storages_list(self):
134
+ def refresh_storages_list(self, set_refresh_flag: bool = True):
125
135
  """
126
136
  Refresh the storages list by retrieving the latest storages from the storage service.
127
137
  """
128
138
  self.storages_list = self.storage_service.list()
139
+ if set_refresh_flag:
140
+ self._requires_storage_refresh = True
129
141
 
130
142
  @on(ReloadStoragesRequest)
131
143
  def on_reload_storages_request(self, _: ReloadStoragesRequest):
@@ -149,12 +161,9 @@ class StoragesListScreen(ModalScreen):
149
161
  event (Button.Click): The button click event.
150
162
  """
151
163
  if event.action == ControlsEnum.CANCEL.name:
152
- self.dismiss()
164
+ self.action_cancel_screen()
153
165
  if event.action == ControlsEnum.ADD_STORAGE.name:
154
- self.app.push_screen(
155
- StoragesRegistrationScreen(),
156
- callback=self.create_storage_entry, # type: ignore
157
- )
166
+ self.action_add_storage()
158
167
 
159
168
  def create_storage_entry(self, storage: StorageEntry | None):
160
169
  """
@@ -178,3 +187,9 @@ class StoragesListScreen(ModalScreen):
178
187
  )
179
188
  )
180
189
  self.refresh_storages_list()
190
+
191
+ def action_add_storage(self):
192
+ self.app.push_screen(
193
+ StoragesRegistrationScreen(),
194
+ callback=self.create_storage_entry, # type: ignore
195
+ )
@@ -23,6 +23,13 @@ StoragesListScreen {
23
23
 
24
24
  .add_storage_button {
25
25
  color: $success-darken-2;
26
+
27
+ &:hover {
28
+ color: $primary;
29
+ }
30
+ &:focus {
31
+ color: $primary;
32
+ }
26
33
  }
27
34
 
28
35
  margin-bottom: 1;
@@ -5,11 +5,11 @@ from dependency_injector.wiring import Provide
5
5
  from textual import on
6
6
  from textual.app import ComposeResult
7
7
  from textual.containers import Container, Horizontal, VerticalScroll
8
- from textual.screen import ModalScreen
9
8
  from textual.widgets import Label, Select
10
9
 
11
10
  from sourcerer.infrastructure.access_credentials.services import CredentialsService
12
11
  from sourcerer.presentation.di_container import DiContainer
12
+ from sourcerer.presentation.screens.shared.modal_screens import ExitBoundModalScreen
13
13
  from sourcerer.presentation.screens.shared.widgets.button import Button
14
14
  from sourcerer.presentation.screens.shared.widgets.labeled_input import LabeledInput
15
15
 
@@ -25,7 +25,7 @@ class StorageEntry:
25
25
  credentials_uuid: str
26
26
 
27
27
 
28
- class StoragesRegistrationScreen(ModalScreen):
28
+ class StoragesRegistrationScreen(ExitBoundModalScreen):
29
29
  CSS_PATH = "styles.tcss"
30
30
 
31
31
  MAIN_CONTAINER_ID = "StoragesRegistrationScreen"
@@ -84,7 +84,7 @@ class StoragesRegistrationScreen(ModalScreen):
84
84
  collected authentication fields.
85
85
  """
86
86
  if event.action == ControlsEnum.CANCEL.name:
87
- self.dismiss()
87
+ self.action_cancel_screen()
88
88
  elif event.action == ControlsEnum.CREATE.name:
89
89
  storage_name = self.query_one("#storage_name", LabeledInput).get().value
90
90
  if not storage_name:
@@ -3,6 +3,7 @@
3
3
  This module contains various configuration constants and settings used throughout
4
4
  the application, including UI elements and display configurations.
5
5
  """
6
+
6
7
  from enum import Enum
7
8
 
8
9
  NO_DATA_LOGO = """
@@ -4,6 +4,7 @@ Utility functions for the presentation layer.
4
4
  This module provides helper functions for the presentation layer,
5
5
  particularly for retrieving and initializing storage provider services.
6
6
  """
7
+
7
8
  from dependency_injector.wiring import Provide
8
9
 
9
10
  from sourcerer.domain.access_credentials.repositories import BaseCredentialsRepository
sourcerer/settings.py CHANGED
@@ -74,3 +74,5 @@ TEXT_EXTENSIONS = {
74
74
 
75
75
 
76
76
  PAGE_SIZE = 100
77
+ PREVIEW_LENGTH_LIMIT = 10_000
78
+ PREVIEW_LIMIT_SIZE = 2 * 1024 * 1024 # 2 MB
sourcerer/utils.py CHANGED
@@ -1,8 +1,10 @@
1
- # Generate and store encryption key in a file
1
+ import contextlib
2
2
  import os
3
3
  import uuid
4
4
  from pathlib import Path
5
5
 
6
+ import requests
7
+
6
8
 
7
9
  def get_encryption_key(path: Path) -> str:
8
10
  """
@@ -30,3 +32,19 @@ def get_encryption_key(path: Path) -> str:
30
32
  f.write(new_key)
31
33
 
32
34
  return new_key
35
+
36
+
37
+ def get_last_package_version(name):
38
+ """
39
+ Fetch the latest version of a package from PyPI.
40
+ Args:
41
+ name (str): The name of the package.
42
+ Returns:
43
+ str: The latest version of the package, or None if an error occurs.
44
+ """
45
+ with contextlib.suppress(Exception):
46
+ url = f"https://pypi.org/pypi/{name}/json"
47
+ response = requests.get(url, timeout=5)
48
+ response.raise_for_status()
49
+ return response.json()["info"]["version"]
50
+ return None