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.
- {data_sourcerer-0.3.0.dist-info → data_sourcerer-0.5.0.dist-info}/METADATA +11 -8
- {data_sourcerer-0.3.0.dist-info → data_sourcerer-0.5.0.dist-info}/RECORD +55 -34
- sourcerer/__init__.py +3 -1
- sourcerer/domain/package_meta/__init__.py +0 -0
- sourcerer/domain/package_meta/entities.py +9 -0
- sourcerer/domain/package_meta/services.py +9 -0
- sourcerer/domain/settings/__init__.py +0 -0
- sourcerer/domain/settings/entities.py +11 -0
- sourcerer/domain/settings/repositories.py +20 -0
- sourcerer/domain/settings/services.py +19 -0
- sourcerer/domain/storage_provider/entities.py +1 -1
- sourcerer/infrastructure/db/models.py +23 -0
- sourcerer/infrastructure/package_meta/__init__.py +0 -0
- sourcerer/infrastructure/package_meta/services.py +26 -0
- sourcerer/infrastructure/settings/__init__.py +0 -0
- sourcerer/infrastructure/settings/repositories.py +59 -0
- sourcerer/infrastructure/settings/services.py +16 -0
- sourcerer/infrastructure/storage_provider/services/azure.py +1 -3
- sourcerer/infrastructure/storage_provider/services/gcp.py +1 -3
- sourcerer/infrastructure/storage_provider/services/s3.py +1 -2
- sourcerer/infrastructure/utils.py +1 -0
- sourcerer/presentation/di_container.py +13 -0
- sourcerer/presentation/screens/about/__init__.py +0 -0
- sourcerer/presentation/screens/about/main.py +60 -0
- sourcerer/presentation/screens/about/styles.tcss +32 -0
- sourcerer/presentation/screens/file_system_finder/__init__.py +0 -0
- sourcerer/presentation/screens/file_system_finder/main.py +3 -9
- sourcerer/presentation/screens/main/main.py +116 -8
- sourcerer/presentation/screens/main/messages/preview_request.py +1 -0
- sourcerer/presentation/screens/main/styles.tcss +13 -4
- sourcerer/presentation/screens/main/widgets/storage_content.py +10 -3
- sourcerer/presentation/screens/main/widgets/storage_list_sidebar.py +102 -18
- sourcerer/presentation/screens/preview_content/main.py +202 -15
- sourcerer/presentation/screens/preview_content/styles.tcss +39 -4
- sourcerer/presentation/screens/preview_content/text_area_style.py +60 -0
- sourcerer/presentation/screens/provider_creds_list/main.py +23 -9
- sourcerer/presentation/screens/provider_creds_list/styles.tcss +9 -0
- sourcerer/presentation/screens/provider_creds_registration/main.py +3 -3
- sourcerer/presentation/screens/settings/__init__.py +0 -0
- sourcerer/presentation/screens/settings/main.py +70 -0
- sourcerer/presentation/screens/settings/styles.tcss +44 -0
- sourcerer/presentation/screens/shared/modal_screens.py +37 -0
- sourcerer/presentation/screens/shared/widgets/button.py +11 -0
- sourcerer/presentation/screens/shared/widgets/labeled_input.py +1 -3
- sourcerer/presentation/screens/storage_action_progress/main.py +1 -2
- sourcerer/presentation/screens/storages_list/main.py +24 -9
- sourcerer/presentation/screens/storages_list/styles.tcss +7 -0
- sourcerer/presentation/screens/storages_registration/main.py +3 -3
- sourcerer/presentation/settings.py +1 -0
- sourcerer/presentation/utils.py +1 -0
- sourcerer/settings.py +2 -0
- sourcerer/utils.py +19 -1
- {data_sourcerer-0.3.0.dist-info → data_sourcerer-0.5.0.dist-info}/WHEEL +0 -0
- {data_sourcerer-0.3.0.dist-info → data_sourcerer-0.5.0.dist-info}/entry_points.txt +0 -0
- {data_sourcerer-0.3.0.dist-info → data_sourcerer-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: data-sourcerer
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.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
|
@@ -16,6 +16,7 @@ Requires-Dist: dependency-injector<5.0.0,>=4.43.0
|
|
16
16
|
Requires-Dist: google-cloud-storage<4.0.0,>=3.1.0
|
17
17
|
Requires-Dist: humanize<5.0.0,>=4.12.1
|
18
18
|
Requires-Dist: msgspec>=0.19.0
|
19
|
+
Requires-Dist: packaging>=25.0
|
19
20
|
Requires-Dist: pyopenssl>=22.1.0; sys_platform == 'linux'
|
20
21
|
Requires-Dist: sqlalchemy-utils<1.0.0,>=0.41.2
|
21
22
|
Requires-Dist: sqlalchemy<3.0.0,>=2.0.38
|
@@ -36,21 +37,23 @@ Description-Content-Type: text/markdown
|
|
36
37
|
|
37
38
|
# 🧙♂️ Sourcerer
|
38
39
|
|
39
|
-
**Sourcerer** is a CLI-based cloud storage explorer that provides a unified interface for developers and DevOps
|
40
|
-
engineers to view and manage files across multiple cloud providers like
|
40
|
+
**Sourcerer** is a CLI-based cloud storage explorer that provides a unified interface for developers and DevOps
|
41
|
+
engineers to view and manage files across multiple cloud providers like
|
41
42
|
**GCP Storage**, **Azure Storage**, **AWS S3**, and **S3-compatible services**.
|
42
43
|
|
43
44
|
> Your terminal. Your storages. Your control.
|
44
45
|
|
46
|
+
[Demo page](https://the-impact-craft.github.io/sourcerer/)
|
47
|
+
|
45
48
|
---
|
46
49
|
|
47
50
|
## ✨ Features
|
48
51
|
|
49
|
-
- 🔍 Unified file browser for GCP Storage, Azure Storage, AWS S3, and S3-compatible services
|
50
|
-
- 🧭 Terminal UI (TUI) built with [Textual](https://github.com/Textualize/textual)
|
51
|
-
- 🗂️ Explore buckets and objects seamlessly
|
52
|
-
- 🔄 Upload, download, and delete files
|
53
|
-
- 🔐 Secure credential management via local **SQLite database**
|
52
|
+
- 🔍 Unified file browser for GCP Storage, Azure Storage, AWS S3, and S3-compatible services
|
53
|
+
- 🧭 Terminal UI (TUI) built with [Textual](https://github.com/Textualize/textual)
|
54
|
+
- 🗂️ Explore buckets and objects seamlessly
|
55
|
+
- 🔄 Upload, download, and delete files
|
56
|
+
- 🔐 Secure credential management via local **SQLite database**
|
54
57
|
|
55
58
|
---
|
56
59
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
sourcerer/__init__.py,sha256=
|
2
|
-
sourcerer/settings.py,sha256=
|
3
|
-
sourcerer/utils.py,sha256=
|
1
|
+
sourcerer/__init__.py,sha256=8QZGs8YbjerouVXeMDz5qYQPZeFHcyXhaO8IKkxzePM,636
|
2
|
+
sourcerer/settings.py,sha256=jIUcq9-_yYPxLS_8m1iKlkGCOg_pbhIjHXkR1LQmHQI,1463
|
3
|
+
sourcerer/utils.py,sha256=l5AitLCh58TdYwhC-Z2wQVXvq1q37ObuXeqDYba4hRU,1361
|
4
4
|
sourcerer/domain/__init__.py,sha256=rV21d-dD-e0q4EQ2LfWDSDLlrUOjnHnWBtWPujoue0o,556
|
5
5
|
sourcerer/domain/access_credentials/__init__.py,sha256=pFAwnr74uy09e7kubYsuaqzkVPkTA66dwjKzpIGQkAY,217
|
6
6
|
sourcerer/domain/access_credentials/entities.py,sha256=m5hktJPmcwgG7vG-HKasA-gAd8US6jrZxGso4sCLZc8,2123
|
@@ -11,17 +11,24 @@ sourcerer/domain/file_system/__init__.py,sha256=5diScp5Q8Hw8e_4vqacVocQBw0KA7fPs
|
|
11
11
|
sourcerer/domain/file_system/entities.py,sha256=IeTu5VYxmBQMH7AcKfW1Muk4wUmgZ769eWwNyqeWRpw,2060
|
12
12
|
sourcerer/domain/file_system/exceptions.py,sha256=TairaMaLkSHgbiq3pImkmVZAACoVHy-1vQMl7Ox24cQ,509
|
13
13
|
sourcerer/domain/file_system/services.py,sha256=kQeEM1Lt28UBVT9uG4M2iFDSxSvQdaJw4QzM9giUdjY,2208
|
14
|
+
sourcerer/domain/package_meta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
+
sourcerer/domain/package_meta/entities.py,sha256=IWAYpoYp4vrf2kxaLtlZrQSP5QmI8neufIY0QehWJjY,183
|
16
|
+
sourcerer/domain/package_meta/services.py,sha256=5hJDrOWV0wuTCoL4IrZ1uI1-Y-xH-NfOGSZlGlnmwP0,256
|
17
|
+
sourcerer/domain/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
|
+
sourcerer/domain/settings/entities.py,sha256=JJLFgJMO9NnpdOjGkAW6z-qxosIQigtbW3B-qcrdDD4,244
|
19
|
+
sourcerer/domain/settings/repositories.py,sha256=0EQxUjWnlL8raPj52pm7774U4rSgHHZfYy5lBfli2LE,611
|
20
|
+
sourcerer/domain/settings/services.py,sha256=j1nwddLhaWfmvtFMCYmQjsQBE34FGF06YLtF-lnsrRQ,817
|
14
21
|
sourcerer/domain/shared/__init__.py,sha256=pkCn6PfBLIlYT5q4xWq3cNtOfbUrrePiH06TduLq6_o,148
|
15
22
|
sourcerer/domain/shared/entities.py,sha256=jJR1PhJBWrSeJyEFvauEYJ1UXG7BcT6pi2oFpmglu4I,497
|
16
23
|
sourcerer/domain/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
24
|
sourcerer/domain/storage/entities.py,sha256=bSWVFx4Hh7_RLCHVLN_U2Wg6XxeYUyzDGLUpbFuWTLo,687
|
18
25
|
sourcerer/domain/storage/repositories.py,sha256=v7RU8_X_HgFT3wn7Yc5m07aN7DEbsBMh8sRZSRSCTxU,1003
|
19
26
|
sourcerer/domain/storage_provider/__init__.py,sha256=P3RUH9LFkWez3ehCczgVbnbp0tZZepPeOQf9spsC8FQ,192
|
20
|
-
sourcerer/domain/storage_provider/entities.py,sha256=
|
27
|
+
sourcerer/domain/storage_provider/entities.py,sha256=koaY4M4nWeie9kXJrHLJ85fEvaLxOQJEq8Zl3vmGwMM,1931
|
21
28
|
sourcerer/domain/storage_provider/exceptions.py,sha256=6xK5r62Bhedx3vV0_i7Eu5ZG5IExxeiuaGHG5sX17i4,508
|
22
29
|
sourcerer/domain/storage_provider/services.py,sha256=Zm6nrKqQJW-9ZaqTp9wQaQuwMeH6hkLJZCkhQ_sTRF0,3997
|
23
30
|
sourcerer/infrastructure/__init__.py,sha256=HQoqA8S9Vx2dr1Eua86wu_YxwXyY6jqa4IfEoZJcXcQ,616
|
24
|
-
sourcerer/infrastructure/utils.py,sha256=
|
31
|
+
sourcerer/infrastructure/utils.py,sha256=o6yxQ4f-kKYn-oRIEKpx3YWXwEEXfEEBvntW_JRMYnk,5199
|
25
32
|
sourcerer/infrastructure/access_credentials/__init__.py,sha256=7BSXnI9n59_PuGxHjOra6PG82R_6JlrU4S1tsJx4WGM,249
|
26
33
|
sourcerer/infrastructure/access_credentials/exceptions.py,sha256=usad48RTA7ub8AfnUs2EzbD-fiUwpmnRGjJluBjd3m4,1101
|
27
34
|
sourcerer/infrastructure/access_credentials/registry.py,sha256=YEjkbh5639j8LkAocozC8O_7t1pPcrJwbL1LCJhiGkY,3416
|
@@ -29,10 +36,15 @@ sourcerer/infrastructure/access_credentials/repositories.py,sha256=_GLT-d3yarQD9
|
|
29
36
|
sourcerer/infrastructure/access_credentials/services.py,sha256=QFU-eRKsl7P4Eg44i-A8BT6llVlT4vc6juvlJE39dq4,15720
|
30
37
|
sourcerer/infrastructure/db/__init__.py,sha256=sjx2F0aDnxej7-FAjhFJYIQQ5d2apmItkquKbvGYIBc,175
|
31
38
|
sourcerer/infrastructure/db/config.py,sha256=c3ubAxBWt-zDUwTe3pAgJ86-rw-LZZAZppuBRo93Xn8,1792
|
32
|
-
sourcerer/infrastructure/db/models.py,sha256=
|
39
|
+
sourcerer/infrastructure/db/models.py,sha256=SnGWpz4PWBeXm6UvXU_rKhQcxscs_ckqjtP7WV2Cu9o,3571
|
33
40
|
sourcerer/infrastructure/file_system/__init__.py,sha256=Swx8arwXeZ4E40ViDSpQglmUrIlpuEvZY6crcWpGC2g,219
|
34
41
|
sourcerer/infrastructure/file_system/exceptions.py,sha256=obx0NxVuhVzLgL5K2MA5d4rNImlRHJ4Gr-2RrIuKJqk,2944
|
35
42
|
sourcerer/infrastructure/file_system/services.py,sha256=J0XHv-zF8WblE6bzzEpKkqU_k3gefMj_OArWBRFtGHA,5649
|
43
|
+
sourcerer/infrastructure/package_meta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
44
|
+
sourcerer/infrastructure/package_meta/services.py,sha256=GKX9W5BGaeOkY4eeemp_mLRzj_DYaUyKIHZDTKeLCtU,873
|
45
|
+
sourcerer/infrastructure/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
|
+
sourcerer/infrastructure/settings/repositories.py,sha256=BZwDtjwR6pM79NB2q1Vr6VB-jjGG9GqkFP9kG4Oatgk,2104
|
47
|
+
sourcerer/infrastructure/settings/services.py,sha256=-wGRpkxSPvrCZnkUhiXztrfJCchxZEhNDwSVgZ8DYsQ,613
|
36
48
|
sourcerer/infrastructure/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
37
49
|
sourcerer/infrastructure/storage/repositories.py,sha256=GTMxxdeRmnxqpLUq_FCX9i28tkUL6xYRSsv9rVYFTzM,2334
|
38
50
|
sourcerer/infrastructure/storage/services.py,sha256=wP37li_ts-By0Z_q5pizl__v-nRjFlkM7LlAJghujnc,1024
|
@@ -40,29 +52,33 @@ sourcerer/infrastructure/storage_provider/__init__.py,sha256=GONjDCsTmd6f_fF3lzx
|
|
40
52
|
sourcerer/infrastructure/storage_provider/exceptions.py,sha256=acx3IIXD2yWlzLvD2asJBEpKEa6eJW31uKkzzr8MrR4,3336
|
41
53
|
sourcerer/infrastructure/storage_provider/registry.py,sha256=8dbRLOx1jLK_i18uuh_JnKvId9NJBECKg4nG9F_dFH4,2249
|
42
54
|
sourcerer/infrastructure/storage_provider/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
43
|
-
sourcerer/infrastructure/storage_provider/services/azure.py,sha256=
|
44
|
-
sourcerer/infrastructure/storage_provider/services/gcp.py,sha256=
|
45
|
-
sourcerer/infrastructure/storage_provider/services/s3.py,sha256=
|
55
|
+
sourcerer/infrastructure/storage_provider/services/azure.py,sha256=ZCglIQ0DQX1mYimsT4rS1tUvZtLbvpR_NZZzjGw7sB4,9757
|
56
|
+
sourcerer/infrastructure/storage_provider/services/gcp.py,sha256=HyiGF-xfUYnykZOaAqqAFpuASsf0Plcp3kPjfvRZuXM,9058
|
57
|
+
sourcerer/infrastructure/storage_provider/services/s3.py,sha256=avRAtgZTobKnovfCG_lnW9xZOG77cnpDnZGazn85XHc,9239
|
46
58
|
sourcerer/presentation/__init__.py,sha256=kzOeaTpy9hm61MLl_nybdooRrawFUd1uEX4f3Y-84ZU,472
|
47
59
|
sourcerer/presentation/app.py,sha256=ROu3vSWzo6d8W30A9Zqi5zdLcVeHJsGLDJMLTKrthHE,1018
|
48
|
-
sourcerer/presentation/di_container.py,sha256=
|
49
|
-
sourcerer/presentation/settings.py,sha256=
|
50
|
-
sourcerer/presentation/utils.py,sha256=
|
60
|
+
sourcerer/presentation/di_container.py,sha256=m05fzDTGI11BXDQy1xmNl8rPHZfS1Zo_QlPhAhIyOHQ,2737
|
61
|
+
sourcerer/presentation/settings.py,sha256=TcoDQWzekC4pYBKTVrUoXZK5s05or_F0KXmW04P6uEA,1256
|
62
|
+
sourcerer/presentation/utils.py,sha256=ea02DEM1QOKtquc1x4ijLpD47p5whteesQP33N6Xizs,2980
|
51
63
|
sourcerer/presentation/screens/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
64
|
+
sourcerer/presentation/screens/about/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
65
|
+
sourcerer/presentation/screens/about/main.py,sha256=7CnXMOYHCmEHjvnEbyHbrV-RUsw3g0OZiKdyiDp1h-w,2091
|
66
|
+
sourcerer/presentation/screens/about/styles.tcss,sha256=ImwlRBJjlyjIKiFb5WF5BydFAErNDuonyGKIdxtNpzM,484
|
52
67
|
sourcerer/presentation/screens/critical_error/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
53
68
|
sourcerer/presentation/screens/critical_error/main.py,sha256=10Ip1InBzktwwM2ijKXBOkhvBXGorw1X8EGxdh75WZE,2208
|
54
69
|
sourcerer/presentation/screens/critical_error/styles.tcss,sha256=mURvbf0_npkRtzVBs2bVBybbyNK9cO_6Ar2Muk1Mpv8,604
|
55
|
-
sourcerer/presentation/screens/file_system_finder/
|
70
|
+
sourcerer/presentation/screens/file_system_finder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
71
|
+
sourcerer/presentation/screens/file_system_finder/main.py,sha256=8V603I74iKoyMMChbl8XjpQAu614qpW0sbRlkCAxSjg,10221
|
56
72
|
sourcerer/presentation/screens/file_system_finder/styles.tcss,sha256=fZkdwXFsDkjXkaIskLxoQ_YHsLWKjgrn6hYseugg_68,718
|
57
73
|
sourcerer/presentation/screens/file_system_finder/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
58
74
|
sourcerer/presentation/screens/file_system_finder/widgets/file_system_navigator.py,sha256=giQXiNEsSAmBV-YWc1eXcJDFUWxpCppv0oNxgvrLZxg,31070
|
59
75
|
sourcerer/presentation/screens/main/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
60
|
-
sourcerer/presentation/screens/main/main.py,sha256=
|
61
|
-
sourcerer/presentation/screens/main/styles.tcss,sha256=
|
76
|
+
sourcerer/presentation/screens/main/main.py,sha256=joYB72Z1H-kEYkJomMfGPN4m4R_QLUuxUbQeCBUtORs,27945
|
77
|
+
sourcerer/presentation/screens/main/styles.tcss,sha256=MgL2UwcLKWXY9R3qqk3cgBqjpW26r68MorAX42KRc28,502
|
62
78
|
sourcerer/presentation/screens/main/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
63
79
|
sourcerer/presentation/screens/main/messages/delete_request.py,sha256=puaU1UtbDErfYi8ViPEawhSbycnUpwdE81zZfzlslNE,203
|
64
80
|
sourcerer/presentation/screens/main/messages/download_request.py,sha256=3urSTrvNbod1FrXfu-C1UDZqOu5D0OC-NJsKJhhprXE,205
|
65
|
-
sourcerer/presentation/screens/main/messages/preview_request.py,sha256
|
81
|
+
sourcerer/presentation/screens/main/messages/preview_request.py,sha256=-UVWFVr4vsB0LGj97MTT8pLbJ3eNJClMWvT6vQ4lqw8,198
|
66
82
|
sourcerer/presentation/screens/main/messages/refresh_storages_list_request.py,sha256=NxOBcUf5oKF1cMCcHZny8qG2Jv6zTdPmYeFV9qORHh8,136
|
67
83
|
sourcerer/presentation/screens/main/messages/resizing_rule.py,sha256=ws7lzS08h6qqeihF66XV5FsX26YkjQOje_4vgCw2mqI,332
|
68
84
|
sourcerer/presentation/screens/main/messages/select_storage_item.py,sha256=iZOwzOwFLhsr58WV01-NtTFr4LXKykz1i76nayLkCLw,269
|
@@ -73,43 +89,48 @@ sourcerer/presentation/screens/main/mixins/resize_containers_watcher_mixin.py,sh
|
|
73
89
|
sourcerer/presentation/screens/main/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
74
90
|
sourcerer/presentation/screens/main/widgets/gradient.py,sha256=Ow6oaX0tsFNbUKRh_e9ITph20_tKoQgqzM7rujwKmxs,1263
|
75
91
|
sourcerer/presentation/screens/main/widgets/resizing_rule.py,sha256=W4tAbSZlmAIytP1BNlSvBeiMGA7cD6l13IpxG2JYzxk,2088
|
76
|
-
sourcerer/presentation/screens/main/widgets/storage_content.py,sha256=
|
77
|
-
sourcerer/presentation/screens/main/widgets/storage_list_sidebar.py,sha256=
|
92
|
+
sourcerer/presentation/screens/main/widgets/storage_content.py,sha256=7ngNzPSagFi2GzzYNzFQqq6DCppLil2taghJdkSube0,26278
|
93
|
+
sourcerer/presentation/screens/main/widgets/storage_list_sidebar.py,sha256=gGRO3r0z76J2snLzpzJDMUfb6__7il4FGVmXqoji_sg,11029
|
78
94
|
sourcerer/presentation/screens/preview_content/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
79
|
-
sourcerer/presentation/screens/preview_content/main.py,sha256=
|
80
|
-
sourcerer/presentation/screens/preview_content/styles.tcss,sha256=
|
95
|
+
sourcerer/presentation/screens/preview_content/main.py,sha256=UejOzmE6cl6HKTH_N0-spoByyTJRxZwI8-0nm3S8hi0,9111
|
96
|
+
sourcerer/presentation/screens/preview_content/styles.tcss,sha256=ESFpZgwZlehkrbCVhA45ODiXTpFuwqQPvQypsSjGxpQ,887
|
97
|
+
sourcerer/presentation/screens/preview_content/text_area_style.py,sha256=AOX8CG9A77gQIhF8GHTW45clPWrQcRKqu0KJcUzXrFU,2699
|
81
98
|
sourcerer/presentation/screens/provider_creds_list/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
82
|
-
sourcerer/presentation/screens/provider_creds_list/main.py,sha256=
|
83
|
-
sourcerer/presentation/screens/provider_creds_list/styles.tcss,sha256=
|
99
|
+
sourcerer/presentation/screens/provider_creds_list/main.py,sha256=hOljhgw6f-RFckfPmzQoFJpvHXhjzaP2kVMQJdsjQp0,9135
|
100
|
+
sourcerer/presentation/screens/provider_creds_list/styles.tcss,sha256=CuSeHllFGWHW25av4UYm4YkhXZ8x0fDi6bEjv7hWOYA,1107
|
84
101
|
sourcerer/presentation/screens/provider_creds_list/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
85
102
|
sourcerer/presentation/screens/provider_creds_list/messages/reload_credentials_request.py,sha256=zkyLFkXZHQMj5OsDU_IiInUnezHNWyvYn8yZLoJmJn0,134
|
86
103
|
sourcerer/presentation/screens/provider_creds_registration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
87
|
-
sourcerer/presentation/screens/provider_creds_registration/main.py,sha256=
|
104
|
+
sourcerer/presentation/screens/provider_creds_registration/main.py,sha256=CkGFef_JdMvcH0ve-XPGA_I8ZRTdzJNurvjUjZ0ZjLo,11422
|
88
105
|
sourcerer/presentation/screens/provider_creds_registration/styles.tcss,sha256=gd1SNeRoHTYwNzdGxK-2aDqNPeY5b2wFWajtoNn5--Y,612
|
89
106
|
sourcerer/presentation/screens/question/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
90
107
|
sourcerer/presentation/screens/question/main.py,sha256=7ogFbKrqP9deBYfgq7Bs6NkiSdy3whEu51mW_--TCIY,980
|
91
108
|
sourcerer/presentation/screens/question/styles.tcss,sha256=NT8Ty4opqYLG1-sal3m1us4M4LNhtTcQywlU-Al-v7o,396
|
109
|
+
sourcerer/presentation/screens/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
110
|
+
sourcerer/presentation/screens/settings/main.py,sha256=sjFEqGKKJ1bfb901Vz21HPx-BRfndtXLFQ_jeRat_Gg,2464
|
111
|
+
sourcerer/presentation/screens/settings/styles.tcss,sha256=116APkhBck6ktct-P_wqokrm80edrkeA2NnwLYVqFak,705
|
92
112
|
sourcerer/presentation/screens/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
93
113
|
sourcerer/presentation/screens/shared/containers.py,sha256=9Tkl5SbPNgycZlfp5Pq50pHnJqbP0EckmxayXPPuhFs,378
|
114
|
+
sourcerer/presentation/screens/shared/modal_screens.py,sha256=ycpqEy9M3Dh_q-EO44_YbIyDAb9lkF8ZCUUje5fh42c,1128
|
94
115
|
sourcerer/presentation/screens/shared/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
95
|
-
sourcerer/presentation/screens/shared/widgets/button.py,sha256=
|
96
|
-
sourcerer/presentation/screens/shared/widgets/labeled_input.py,sha256=
|
116
|
+
sourcerer/presentation/screens/shared/widgets/button.py,sha256=4L_reDy_nJg7l-xJwXbl6wWx0X5zYWyKwKd5OT_Qkms,1941
|
117
|
+
sourcerer/presentation/screens/shared/widgets/labeled_input.py,sha256=q6SBBnQykxDaMoVA3J8yJe3Ce9SPtElbO4OkShwBMQU,2774
|
97
118
|
sourcerer/presentation/screens/shared/widgets/spinner.py,sha256=Do_Gc32GKA9ibFhORjfiK0RM3a7bvhnJ1SvnCXQJsHY,1713
|
98
119
|
sourcerer/presentation/screens/storage_action_progress/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
99
|
-
sourcerer/presentation/screens/storage_action_progress/main.py,sha256=
|
120
|
+
sourcerer/presentation/screens/storage_action_progress/main.py,sha256=j5APRiZVpp08AN2jWTSrxj8jCGqTJ1Vczqf3_ezAHwE,17895
|
100
121
|
sourcerer/presentation/screens/storage_action_progress/styles.tcss,sha256=ffvDxRWhckE5tjEG4jwlhQNqeQsYpdF71104StWCGWU,818
|
101
122
|
sourcerer/presentation/screens/storages_list/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
102
|
-
sourcerer/presentation/screens/storages_list/main.py,sha256=
|
103
|
-
sourcerer/presentation/screens/storages_list/styles.tcss,sha256=
|
123
|
+
sourcerer/presentation/screens/storages_list/main.py,sha256=i7Kxxz4W-OAg3fynUAKH7JO-PTwxUgegztbmUeFQ5eI,6823
|
124
|
+
sourcerer/presentation/screens/storages_list/styles.tcss,sha256=iKECoDkKiQB7UwxmHguYMd0nSzEWFScczXFWDpFPdN0,881
|
104
125
|
sourcerer/presentation/screens/storages_list/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
105
126
|
sourcerer/presentation/screens/storages_list/messages/reload_storages_request.py,sha256=PtwlWgPUP_ZcAid535fd4Py6AvKUVhaXbc0WMka4QD0,131
|
106
127
|
sourcerer/presentation/screens/storages_registration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
107
|
-
sourcerer/presentation/screens/storages_registration/main.py,sha256=
|
128
|
+
sourcerer/presentation/screens/storages_registration/main.py,sha256=xam1qrUgmh7kA1y7EEbUAO1znZRpd1FQ7igN7Q_4j50,3802
|
108
129
|
sourcerer/presentation/screens/storages_registration/styles.tcss,sha256=Yd78pkiaaShb30r5s6qlYlLxyB62DJNeAQqs_gMcP6k,601
|
109
130
|
sourcerer/presentation/themes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
110
131
|
sourcerer/presentation/themes/github_dark.py,sha256=9E1mEOr701nU-ZDSKBccMl3GYchroCEsxEVelm5oI-E,497
|
111
|
-
data_sourcerer-0.
|
112
|
-
data_sourcerer-0.
|
113
|
-
data_sourcerer-0.
|
114
|
-
data_sourcerer-0.
|
115
|
-
data_sourcerer-0.
|
132
|
+
data_sourcerer-0.5.0.dist-info/METADATA,sha256=IJq5I6ZR1wDk_-IM9M3zk4j63kOh6JsgGVbL1fQulNk,2614
|
133
|
+
data_sourcerer-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
134
|
+
data_sourcerer-0.5.0.dist-info/entry_points.txt,sha256=CyD02GehPW6QuhR5oDY5tLLRHQ9qbPXe0v3aT1pK3N8,62
|
135
|
+
data_sourcerer-0.5.0.dist-info/licenses/LICENSE,sha256=HjZ7RAG3i6izxvitGfY4feHfvW5F8DPj5eF0YBSf2rI,1073
|
136
|
+
data_sourcerer-0.5.0.dist-info/RECORD,,
|
sourcerer/__init__.py
CHANGED
File without changes
|
File without changes
|
@@ -0,0 +1,20 @@
|
|
1
|
+
from abc import ABCMeta, abstractmethod
|
2
|
+
|
3
|
+
from sourcerer.domain.settings.entities import Settings
|
4
|
+
|
5
|
+
|
6
|
+
class BaseSettingsRepository(metaclass=ABCMeta):
|
7
|
+
@abstractmethod
|
8
|
+
def get_settings(self) -> Settings:
|
9
|
+
"""Retrieve all settings as a Settings."""
|
10
|
+
raise NotImplementedError()
|
11
|
+
|
12
|
+
@abstractmethod
|
13
|
+
def get_setting(self, key: str) -> str:
|
14
|
+
"""Retrieve a setting by its key."""
|
15
|
+
raise NotImplementedError()
|
16
|
+
|
17
|
+
@abstractmethod
|
18
|
+
def set_setting(self, key: str, value: str) -> None:
|
19
|
+
"""Set a setting with a given key and value."""
|
20
|
+
raise NotImplementedError()
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from sourcerer.domain.settings.entities import Settings
|
2
|
+
from sourcerer.domain.settings.repositories import BaseSettingsRepository
|
3
|
+
|
4
|
+
|
5
|
+
class BaseSettingsService:
|
6
|
+
def __init__(self, repository: BaseSettingsRepository):
|
7
|
+
self.repository = repository
|
8
|
+
|
9
|
+
def load_settings(self) -> Settings:
|
10
|
+
"""Load settings from the settings file."""
|
11
|
+
raise NotImplementedError("This method should be implemented by subclasses.")
|
12
|
+
|
13
|
+
def get_setting(self, key: str) -> str:
|
14
|
+
"""Get the value of a setting by its key."""
|
15
|
+
raise NotImplementedError("This method should be implemented by subclasses.")
|
16
|
+
|
17
|
+
def set_setting(self, key: str, value: str) -> None:
|
18
|
+
"""Set the value of a setting by its key."""
|
19
|
+
raise NotImplementedError("This method should be implemented by subclasses.")
|
@@ -76,3 +76,26 @@ class Storage(Base):
|
|
76
76
|
cascade="save-update",
|
77
77
|
backref=backref("storages", passive_deletes=True),
|
78
78
|
)
|
79
|
+
|
80
|
+
|
81
|
+
class Settings(Base):
|
82
|
+
"""
|
83
|
+
SQLAlchemy model for storing application settings.
|
84
|
+
|
85
|
+
This model represents the settings table in the database,
|
86
|
+
storing key-value pairs for application configuration.
|
87
|
+
|
88
|
+
Attributes:
|
89
|
+
id (int): Primary key
|
90
|
+
key (str): Setting key
|
91
|
+
value (str): Setting value
|
92
|
+
created_at (datetime): Timestamp when the setting was created
|
93
|
+
updated_at (datetime): Timestamp when the setting was last updated
|
94
|
+
"""
|
95
|
+
|
96
|
+
__tablename__ = "settings"
|
97
|
+
id = Column(Integer, primary_key=True)
|
98
|
+
key = Column(String, unique=True, nullable=False)
|
99
|
+
value = Column(String, nullable=False)
|
100
|
+
created_at = Column(DateTime, default=datetime.utcnow)
|
101
|
+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
File without changes
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import platform
|
2
|
+
|
3
|
+
from packaging import version
|
4
|
+
|
5
|
+
from sourcerer import __version__, package_name
|
6
|
+
from sourcerer.domain.package_meta.entities import PackageMeta
|
7
|
+
from sourcerer.domain.package_meta.services import BasePackageMetaService
|
8
|
+
from sourcerer.utils import get_last_package_version
|
9
|
+
|
10
|
+
|
11
|
+
class PackageMetaService(BasePackageMetaService):
|
12
|
+
def get_package_meta(self) -> PackageMeta:
|
13
|
+
latest_version = get_last_package_version(package_name)
|
14
|
+
has_available_update = (
|
15
|
+
version.parse(latest_version) > version.parse(__version__)
|
16
|
+
if latest_version
|
17
|
+
else False
|
18
|
+
)
|
19
|
+
|
20
|
+
return PackageMeta(
|
21
|
+
version=__version__,
|
22
|
+
latest_version=latest_version,
|
23
|
+
has_available_update=has_available_update,
|
24
|
+
system_version=platform.release(),
|
25
|
+
platform=platform.system(),
|
26
|
+
)
|
File without changes
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from typing import get_type_hints
|
2
|
+
|
3
|
+
from sourcerer.domain.settings.entities import Settings
|
4
|
+
from sourcerer.domain.settings.repositories import BaseSettingsRepository
|
5
|
+
from sourcerer.infrastructure.db.models import Settings as DBSettings
|
6
|
+
|
7
|
+
|
8
|
+
class SQLAlchemySettingsRepository(BaseSettingsRepository):
|
9
|
+
def __init__(self, db):
|
10
|
+
"""
|
11
|
+
Initialize the repository with a database session factory.
|
12
|
+
|
13
|
+
Args:
|
14
|
+
db: Database session factory
|
15
|
+
"""
|
16
|
+
self.db = db
|
17
|
+
|
18
|
+
def get_settings(self) -> Settings:
|
19
|
+
"""Retrieve all settings as a Settings."""
|
20
|
+
|
21
|
+
with self.db() as session:
|
22
|
+
settings = session.query(DBSettings).all()
|
23
|
+
type_hints = get_type_hints(Settings)
|
24
|
+
return Settings(
|
25
|
+
**{
|
26
|
+
setting.key: self.parse_value(
|
27
|
+
setting.key, setting.value, type_hints.get(setting.key, str)
|
28
|
+
)
|
29
|
+
for setting in settings
|
30
|
+
}
|
31
|
+
)
|
32
|
+
|
33
|
+
def get_setting(self, key: str) -> str:
|
34
|
+
"""Retrieve a setting by its key."""
|
35
|
+
with self.db() as session:
|
36
|
+
setting = session.query(DBSettings).filter(DBSettings.key == key).first()
|
37
|
+
if setting is None:
|
38
|
+
raise KeyError(f"Setting '{key}' not found.")
|
39
|
+
return setting.value
|
40
|
+
|
41
|
+
def set_setting(self, key: str, value: str) -> None:
|
42
|
+
"""Set a setting with a given key and value."""
|
43
|
+
with self.db() as session:
|
44
|
+
setting = session.query(DBSettings).filter(DBSettings.key == key).first()
|
45
|
+
if setting is None:
|
46
|
+
setting = DBSettings(key=key, value=value)
|
47
|
+
session.add(setting)
|
48
|
+
else:
|
49
|
+
setting.value = value
|
50
|
+
session.commit()
|
51
|
+
|
52
|
+
def parse_value(self, key: str, value: str, expected_type):
|
53
|
+
if expected_type is bool:
|
54
|
+
return value.lower() in ("true", "1", "yes", "on")
|
55
|
+
if expected_type is int:
|
56
|
+
return int(value)
|
57
|
+
if expected_type is float:
|
58
|
+
return float(value)
|
59
|
+
return value # Assume string or leave as-is
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from sourcerer.domain.settings.entities import Settings
|
2
|
+
from sourcerer.domain.settings.services import BaseSettingsService
|
3
|
+
|
4
|
+
|
5
|
+
class SettingsService(BaseSettingsService):
|
6
|
+
def load_settings(self) -> Settings:
|
7
|
+
"""Load settings from the settings file."""
|
8
|
+
return self.repository.get_settings()
|
9
|
+
|
10
|
+
def get_setting(self, key: str) -> str:
|
11
|
+
"""Get the value of a setting by its key."""
|
12
|
+
return self.repository.get_setting(key)
|
13
|
+
|
14
|
+
def set_setting(self, key: str, value: str) -> None:
|
15
|
+
"""Set the value of a setting by its key."""
|
16
|
+
self.repository.set_setting(key, value)
|
@@ -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
|
@@ -119,7 +118,6 @@ class GCPStorageProviderService(BaseStorageProviderService):
|
|
119
118
|
ListStorageItemsError: If an error occurs while listing items
|
120
119
|
"""
|
121
120
|
try:
|
122
|
-
|
123
121
|
files = []
|
124
122
|
folders = []
|
125
123
|
if path and not path.endswith("/"):
|
@@ -136,7 +134,7 @@ class GCPStorageProviderService(BaseStorageProviderService):
|
|
136
134
|
File(
|
137
135
|
generate_uuid(),
|
138
136
|
blob.name[len(path) :],
|
139
|
-
size=
|
137
|
+
size=blob.size,
|
140
138
|
date_modified=blob.updated.date(),
|
141
139
|
is_text=is_text_file(blob.name),
|
142
140
|
)
|
@@ -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
|
)
|
@@ -16,6 +16,9 @@ from sourcerer.infrastructure.access_credentials.repositories import (
|
|
16
16
|
from sourcerer.infrastructure.access_credentials.services import CredentialsService
|
17
17
|
from sourcerer.infrastructure.db.config import Database
|
18
18
|
from sourcerer.infrastructure.file_system.services import FileSystemService
|
19
|
+
from sourcerer.infrastructure.package_meta.services import PackageMetaService
|
20
|
+
from sourcerer.infrastructure.settings.repositories import SQLAlchemySettingsRepository
|
21
|
+
from sourcerer.infrastructure.settings.services import SettingsService
|
19
22
|
from sourcerer.infrastructure.storage.repositories import SQLAlchemyStoragesRepository
|
20
23
|
from sourcerer.infrastructure.storage.services import StoragesService
|
21
24
|
from sourcerer.settings import APP_DIR, DB_NAME
|
@@ -50,6 +53,10 @@ class DiContainer(containers.DeclarativeContainer):
|
|
50
53
|
SQLAlchemyStoragesRepository, session_factory
|
51
54
|
)
|
52
55
|
|
56
|
+
settings_repository = providers.Factory(
|
57
|
+
SQLAlchemySettingsRepository, session_factory
|
58
|
+
)
|
59
|
+
|
53
60
|
credentials_service = providers.Factory(
|
54
61
|
CredentialsService, repository=credentials_repository
|
55
62
|
)
|
@@ -59,3 +66,9 @@ class DiContainer(containers.DeclarativeContainer):
|
|
59
66
|
)
|
60
67
|
|
61
68
|
file_system_service = providers.Factory(FileSystemService, Path.home())
|
69
|
+
|
70
|
+
package_meta_service = providers.Factory(PackageMetaService)
|
71
|
+
settings_service = providers.Factory(
|
72
|
+
SettingsService,
|
73
|
+
repository=settings_repository,
|
74
|
+
)
|
File without changes
|
@@ -0,0 +1,60 @@
|
|
1
|
+
from dependency_injector.wiring import Provide
|
2
|
+
from rich.text import Text
|
3
|
+
from textual import on
|
4
|
+
from textual.app import ComposeResult
|
5
|
+
from textual.containers import Container, Horizontal
|
6
|
+
from textual.widgets import Static
|
7
|
+
|
8
|
+
from sourcerer.domain.package_meta.services import BasePackageMetaService
|
9
|
+
from sourcerer.presentation.di_container import DiContainer
|
10
|
+
from sourcerer.presentation.screens.shared.modal_screens import ExitBoundModalScreen
|
11
|
+
from sourcerer.presentation.screens.shared.widgets.button import Button
|
12
|
+
|
13
|
+
|
14
|
+
class AboutScreen(ExitBoundModalScreen):
|
15
|
+
"""Screen with a parameter."""
|
16
|
+
|
17
|
+
CSS_PATH = "styles.tcss"
|
18
|
+
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
package_meta_service: BasePackageMetaService = Provide[
|
22
|
+
DiContainer.package_meta_service
|
23
|
+
],
|
24
|
+
) -> None:
|
25
|
+
super().__init__()
|
26
|
+
self.package_meta_service = package_meta_service
|
27
|
+
|
28
|
+
def compose(self) -> ComposeResult:
|
29
|
+
package_meta = self.package_meta_service.get_package_meta()
|
30
|
+
|
31
|
+
with Container():
|
32
|
+
yield Static(Text("Sourcerer", style="bold cyan", justify="center"))
|
33
|
+
yield Static(
|
34
|
+
Text(
|
35
|
+
f"Version: {package_meta.version}"
|
36
|
+
f"{' (newer version is available)' if package_meta.has_available_update else ''}",
|
37
|
+
style="dim",
|
38
|
+
justify="center",
|
39
|
+
)
|
40
|
+
)
|
41
|
+
yield Static(
|
42
|
+
Text(
|
43
|
+
f"Platform: {package_meta.platform}", style="dim", justify="center"
|
44
|
+
)
|
45
|
+
)
|
46
|
+
yield Static(
|
47
|
+
Text(
|
48
|
+
f"System Version: {package_meta.system_version}",
|
49
|
+
style="dim",
|
50
|
+
justify="center",
|
51
|
+
)
|
52
|
+
)
|
53
|
+
with Horizontal(id="controls"):
|
54
|
+
yield Button("Close", name="close")
|
55
|
+
|
56
|
+
@on(Button.Click)
|
57
|
+
def on_button_clicked(self, event: Button.Click) -> None:
|
58
|
+
"""Handle button clicked events."""
|
59
|
+
if event.action == "close":
|
60
|
+
self.action_cancel_screen()
|