restiny 0.2.1__py3-none-any.whl → 0.6.1__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 (38) hide show
  1. restiny/__about__.py +1 -1
  2. restiny/__main__.py +28 -14
  3. restiny/assets/style.tcss +56 -2
  4. restiny/consts.py +236 -0
  5. restiny/data/db.py +60 -0
  6. restiny/data/models.py +111 -0
  7. restiny/data/repos.py +455 -0
  8. restiny/data/sql/__init__.py +3 -0
  9. restiny/entities.py +438 -0
  10. restiny/enums.py +14 -5
  11. restiny/httpx_auths.py +52 -0
  12. restiny/ui/__init__.py +17 -0
  13. restiny/ui/app.py +586 -0
  14. restiny/ui/collections_area.py +594 -0
  15. restiny/ui/environments_screen.py +270 -0
  16. restiny/ui/request_area.py +602 -0
  17. restiny/{core → ui}/response_area.py +4 -1
  18. restiny/ui/settings_screen.py +73 -0
  19. restiny/ui/top_bar_area.py +60 -0
  20. restiny/{core → ui}/url_area.py +54 -38
  21. restiny/utils.py +52 -15
  22. restiny/widgets/__init__.py +15 -1
  23. restiny/widgets/collections_tree.py +74 -0
  24. restiny/widgets/confirm_prompt.py +76 -0
  25. restiny/widgets/custom_input.py +20 -0
  26. restiny/widgets/dynamic_fields.py +65 -70
  27. restiny/widgets/password_input.py +161 -0
  28. restiny/widgets/path_chooser.py +12 -12
  29. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/METADATA +7 -5
  30. restiny-0.6.1.dist-info/RECORD +38 -0
  31. restiny/core/__init__.py +0 -15
  32. restiny/core/app.py +0 -348
  33. restiny/core/request_area.py +0 -337
  34. restiny-0.2.1.dist-info/RECORD +0 -24
  35. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/WHEEL +0 -0
  36. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/entry_points.txt +0 -0
  37. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/licenses/LICENSE +0 -0
  38. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/top_level.txt +0 -0
restiny/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.2.1'
1
+ __version__ = '0.6.1'
restiny/__main__.py CHANGED
@@ -2,26 +2,40 @@ import sys
2
2
  from pathlib import Path
3
3
 
4
4
 
5
- def prepare_to_run_app() -> None:
6
- def adjust_python_path() -> None:
7
- """
8
- Hack to prepares the environment for running the app with `textual run --dev` command.
9
- """
10
- MODULE_PARENT_DIR = Path(__file__).parent.parent
11
- if str(MODULE_PARENT_DIR) not in sys.path:
12
- sys.path.append(str(MODULE_PARENT_DIR))
13
-
14
- adjust_python_path()
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))
15
12
 
16
13
 
17
14
  def run_app() -> None:
18
- from restiny.core.app import RESTinyApp
19
-
20
- RESTinyApp().run()
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()
21
35
 
22
36
 
23
37
  def main() -> None:
24
- prepare_to_run_app()
38
+ prepare_textual_dev_run()
25
39
  run_app()
26
40
 
27
41
 
restiny/assets/style.tcss CHANGED
@@ -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
+
restiny/consts.py CHANGED
@@ -1,9 +1,245 @@
1
1
  import sys
2
2
  from pathlib import Path
3
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
+
4
10
  if getattr(sys, 'frozen', False):
5
11
  # When running with pyinstaller
6
12
  MODULE_DIR = Path(sys._MEIPASS)
7
13
  else:
8
14
  # When running without pyinstaller
9
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
+ }
restiny/data/db.py ADDED
@@ -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
restiny/data/models.py ADDED
@@ -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
+ )