restiny 0.1.2__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. {restiny-0.1.2 → restiny-0.6.0}/PKG-INFO +9 -6
  2. {restiny-0.1.2 → restiny-0.6.0}/README.md +3 -2
  3. {restiny-0.1.2 → restiny-0.6.0}/pyproject.toml +7 -5
  4. restiny-0.6.0/restiny/__about__.py +1 -0
  5. restiny-0.6.0/restiny/__main__.py +43 -0
  6. {restiny-0.1.2 → restiny-0.6.0}/restiny/assets/style.tcss +56 -2
  7. restiny-0.6.0/restiny/consts.py +245 -0
  8. restiny-0.6.0/restiny/data/db.py +60 -0
  9. restiny-0.6.0/restiny/data/models.py +111 -0
  10. restiny-0.6.0/restiny/data/repos.py +455 -0
  11. restiny-0.6.0/restiny/data/sql/__init__.py +3 -0
  12. restiny-0.6.0/restiny/entities.py +438 -0
  13. {restiny-0.1.2 → restiny-0.6.0}/restiny/enums.py +14 -6
  14. restiny-0.6.0/restiny/httpx_auths.py +52 -0
  15. restiny-0.6.0/restiny/ui/__init__.py +17 -0
  16. restiny-0.6.0/restiny/ui/app.py +541 -0
  17. restiny-0.6.0/restiny/ui/collections_area.py +566 -0
  18. restiny-0.6.0/restiny/ui/environments_screen.py +270 -0
  19. restiny-0.6.0/restiny/ui/request_area.py +575 -0
  20. {restiny-0.1.2/restiny/core → restiny-0.6.0/restiny/ui}/response_area.py +53 -20
  21. restiny-0.6.0/restiny/ui/settings_screen.py +73 -0
  22. restiny-0.6.0/restiny/ui/top_bar_area.py +60 -0
  23. restiny-0.6.0/restiny/ui/url_area.py +129 -0
  24. {restiny-0.1.2 → restiny-0.6.0}/restiny/utils.py +52 -15
  25. restiny-0.6.0/restiny/widgets/__init__.py +30 -0
  26. restiny-0.6.0/restiny/widgets/collections_tree.py +70 -0
  27. restiny-0.6.0/restiny/widgets/confirm_prompt.py +76 -0
  28. restiny-0.6.0/restiny/widgets/custom_input.py +20 -0
  29. restiny-0.6.0/restiny/widgets/dynamic_fields.py +561 -0
  30. restiny-0.6.0/restiny/widgets/password_input.py +161 -0
  31. {restiny-0.1.2 → restiny-0.6.0}/restiny/widgets/path_chooser.py +75 -30
  32. {restiny-0.1.2 → restiny-0.6.0}/restiny.egg-info/PKG-INFO +9 -6
  33. restiny-0.6.0/restiny.egg-info/SOURCES.txt +41 -0
  34. restiny-0.6.0/restiny.egg-info/requires.txt +6 -0
  35. restiny-0.1.2/restiny/__about__.py +0 -1
  36. restiny-0.1.2/restiny/__main__.py +0 -29
  37. restiny-0.1.2/restiny/assets/__pycache__/__init__.cpython-310.pyc +0 -0
  38. restiny-0.1.2/restiny/assets/__pycache__/__init__.cpython-313.pyc +0 -0
  39. restiny-0.1.2/restiny/assets/__pycache__/__init__.cpython-314.pyc +0 -0
  40. restiny-0.1.2/restiny/consts.py +0 -9
  41. restiny-0.1.2/restiny/core/__init__.py +0 -15
  42. restiny-0.1.2/restiny/core/app.py +0 -341
  43. restiny-0.1.2/restiny/core/request_area.py +0 -335
  44. restiny-0.1.2/restiny/core/url_area.py +0 -109
  45. restiny-0.1.2/restiny/screens/__init__.py +0 -0
  46. restiny-0.1.2/restiny/screens/dialog.py +0 -109
  47. restiny-0.1.2/restiny/widgets/__init__.py +0 -16
  48. restiny-0.1.2/restiny/widgets/dynamic_fields.py +0 -287
  49. restiny-0.1.2/restiny.egg-info/SOURCES.txt +0 -32
  50. restiny-0.1.2/restiny.egg-info/requires.txt +0 -4
  51. {restiny-0.1.2 → restiny-0.6.0}/LICENSE +0 -0
  52. {restiny-0.1.2 → restiny-0.6.0}/restiny/__init__.py +0 -0
  53. {restiny-0.1.2 → restiny-0.6.0}/restiny/assets/__init__.py +0 -0
  54. {restiny-0.1.2 → restiny-0.6.0}/restiny/widgets/custom_directory_tree.py +0 -0
  55. {restiny-0.1.2 → restiny-0.6.0}/restiny/widgets/custom_text_area.py +0 -0
  56. {restiny-0.1.2 → restiny-0.6.0}/restiny.egg-info/dependency_links.txt +0 -0
  57. {restiny-0.1.2 → restiny-0.6.0}/restiny.egg-info/entry_points.txt +0 -0
  58. {restiny-0.1.2 → restiny-0.6.0}/restiny.egg-info/top_level.txt +0 -0
  59. {restiny-0.1.2 → restiny-0.6.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: restiny
3
- Version: 0.1.2
3
+ Version: 0.6.0
4
4
  Summary: A minimalist HTTP client, no bullshit
5
5
  Author-email: Kalebe Chimanski de Almeida <kalebe.chi.almeida@gmail.com>
6
6
  License: Apache License
@@ -216,10 +216,10 @@ Classifier: License :: OSI Approved :: Apache Software License
216
216
  Classifier: Environment :: Console
217
217
  Classifier: Typing :: Typed
218
218
  Classifier: Programming Language :: Python :: 3
219
- Classifier: Programming Language :: Python :: 3.10
220
219
  Classifier: Programming Language :: Python :: 3.11
221
220
  Classifier: Programming Language :: Python :: 3.12
222
221
  Classifier: Programming Language :: Python :: 3.13
222
+ Classifier: Programming Language :: Python :: 3.14
223
223
  Classifier: Operating System :: POSIX :: Linux
224
224
  Classifier: Operating System :: MacOS
225
225
  Classifier: Operating System :: Microsoft :: Windows :: Windows 10
@@ -227,17 +227,20 @@ Classifier: Operating System :: Microsoft :: Windows :: Windows 11
227
227
  Classifier: Topic :: Internet :: WWW/HTTP
228
228
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
229
229
  Classifier: Natural Language :: English
230
- Requires-Python: >=3.10
230
+ Requires-Python: >=3.11
231
231
  Description-Content-Type: text/markdown
232
232
  License-File: LICENSE
233
- Requires-Dist: textual<6.3,>=6.2
233
+ Requires-Dist: textual<6.4,>=6.3
234
234
  Requires-Dist: textual[syntax]
235
235
  Requires-Dist: httpx<0.29,>=0.28
236
236
  Requires-Dist: pyperclip<1.10,>=1.9
237
+ Requires-Dist: sqlalchemy<3.0,>=2.0
238
+ Requires-Dist: pydantic<2.13,>=2.12
237
239
  Dynamic: license-file
238
240
 
239
241
  ![OS support](https://img.shields.io/badge/OS-macOS%20Linux%20Windows-red)
240
- ![Python versions](https://img.shields.io/badge/Python-3.10%20|%203.11%20|%203.12%20|%203.13-blue)
242
+ ![Python versions](https://img.shields.io/badge/Python-3.11%20|%203.12%20|%203.13%20|%203.14-blue)
243
+
241
244
 
242
245
 
243
246
  - [RESTiny](#restiny)
@@ -253,7 +256,7 @@ Dynamic: license-file
253
256
 
254
257
  _A minimal, elegant HTTP client for Python — with a TUI interface powered by [Textual](https://github.com/Textualize/textual)._
255
258
 
256
- <img width="1905" alt="image" src="https://github.com/user-attachments/assets/e6f0c03a-e98e-40cd-af1d-38489d650fb1" />
259
+ ![image](https://github.com/user-attachments/assets/798c994f-af7e-4be6-8f8c-87157dcf94e0)
257
260
 
258
261
  ## How to install
259
262
 
@@ -1,5 +1,6 @@
1
1
  ![OS support](https://img.shields.io/badge/OS-macOS%20Linux%20Windows-red)
2
- ![Python versions](https://img.shields.io/badge/Python-3.10%20|%203.11%20|%203.12%20|%203.13-blue)
2
+ ![Python versions](https://img.shields.io/badge/Python-3.11%20|%203.12%20|%203.13%20|%203.14-blue)
3
+
3
4
 
4
5
 
5
6
  - [RESTiny](#restiny)
@@ -15,7 +16,7 @@
15
16
 
16
17
  _A minimal, elegant HTTP client for Python — with a TUI interface powered by [Textual](https://github.com/Textualize/textual)._
17
18
 
18
- <img width="1905" alt="image" src="https://github.com/user-attachments/assets/e6f0c03a-e98e-40cd-af1d-38489d650fb1" />
19
+ ![image](https://github.com/user-attachments/assets/798c994f-af7e-4be6-8f8c-87157dcf94e0)
19
20
 
20
21
  ## How to install
21
22
 
@@ -12,12 +12,14 @@ description = "A minimalist HTTP client, no bullshit"
12
12
  authors = [{ name = "Kalebe Chimanski de Almeida", email = "kalebe.chi.almeida@gmail.com" }]
13
13
  readme = "README.md"
14
14
  license = { file = "LICENSE" }
15
- requires-python = ">=3.10"
15
+ requires-python = ">=3.11"
16
16
  dependencies = [
17
- "textual>=6.2,<6.3",
17
+ "textual>=6.3,<6.4",
18
18
  "textual[syntax]",
19
19
  "httpx>=0.28,<0.29",
20
20
  "pyperclip>=1.9,<1.10",
21
+ "sqlalchemy>=2.0,<3.0",
22
+ "pydantic>=2.12,<2.13"
21
23
  ]
22
24
  keywords = ["restiny", "tui", "http", "client", "terminal", "api", "testing", "textual", "cli", "developer-tool"]
23
25
  classifiers = [
@@ -27,10 +29,10 @@ classifiers = [
27
29
  "Environment :: Console",
28
30
  "Typing :: Typed",
29
31
  "Programming Language :: Python :: 3",
30
- "Programming Language :: Python :: 3.10",
31
32
  "Programming Language :: Python :: 3.11",
32
33
  "Programming Language :: Python :: 3.12",
33
34
  "Programming Language :: Python :: 3.13",
35
+ "Programming Language :: Python :: 3.14",
34
36
  "Operating System :: POSIX :: Linux",
35
37
  "Operating System :: MacOS",
36
38
  "Operating System :: Microsoft :: Windows :: Windows 10",
@@ -63,11 +65,11 @@ restiny = ["assets/**/*"]
63
65
  ##########
64
66
  [tool.ruff]
65
67
  line-length = 79
66
- target-version = "py313"
68
+ target-version = "py314"
67
69
 
68
70
  [tool.ruff.lint]
69
71
  select = ["E", "F", "B", "I", "UP"]
70
- ignore = ["E501", "B006"]
72
+ ignore = ["E501", "B006", "UP037"]
71
73
 
72
74
  [tool.ruff.format]
73
75
  quote-style = "single"
@@ -0,0 +1 @@
1
+ __version__ = '0.6.0'
@@ -0,0 +1,43 @@
1
+ import sys
2
+ from pathlib import Path
3
+
4
+
5
+ def prepare_textual_dev_run() -> None:
6
+ """
7
+ Prepares the environment for running the app with `textual run --dev`
8
+ """
9
+ MODULE_PARENT_DIR = Path(__file__).parent.parent
10
+ if str(MODULE_PARENT_DIR) not in sys.path:
11
+ sys.path.append(str(MODULE_PARENT_DIR))
12
+
13
+
14
+ def run_app() -> None:
15
+ from restiny.consts import CONF_DIR, DB_FILE
16
+ from restiny.data.db import DBManager
17
+ from restiny.data.repos import (
18
+ EnvironmentsSQLRepo,
19
+ FoldersSQLRepo,
20
+ RequestsSQLRepo,
21
+ SettingsSQLRepo,
22
+ )
23
+ from restiny.ui.app import RESTinyApp
24
+
25
+ CONF_DIR.mkdir(exist_ok=True)
26
+ DB_FILE.touch(exist_ok=True)
27
+ db_manager = DBManager(db_url=f'sqlite:///{DB_FILE}')
28
+ db_manager.run_migrations()
29
+ RESTinyApp(
30
+ folders_repo=FoldersSQLRepo(db_manager=db_manager),
31
+ requests_repo=RequestsSQLRepo(db_manager=db_manager),
32
+ settings_repo=SettingsSQLRepo(db_manager=db_manager),
33
+ environments_repo=EnvironmentsSQLRepo(db_manager=db_manager),
34
+ ).run()
35
+
36
+
37
+ def main() -> None:
38
+ prepare_textual_dev_run()
39
+ run_app()
40
+
41
+
42
+ if __name__ == '__main__':
43
+ main()
@@ -3,6 +3,10 @@ Button {
3
3
  max-width: 100%;
4
4
  }
5
5
 
6
+ Input {
7
+ width: 1fr;
8
+ }
9
+
6
10
  .hidden {
7
11
  display: none;
8
12
  }
@@ -19,6 +23,22 @@ Button {
19
23
  width: 2fr;
20
24
  }
21
25
 
26
+ .w-3fr {
27
+ width: 3fr;
28
+ }
29
+
30
+ .w-4fr {
31
+ width: 4fr;
32
+ }
33
+
34
+ .w-5fr {
35
+ width: 5fr;
36
+ }
37
+
38
+ .w-6fr {
39
+ width: 6fr;
40
+ }
41
+
22
42
  .h-auto {
23
43
  height: auto;
24
44
  }
@@ -28,13 +48,33 @@ Button {
28
48
  }
29
49
 
30
50
  .h-2fr {
31
- width: 2fr;
51
+ height: 2fr;
52
+ }
53
+
54
+ .h-3fr {
55
+ height: 3fr;
56
+ }
57
+
58
+ .h-4fr {
59
+ height: 4fr;
60
+ }
61
+
62
+ .h-5fr {
63
+ height: 5fr;
64
+ }
65
+
66
+ .h-6fr {
67
+ height: 6fr;
32
68
  }
33
69
 
34
70
  .p-0 {
35
71
  padding: 0;
36
72
  }
37
73
 
74
+ .p-1 {
75
+ padding: 1;
76
+ }
77
+
38
78
  .pt-1 {
39
79
  padding-top: 1;
40
80
  }
@@ -51,10 +91,19 @@ Button {
51
91
  padding-bottom: 1;
52
92
  }
53
93
 
54
- .m {
94
+ .px-1 {
95
+ padding-left: 1;
96
+ padding-right: 1;
97
+ }
98
+
99
+ .m-0 {
55
100
  margin: 0;
56
101
  }
57
102
 
103
+ .m-1 {
104
+ margin: 1;
105
+ }
106
+
58
107
  .mt-1 {
59
108
  margin-top: 1;
60
109
  }
@@ -71,3 +120,8 @@ Button {
71
120
  margin-bottom: 1;
72
121
  }
73
122
 
123
+ .mx-1 {
124
+ margin-left: 1;
125
+ margin-right: 1;
126
+ }
127
+
@@ -0,0 +1,245 @@
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ from rich.style import Style
5
+ from textual._text_area_theme import TextAreaTheme
6
+ from textual.theme import Theme
7
+
8
+ from restiny.enums import CustomThemes
9
+
10
+ if getattr(sys, 'frozen', False):
11
+ # When running with pyinstaller
12
+ MODULE_DIR = Path(sys._MEIPASS)
13
+ else:
14
+ # When running without pyinstaller
15
+ MODULE_DIR = Path(__file__).resolve().parent
16
+
17
+ CONF_DIR = Path.home().joinpath('.restiny')
18
+ DB_FILE = CONF_DIR / 'restiny.sqlite3'
19
+
20
+ # Textual themes
21
+ CUSTOM_THEMES: dict[CustomThemes, dict[Theme | TextAreaTheme]] = {
22
+ CustomThemes.DARK: {
23
+ 'global': Theme(
24
+ name='dark',
25
+ primary='#0178D4',
26
+ secondary='#004578',
27
+ accent='#ffa62b',
28
+ warning='#ffa62b',
29
+ error='#ba3c5b',
30
+ success='#4EBF71',
31
+ foreground='#e0e0e0',
32
+ ),
33
+ 'text_area': TextAreaTheme(
34
+ name='dark',
35
+ base_style=Style(color='#CCCCCC', bgcolor='#1F1F1F'),
36
+ gutter_style=Style(color='#6E7681', bgcolor='#1F1F1F'),
37
+ cursor_style=Style(color='#1e1e1e', bgcolor='#f0f0f0'),
38
+ cursor_line_style=Style(bgcolor='#2b2b2b'),
39
+ bracket_matching_style=Style(bgcolor='#3a3a3a', bold=True),
40
+ cursor_line_gutter_style=Style(color='#CCCCCC', bgcolor='#2b2b2b'),
41
+ selection_style=Style(bgcolor='#264F78'),
42
+ syntax_styles={
43
+ 'string': Style(color='#ce9178'),
44
+ 'string.documentation': Style(color='#ce9178'),
45
+ 'comment': Style(color='#6A9955'),
46
+ 'heading.marker': Style(color='#6E7681'),
47
+ 'keyword': Style(color='#C586C0'),
48
+ 'operator': Style(color='#CCCCCC'),
49
+ 'conditional': Style(color='#569cd6'),
50
+ 'keyword.function': Style(color='#569cd6'),
51
+ 'keyword.return': Style(color='#569cd6'),
52
+ 'keyword.operator': Style(color='#569cd6'),
53
+ 'repeat': Style(color='#569cd6'),
54
+ 'exception': Style(color='#569cd6'),
55
+ 'include': Style(color='#569cd6'),
56
+ 'number': Style(color='#b5cea8'),
57
+ 'float': Style(color='#b5cea8'),
58
+ 'class': Style(color='#4EC9B0'),
59
+ 'type': Style(color='#EFCB43'),
60
+ 'type.class': Style(color='#4EC9B0'),
61
+ 'type.builtin': Style(color='#9CDCFE'),
62
+ 'function': Style(color='#DCDCAA'),
63
+ 'function.call': Style(color='#DCDCAA'),
64
+ 'method': Style(color='#4EC9B0'),
65
+ 'method.call': Style(color='#4EC9B0'),
66
+ 'constructor': Style(color='#4EC9B0'),
67
+ 'boolean': Style(color='#7DAF9C'),
68
+ 'constant.builtin': Style(color='#7DAF9C'),
69
+ 'json.null': Style(color='#7DAF9C'),
70
+ 'tag': Style(color='#EFCB43'),
71
+ 'yaml.field': Style(color='#569cd6', bold=True),
72
+ 'json.label': Style(color='#569cd6', bold=True),
73
+ 'toml.type': Style(color='#569cd6'),
74
+ 'toml.datetime': Style(color='#C586C0', italic=True),
75
+ 'css.property': Style(color='#569cd6'),
76
+ 'heading': Style(color='#569cd6', bold=True),
77
+ 'bold': Style(bold=True),
78
+ 'italic': Style(italic=True),
79
+ 'strikethrough': Style(strike=True),
80
+ 'link.uri': Style(color='#40A6FF', underline=True),
81
+ 'link.label': Style(color='#569cd6'),
82
+ 'list.marker': Style(color='#6E7681'),
83
+ 'inline_code': Style(color='#ce9178'),
84
+ 'info_string': Style(color='#ce9178', bold=True, italic=True),
85
+ 'punctuation.bracket': Style(color='#CCCCCC'),
86
+ 'punctuation.delimiter': Style(color='#CCCCCC'),
87
+ 'punctuation.special': Style(color='#CCCCCC'),
88
+ },
89
+ ),
90
+ },
91
+ CustomThemes.DRACULA: {
92
+ 'global': Theme(
93
+ name='dracula',
94
+ primary='#BD93F9',
95
+ secondary='#6272A4',
96
+ warning='#FFB86C',
97
+ error='#FF5555',
98
+ success='#50FA7B',
99
+ accent='#FF79C6',
100
+ background='#282A36',
101
+ surface='#2B2E3B',
102
+ panel='#313442',
103
+ foreground='#F8F8F2',
104
+ variables={
105
+ 'button-color-foreground': '#282A36',
106
+ },
107
+ ),
108
+ 'text_area': TextAreaTheme(
109
+ name='dracula',
110
+ base_style=Style(color='#f8f8f2', bgcolor='#1E1F35'),
111
+ gutter_style=Style(color='#6272a4'),
112
+ cursor_style=Style(color='#282a36', bgcolor='#f8f8f0'),
113
+ cursor_line_style=Style(bgcolor='#282b45'),
114
+ cursor_line_gutter_style=Style(
115
+ color='#c2c2bf', bgcolor='#282b45', bold=True
116
+ ),
117
+ bracket_matching_style=Style(
118
+ bgcolor='#99999d', bold=True, underline=True
119
+ ),
120
+ selection_style=Style(bgcolor='#44475A'),
121
+ syntax_styles={
122
+ 'string': Style(color='#f1fa8c'),
123
+ 'string.documentation': Style(color='#f1fa8c'),
124
+ 'comment': Style(color='#6272a4'),
125
+ 'heading.marker': Style(color='#6272a4'),
126
+ 'keyword': Style(color='#ff79c6'),
127
+ 'operator': Style(color='#f8f8f2'),
128
+ 'repeat': Style(color='#ff79c6'),
129
+ 'exception': Style(color='#ff79c6'),
130
+ 'include': Style(color='#ff79c6'),
131
+ 'keyword.function': Style(color='#ff79c6'),
132
+ 'keyword.return': Style(color='#ff79c6'),
133
+ 'keyword.operator': Style(color='#ff79c6'),
134
+ 'conditional': Style(color='#ff79c6'),
135
+ 'number': Style(color='#bd93f9'),
136
+ 'float': Style(color='#bd93f9'),
137
+ 'class': Style(color='#50fa7b'),
138
+ 'type': Style(color='#ff79c6'),
139
+ 'type.class': Style(color='#50fa7b'),
140
+ 'type.builtin': Style(color='#bd93f9'),
141
+ 'variable.builtin': Style(color='#f8f8f2'),
142
+ 'function': Style(color='#50fa7b'),
143
+ 'function.call': Style(color='#50fa7b'),
144
+ 'method': Style(color='#50fa7b'),
145
+ 'method.call': Style(color='#50fa7b'),
146
+ 'boolean': Style(color='#50fa7b'),
147
+ 'constant.builtin': Style(color='#bd93f9'),
148
+ 'json.null': Style(color='#bd93f9'),
149
+ 'regex.punctuation.bracket': Style(color='#ff79c6'),
150
+ 'regex.operator': Style(color='#ff79c6'),
151
+ 'html.end_tag_error': Style(color='#F83333', underline=True),
152
+ 'tag': Style(color='#ff79c6'),
153
+ 'yaml.field': Style(color='#ff79c6', bold=True),
154
+ 'json.label': Style(color='#ff79c6', bold=True),
155
+ 'toml.type': Style(color='#ff79c6'),
156
+ 'toml.datetime': Style(color='#bd93f9'),
157
+ 'css.property': Style(color='#bd93f9'),
158
+ 'heading': Style(color='#ff79c6', bold=True),
159
+ 'bold': Style(bold=True),
160
+ 'italic': Style(italic=True),
161
+ 'strikethrough': Style(strike=True),
162
+ 'link.label': Style(color='#ff79c6'),
163
+ 'link.uri': Style(color='#bd93f9', underline=True),
164
+ 'list.marker': Style(color='#6272a4'),
165
+ 'inline_code': Style(color='#f1fa8c'),
166
+ 'punctuation.bracket': Style(color='#f8f8f2'),
167
+ 'punctuation.delimiter': Style(color='#f8f8f2'),
168
+ 'punctuation.special': Style(color='#f8f8f2'),
169
+ },
170
+ ),
171
+ },
172
+ CustomThemes.FOREST: {
173
+ 'global': Theme(
174
+ name='forest',
175
+ primary='#58A55C',
176
+ secondary='#0E1A12',
177
+ accent='#8BC34A',
178
+ warning='#C89B3C',
179
+ error='#C0392B',
180
+ success='#4CAF50',
181
+ foreground='#E7F6E7',
182
+ background='#0B1510',
183
+ surface='#142019',
184
+ panel='#1B2A21',
185
+ ),
186
+ 'text_area': TextAreaTheme(
187
+ name='forest',
188
+ base_style=Style(color='#E7F6E7', bgcolor='#0B1510'),
189
+ gutter_style=Style(color='#8CA694', bgcolor='#142019'),
190
+ cursor_style=Style(color='#0B1510', bgcolor='#58A55C'),
191
+ cursor_line_style=Style(bgcolor='#1B2A21'),
192
+ cursor_line_gutter_style=Style(color='#B5D3C0', bgcolor='#1B2A21'),
193
+ bracket_matching_style=Style(bgcolor='#284232', bold=True),
194
+ selection_style=Style(bgcolor='#284232'),
195
+ syntax_styles={
196
+ 'string': Style(color='#BCEABF'),
197
+ 'string.documentation': Style(color='#BCEABF'),
198
+ 'comment': Style(color='#4A6B59', italic=True),
199
+ 'heading.marker': Style(color='#6C8F7E'),
200
+ 'keyword': Style(color='#8BC34A', bold=True),
201
+ 'operator': Style(color='#E7F6E7'),
202
+ 'conditional': Style(color='#8BC34A'),
203
+ 'keyword.function': Style(color='#8BC34A'),
204
+ 'keyword.return': Style(color='#8BC34A'),
205
+ 'keyword.operator': Style(color='#8BC34A'),
206
+ 'repeat': Style(color='#8BC34A'),
207
+ 'exception': Style(color='#C0392B'),
208
+ 'include': Style(color='#8BC34A'),
209
+ 'number': Style(color='#A4E28F'),
210
+ 'float': Style(color='#A4E28F'),
211
+ 'class': Style(color='#4CAF50'),
212
+ 'type': Style(color='#4CAF50'),
213
+ 'type.class': Style(color='#4CAF50'),
214
+ 'type.builtin': Style(color='#8BC34A'),
215
+ 'variable.builtin': Style(color='#E7F6E7'),
216
+ 'function': Style(color='#58A55C'),
217
+ 'function.call': Style(color='#58A55C'),
218
+ 'method': Style(color='#58A55C'),
219
+ 'method.call': Style(color='#58A55C'),
220
+ 'boolean': Style(color='#9FE6A0'),
221
+ 'constant.builtin': Style(color='#9FE6A0'),
222
+ 'json.null': Style(color='#9FE6A0'),
223
+ 'regex.punctuation.bracket': Style(color='#8BC34A'),
224
+ 'regex.operator': Style(color='#8BC34A'),
225
+ 'tag': Style(color='#8BC34A'),
226
+ 'yaml.field': Style(color='#8BC34A', bold=True),
227
+ 'json.label': Style(color='#8BC34A', bold=True),
228
+ 'toml.type': Style(color='#8BC34A'),
229
+ 'toml.datetime': Style(color='#A4E28F'),
230
+ 'css.property': Style(color='#A4E28F'),
231
+ 'heading': Style(color='#8BC34A', bold=True),
232
+ 'bold': Style(bold=True),
233
+ 'italic': Style(italic=True),
234
+ 'strikethrough': Style(strike=True),
235
+ 'link.label': Style(color='#8BC34A'),
236
+ 'link.uri': Style(color='#A4E28F', underline=True),
237
+ 'list.marker': Style(color='#6C8F7E'),
238
+ 'inline_code': Style(color='#BCEABF'),
239
+ 'punctuation.bracket': Style(color='#E7F6E7'),
240
+ 'punctuation.delimiter': Style(color='#E7F6E7'),
241
+ 'punctuation.special': Style(color='#E7F6E7'),
242
+ },
243
+ ),
244
+ },
245
+ }
@@ -0,0 +1,60 @@
1
+ from contextlib import contextmanager
2
+
3
+ from sqlalchemy import create_engine, event, text
4
+ from sqlalchemy.orm import sessionmaker
5
+
6
+ from restiny.data.sql import SQL_DIR
7
+
8
+
9
+ class DBManager:
10
+ def __init__(self, db_url: str) -> None:
11
+ self.db_url = db_url
12
+ self.engine = create_engine(self.db_url, echo=False)
13
+ self.SessionMaker = sessionmaker(
14
+ bind=self.engine, autoflush=True, expire_on_commit=False
15
+ )
16
+
17
+ @event.listens_for(self.engine, 'connect')
18
+ def _set_sqlite_pragmas(dbapi_connection, connection_record):
19
+ cursor = dbapi_connection.cursor()
20
+ cursor.execute('PRAGMA foreign_keys=ON')
21
+ cursor.close()
22
+
23
+ @contextmanager
24
+ def session_scope(self):
25
+ session = self.SessionMaker()
26
+ try:
27
+ yield session
28
+ session.commit()
29
+ except Exception:
30
+ session.rollback()
31
+ raise
32
+ finally:
33
+ session.close()
34
+
35
+ def run_migrations(self) -> None:
36
+ version_in_db = self._get_version()
37
+
38
+ for sql_script in sorted(
39
+ SQL_DIR.glob('[0-9]*_*.sql'),
40
+ key=lambda path: int(path.stem.split('_', 1)[0]),
41
+ ):
42
+ version_in_script = int(sql_script.stem.split('_', 1)[0])
43
+ if version_in_script <= version_in_db:
44
+ continue
45
+
46
+ self._run_migration(
47
+ sql_migration=sql_script.read_text(encoding='utf-8'),
48
+ new_version=version_in_script,
49
+ )
50
+
51
+ def _run_migration(self, sql_migration: str, new_version: int) -> None:
52
+ with self.session_scope() as session:
53
+ raw = session.connection().connection
54
+ raw.executescript(sql_migration)
55
+ raw.execute(f'PRAGMA user_version = {new_version}')
56
+
57
+ def _get_version(self) -> int:
58
+ with self.session_scope() as session:
59
+ result = session.execute(text('PRAGMA user_version'))
60
+ return int(result.scalar_one()) or 0
@@ -0,0 +1,111 @@
1
+ from datetime import datetime
2
+
3
+ from sqlalchemy import DateTime, ForeignKey, func
4
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
5
+
6
+
7
+ class SQLModelBase(DeclarativeBase):
8
+ pass
9
+
10
+
11
+ class SQLFolder(SQLModelBase):
12
+ __tablename__ = 'folders'
13
+
14
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
15
+ name: Mapped[str] = mapped_column(nullable=False)
16
+ parent_id: Mapped[int | None] = mapped_column(
17
+ ForeignKey('folders.id'), nullable=True
18
+ )
19
+
20
+ created_at: Mapped[datetime] = mapped_column(
21
+ DateTime(),
22
+ default=func.current_timestamp(),
23
+ nullable=False,
24
+ )
25
+
26
+ updated_at: Mapped[datetime] = mapped_column(
27
+ DateTime(),
28
+ default=func.current_timestamp(),
29
+ onupdate=func.current_timestamp(),
30
+ nullable=False,
31
+ )
32
+
33
+
34
+ class SQLRequest(SQLModelBase):
35
+ __tablename__ = 'requests'
36
+
37
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
38
+ folder_id: Mapped[int] = mapped_column(
39
+ ForeignKey('folders.id'), nullable=False
40
+ )
41
+ name: Mapped[str] = mapped_column(nullable=False)
42
+
43
+ method: Mapped[str] = mapped_column(nullable=False)
44
+ url: Mapped[str | None] = mapped_column(nullable=True)
45
+ headers: Mapped[str | None] = mapped_column(nullable=True)
46
+ params: Mapped[str | None] = mapped_column(nullable=True)
47
+
48
+ body_enabled: Mapped[bool] = mapped_column(nullable=False)
49
+ body_mode: Mapped[str] = mapped_column(nullable=False)
50
+ body: Mapped[str | None] = mapped_column(nullable=True)
51
+
52
+ auth_enabled: Mapped[bool] = mapped_column(nullable=False)
53
+ auth_mode: Mapped[str] = mapped_column(nullable=False)
54
+ auth: Mapped[str | None] = mapped_column(nullable=True)
55
+
56
+ option_timeout: Mapped[float | None] = mapped_column(nullable=True)
57
+ option_follow_redirects: Mapped[bool] = mapped_column(nullable=False)
58
+ option_verify_ssl: Mapped[bool] = mapped_column(nullable=False)
59
+
60
+ created_at: Mapped[datetime] = mapped_column(
61
+ DateTime(),
62
+ default=func.current_timestamp(),
63
+ nullable=False,
64
+ )
65
+ updated_at: Mapped[datetime] = mapped_column(
66
+ DateTime(),
67
+ default=func.current_timestamp(),
68
+ onupdate=func.current_timestamp(),
69
+ nullable=False,
70
+ )
71
+
72
+
73
+ class SQLSettings(SQLModelBase):
74
+ __tablename__ = 'settings'
75
+
76
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
77
+
78
+ theme: Mapped[str] = mapped_column(nullable=False)
79
+
80
+ created_at: Mapped[datetime] = mapped_column(
81
+ DateTime(),
82
+ default=func.current_timestamp(),
83
+ nullable=False,
84
+ )
85
+ updated_at: Mapped[datetime] = mapped_column(
86
+ DateTime(),
87
+ default=func.current_timestamp(),
88
+ onupdate=func.current_timestamp(),
89
+ nullable=False,
90
+ )
91
+
92
+
93
+ class SQLEnvironment(SQLModelBase):
94
+ __tablename__ = 'environments'
95
+
96
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
97
+
98
+ name: Mapped[str] = mapped_column(nullable=False, unique=True)
99
+ variables: Mapped[str] = mapped_column(nullable=False)
100
+
101
+ created_at: Mapped[datetime] = mapped_column(
102
+ DateTime(),
103
+ default=func.current_timestamp(),
104
+ nullable=False,
105
+ )
106
+ updated_at: Mapped[datetime] = mapped_column(
107
+ DateTime(),
108
+ default=func.current_timestamp(),
109
+ onupdate=func.current_timestamp(),
110
+ nullable=False,
111
+ )