restiny 0.2.1__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.
restiny/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.2.1'
1
+ __version__ = '0.5.0'
restiny/__main__.py CHANGED
@@ -2,26 +2,38 @@ 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
+ FoldersSQLRepo,
19
+ RequestsSQLRepo,
20
+ SettingsSQLRepo,
21
+ )
22
+ from restiny.ui.app import RESTinyApp
23
+
24
+ CONF_DIR.mkdir(exist_ok=True)
25
+ DB_FILE.touch(exist_ok=True)
26
+ db_manager = DBManager(db_url=f'sqlite:///{DB_FILE}')
27
+ db_manager.run_migrations()
28
+ RESTinyApp(
29
+ folders_repo=FoldersSQLRepo(db_manager=db_manager),
30
+ requests_repo=RequestsSQLRepo(db_manager=db_manager),
31
+ settings_repo=SettingsSQLRepo(db_manager=db_manager),
32
+ ).run()
21
33
 
22
34
 
23
35
  def main() -> None:
24
- prepare_to_run_app()
36
+ prepare_textual_dev_run()
25
37
  run_app()
26
38
 
27
39
 
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,7 +48,23 @@ 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 {
@@ -51,6 +87,11 @@ Button {
51
87
  padding-bottom: 1;
52
88
  }
53
89
 
90
+ .px-1 {
91
+ padding-left: 1;
92
+ padding-right: 1;
93
+ }
94
+
54
95
  .m {
55
96
  margin: 0;
56
97
  }
@@ -71,3 +112,8 @@ Button {
71
112
  margin-bottom: 1;
72
113
  }
73
114
 
115
+ .mx-1 {
116
+ margin-left: 1;
117
+ margin-right: 1;
118
+ }
119
+
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,90 @@
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()
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()
42
+
43
+ method: Mapped[str] = mapped_column()
44
+ url: Mapped[str | None] = mapped_column()
45
+ headers: Mapped[str | None] = mapped_column()
46
+ params: Mapped[str | None] = mapped_column()
47
+
48
+ body_enabled: Mapped[bool] = mapped_column()
49
+ body_mode: Mapped[str] = mapped_column()
50
+ body: Mapped[str | None] = mapped_column()
51
+
52
+ auth_enabled: Mapped[bool] = mapped_column()
53
+ auth_mode: Mapped[str] = mapped_column()
54
+ auth: Mapped[str | None] = mapped_column()
55
+
56
+ option_timeout: Mapped[float | None] = mapped_column()
57
+ option_follow_redirects: Mapped[bool] = mapped_column()
58
+ option_verify_ssl: Mapped[bool] = mapped_column()
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
+ )