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 +1 -1
- restiny/__main__.py +26 -14
- restiny/assets/style.tcss +47 -1
- restiny/consts.py +236 -0
- restiny/data/db.py +60 -0
- restiny/data/models.py +90 -0
- restiny/data/repos.py +351 -0
- restiny/data/sql/__init__.py +3 -0
- restiny/entities.py +320 -0
- restiny/enums.py +14 -5
- restiny/httpx_auths.py +52 -0
- restiny/ui/__init__.py +15 -0
- restiny/ui/app.py +500 -0
- restiny/ui/collections_area.py +569 -0
- restiny/ui/request_area.py +575 -0
- restiny/ui/settings_screen.py +80 -0
- restiny/{core → ui}/url_area.py +50 -38
- restiny/utils.py +52 -15
- restiny/widgets/__init__.py +15 -1
- restiny/widgets/collections_tree.py +70 -0
- restiny/widgets/confirm_prompt.py +76 -0
- restiny/widgets/custom_input.py +20 -0
- restiny/widgets/dynamic_fields.py +65 -70
- restiny/widgets/password_input.py +161 -0
- restiny/widgets/path_chooser.py +12 -12
- {restiny-0.2.1.dist-info → restiny-0.5.0.dist-info}/METADATA +7 -5
- restiny-0.5.0.dist-info/RECORD +36 -0
- restiny/core/__init__.py +0 -15
- restiny/core/app.py +0 -348
- restiny/core/request_area.py +0 -337
- restiny-0.2.1.dist-info/RECORD +0 -24
- /restiny/{core → ui}/response_area.py +0 -0
- {restiny-0.2.1.dist-info → restiny-0.5.0.dist-info}/WHEEL +0 -0
- {restiny-0.2.1.dist-info → restiny-0.5.0.dist-info}/entry_points.txt +0 -0
- {restiny-0.2.1.dist-info → restiny-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {restiny-0.2.1.dist-info → restiny-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from textual import on
|
|
4
|
+
from textual.app import ComposeResult
|
|
5
|
+
from textual.containers import Container, Horizontal
|
|
6
|
+
from textual.widgets import (
|
|
7
|
+
ContentSwitcher,
|
|
8
|
+
Label,
|
|
9
|
+
Select,
|
|
10
|
+
Static,
|
|
11
|
+
Switch,
|
|
12
|
+
TabbedContent,
|
|
13
|
+
TabPane,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from restiny.enums import AuthMode, BodyMode, BodyRawLanguage
|
|
17
|
+
from restiny.widgets import (
|
|
18
|
+
CustomInput,
|
|
19
|
+
CustomTextArea,
|
|
20
|
+
DynamicFields,
|
|
21
|
+
PasswordInput,
|
|
22
|
+
PathChooser,
|
|
23
|
+
TextDynamicField,
|
|
24
|
+
TextOrFileDynamicField,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RequestArea(Static):
|
|
29
|
+
ALLOW_MAXIMIZE = True
|
|
30
|
+
focusable = True
|
|
31
|
+
BORDER_TITLE = 'Request'
|
|
32
|
+
DEFAULT_CSS = """
|
|
33
|
+
RequestArea {
|
|
34
|
+
width: 1fr;
|
|
35
|
+
height: 1fr;
|
|
36
|
+
border: heavy black;
|
|
37
|
+
border-title-color: gray;
|
|
38
|
+
padding: 1;
|
|
39
|
+
}
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def compose(self) -> ComposeResult:
|
|
43
|
+
with TabbedContent():
|
|
44
|
+
with TabPane('Headers'):
|
|
45
|
+
yield DynamicFields(
|
|
46
|
+
fields=[TextDynamicField(enabled=False, key='', value='')],
|
|
47
|
+
id='headers',
|
|
48
|
+
)
|
|
49
|
+
with TabPane('Params'):
|
|
50
|
+
yield DynamicFields(
|
|
51
|
+
fields=[TextDynamicField(enabled=False, key='', value='')],
|
|
52
|
+
id='params',
|
|
53
|
+
)
|
|
54
|
+
with TabPane('Auth'):
|
|
55
|
+
with Horizontal(classes='h-auto'):
|
|
56
|
+
yield Switch(tooltip='Enabled', id='auth-enabled')
|
|
57
|
+
yield Select(
|
|
58
|
+
(
|
|
59
|
+
('Basic', AuthMode.BASIC),
|
|
60
|
+
('Bearer', AuthMode.BEARER),
|
|
61
|
+
('API Key', AuthMode.API_KEY),
|
|
62
|
+
('Digest', AuthMode.DIGEST),
|
|
63
|
+
),
|
|
64
|
+
allow_blank=False,
|
|
65
|
+
tooltip='Auth mode',
|
|
66
|
+
id='auth-mode',
|
|
67
|
+
)
|
|
68
|
+
with ContentSwitcher(
|
|
69
|
+
initial='auth-basic', id='auth-mode-switcher'
|
|
70
|
+
):
|
|
71
|
+
with Horizontal(id='auth-basic', classes='mt-1'):
|
|
72
|
+
yield CustomInput(
|
|
73
|
+
placeholder='Username',
|
|
74
|
+
select_on_focus=False,
|
|
75
|
+
classes='w-1fr',
|
|
76
|
+
id='auth-basic-username',
|
|
77
|
+
)
|
|
78
|
+
yield PasswordInput(
|
|
79
|
+
placeholder='Password',
|
|
80
|
+
select_on_focus=False,
|
|
81
|
+
classes='w-2fr',
|
|
82
|
+
id='auth-basic-password',
|
|
83
|
+
)
|
|
84
|
+
with Horizontal(id='auth-bearer', classes='mt-1'):
|
|
85
|
+
yield PasswordInput(
|
|
86
|
+
placeholder='Token',
|
|
87
|
+
select_on_focus=False,
|
|
88
|
+
id='auth-bearer-token',
|
|
89
|
+
)
|
|
90
|
+
with Horizontal(id='auth-api-key', classes='mt-1'):
|
|
91
|
+
yield Select(
|
|
92
|
+
(('Header', 'header'), ('Param', 'param')),
|
|
93
|
+
allow_blank=False,
|
|
94
|
+
tooltip='Where',
|
|
95
|
+
classes='w-1fr',
|
|
96
|
+
id='auth-api-key-where',
|
|
97
|
+
)
|
|
98
|
+
yield CustomInput(
|
|
99
|
+
placeholder='Key',
|
|
100
|
+
classes='w-2fr',
|
|
101
|
+
id='auth-api-key-key',
|
|
102
|
+
)
|
|
103
|
+
yield PasswordInput(
|
|
104
|
+
placeholder='Value',
|
|
105
|
+
classes='w-3fr',
|
|
106
|
+
id='auth-api-key-value',
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
with Horizontal(id='auth-digest', classes='mt-1'):
|
|
110
|
+
yield CustomInput(
|
|
111
|
+
placeholder='Username',
|
|
112
|
+
select_on_focus=False,
|
|
113
|
+
classes='w-1fr',
|
|
114
|
+
id='auth-digest-username',
|
|
115
|
+
)
|
|
116
|
+
yield PasswordInput(
|
|
117
|
+
placeholder='Password',
|
|
118
|
+
select_on_focus=False,
|
|
119
|
+
classes='w-2fr',
|
|
120
|
+
id='auth-digest-password',
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
with TabPane('Body'):
|
|
124
|
+
with Horizontal(classes='h-auto'):
|
|
125
|
+
yield Switch(id='body-enabled', tooltip='Send body?')
|
|
126
|
+
yield Select(
|
|
127
|
+
(
|
|
128
|
+
('Raw', BodyMode.RAW),
|
|
129
|
+
('File', BodyMode.FILE),
|
|
130
|
+
('Form (urlencoded)', BodyMode.FORM_URLENCODED),
|
|
131
|
+
('Form (multipart)', BodyMode.FORM_MULTIPART),
|
|
132
|
+
),
|
|
133
|
+
allow_blank=False,
|
|
134
|
+
tooltip='Body mode',
|
|
135
|
+
id='body-mode',
|
|
136
|
+
)
|
|
137
|
+
with ContentSwitcher(
|
|
138
|
+
id='body-mode-switcher',
|
|
139
|
+
initial='body-mode-raw',
|
|
140
|
+
classes='h-1fr',
|
|
141
|
+
):
|
|
142
|
+
with Container(id='body-mode-raw', classes='pt-1'):
|
|
143
|
+
yield Select(
|
|
144
|
+
(
|
|
145
|
+
('Plain', BodyRawLanguage.PLAIN),
|
|
146
|
+
('JSON', BodyRawLanguage.JSON),
|
|
147
|
+
('YAML', BodyRawLanguage.YAML),
|
|
148
|
+
('XML', BodyRawLanguage.XML),
|
|
149
|
+
('HTML', BodyRawLanguage.HTML),
|
|
150
|
+
),
|
|
151
|
+
allow_blank=False,
|
|
152
|
+
tooltip='Text type',
|
|
153
|
+
id='body-raw-language',
|
|
154
|
+
)
|
|
155
|
+
yield CustomTextArea.code_editor(
|
|
156
|
+
language='json', id='body-raw', classes='mt-1'
|
|
157
|
+
)
|
|
158
|
+
with Horizontal(
|
|
159
|
+
id='body-mode-file', classes='h-auto mt-1'
|
|
160
|
+
):
|
|
161
|
+
yield PathChooser.file(id='body-file')
|
|
162
|
+
with Horizontal(
|
|
163
|
+
id='body-mode-form-urlencoded', classes='h-auto mt-1'
|
|
164
|
+
):
|
|
165
|
+
yield DynamicFields(
|
|
166
|
+
[
|
|
167
|
+
TextDynamicField(
|
|
168
|
+
enabled=False, key='', value=''
|
|
169
|
+
)
|
|
170
|
+
],
|
|
171
|
+
id='body-form-urlencoded',
|
|
172
|
+
)
|
|
173
|
+
with Horizontal(
|
|
174
|
+
id='body-mode-form-multipart', classes='h-auto mt-1'
|
|
175
|
+
):
|
|
176
|
+
yield DynamicFields(
|
|
177
|
+
[
|
|
178
|
+
TextOrFileDynamicField(
|
|
179
|
+
enabled=False, key='', value=''
|
|
180
|
+
)
|
|
181
|
+
],
|
|
182
|
+
id='body-form-multipart',
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
with TabPane('Options'):
|
|
186
|
+
with Horizontal(classes='h-auto'):
|
|
187
|
+
yield Label('Timeout', classes='pt-1 ml-1')
|
|
188
|
+
yield CustomInput(
|
|
189
|
+
'5.5',
|
|
190
|
+
placeholder='5.5',
|
|
191
|
+
select_on_focus=False,
|
|
192
|
+
type='number',
|
|
193
|
+
valid_empty=True,
|
|
194
|
+
classes='w-1fr',
|
|
195
|
+
id='options-timeout',
|
|
196
|
+
)
|
|
197
|
+
with Horizontal(classes='mt-1 h-auto'):
|
|
198
|
+
yield Switch(id='options-follow-redirects')
|
|
199
|
+
yield Label('Follow redirects', classes='pt-1')
|
|
200
|
+
with Horizontal(classes='h-auto'):
|
|
201
|
+
yield Switch(id='options-verify-ssl')
|
|
202
|
+
yield Label('Verify SSL', classes='pt-1')
|
|
203
|
+
|
|
204
|
+
def on_mount(self) -> None:
|
|
205
|
+
self.header_fields = self.query_one('#headers', DynamicFields)
|
|
206
|
+
|
|
207
|
+
self.param_fields = self.query_one('#params', DynamicFields)
|
|
208
|
+
|
|
209
|
+
self.auth_enabled_switch = self.query_one('#auth-enabled', Switch)
|
|
210
|
+
self.auth_mode_switcher = self.query_one(
|
|
211
|
+
'#auth-mode-switcher', ContentSwitcher
|
|
212
|
+
)
|
|
213
|
+
self.auth_mode_select = self.query_one('#auth-mode', Select)
|
|
214
|
+
self.auth_basic_username_input = self.query_one(
|
|
215
|
+
'#auth-basic-username', CustomInput
|
|
216
|
+
)
|
|
217
|
+
self.auth_basic_password_input = self.query_one(
|
|
218
|
+
'#auth-basic-password', PasswordInput
|
|
219
|
+
)
|
|
220
|
+
self.auth_bearer_token_input = self.query_one(
|
|
221
|
+
'#auth-bearer-token', PasswordInput
|
|
222
|
+
)
|
|
223
|
+
self.auth_api_key_key_input = self.query_one(
|
|
224
|
+
'#auth-api-key-key', CustomInput
|
|
225
|
+
)
|
|
226
|
+
self.auth_api_key_value_input = self.query_one(
|
|
227
|
+
'#auth-api-key-value', PasswordInput
|
|
228
|
+
)
|
|
229
|
+
self.auth_api_key_where_select = self.query_one(
|
|
230
|
+
'#auth-api-key-where', Select
|
|
231
|
+
)
|
|
232
|
+
self.auth_digest_username_input = self.query_one(
|
|
233
|
+
'#auth-digest-username', CustomInput
|
|
234
|
+
)
|
|
235
|
+
self.auth_digest_password_input = self.query_one(
|
|
236
|
+
'#auth-digest-password', PasswordInput
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
self.body_enabled_switch = self.query_one('#body-enabled', Switch)
|
|
240
|
+
self.body_mode_select = self.query_one('#body-mode', Select)
|
|
241
|
+
self.body_mode_switcher = self.query_one(
|
|
242
|
+
'#body-mode-switcher', ContentSwitcher
|
|
243
|
+
)
|
|
244
|
+
self.body_raw_editor = self.query_one('#body-raw', CustomTextArea)
|
|
245
|
+
self.body_raw_language_select = self.query_one(
|
|
246
|
+
'#body-raw-language', Select
|
|
247
|
+
)
|
|
248
|
+
self.body_file_path_chooser = self.query_one('#body-file', PathChooser)
|
|
249
|
+
self.body_form_urlencoded_fields = self.query_one(
|
|
250
|
+
'#body-form-urlencoded', DynamicFields
|
|
251
|
+
)
|
|
252
|
+
self.body_form_multipart_fields = self.query_one(
|
|
253
|
+
'#body-form-multipart', DynamicFields
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
self.options_timeout_input = self.query_one(
|
|
257
|
+
'#options-timeout', CustomInput
|
|
258
|
+
)
|
|
259
|
+
self.options_follow_redirects_switch = self.query_one(
|
|
260
|
+
'#options-follow-redirects', Switch
|
|
261
|
+
)
|
|
262
|
+
self.options_verify_ssl_switch = self.query_one(
|
|
263
|
+
'#options-verify-ssl', Switch
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def headers(self) -> list[dict[str, str | bool]]:
|
|
268
|
+
return [
|
|
269
|
+
{
|
|
270
|
+
'enabled': header.enabled,
|
|
271
|
+
'key': header.key,
|
|
272
|
+
'value': header.value,
|
|
273
|
+
}
|
|
274
|
+
for header in self.header_fields.fields
|
|
275
|
+
if header.is_filled or header.enabled
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
@headers.setter
|
|
279
|
+
def headers(self, headers: list[dict[str, str | bool]]) -> None:
|
|
280
|
+
for field in self.header_fields.fields:
|
|
281
|
+
self.header_fields.remove_field(field=field)
|
|
282
|
+
|
|
283
|
+
for header in headers:
|
|
284
|
+
self.run_worker(
|
|
285
|
+
self.header_fields.add_field(
|
|
286
|
+
field=TextDynamicField(
|
|
287
|
+
enabled=header['enabled'],
|
|
288
|
+
key=header['key'],
|
|
289
|
+
value=header['value'],
|
|
290
|
+
),
|
|
291
|
+
before_last=True,
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def params(self) -> list[dict[str, str | bool]]:
|
|
297
|
+
return [
|
|
298
|
+
{
|
|
299
|
+
'enabled': param.enabled,
|
|
300
|
+
'key': param.key,
|
|
301
|
+
'value': param.value,
|
|
302
|
+
}
|
|
303
|
+
for param in self.param_fields.fields
|
|
304
|
+
if param.is_filled or param.enabled
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
@params.setter
|
|
308
|
+
def params(self, params: list[dict[str, str | bool]]) -> None:
|
|
309
|
+
for field in self.param_fields.fields:
|
|
310
|
+
self.param_fields.remove_field(field=field)
|
|
311
|
+
|
|
312
|
+
for param in params:
|
|
313
|
+
self.run_worker(
|
|
314
|
+
self.param_fields.add_field(
|
|
315
|
+
field=TextDynamicField(
|
|
316
|
+
enabled=param['enabled'],
|
|
317
|
+
key=param['key'],
|
|
318
|
+
value=param['value'],
|
|
319
|
+
),
|
|
320
|
+
before_last=True,
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def auth_enabled(self) -> bool:
|
|
326
|
+
return self.auth_enabled_switch.value
|
|
327
|
+
|
|
328
|
+
@auth_enabled.setter
|
|
329
|
+
def auth_enabled(self, value: bool) -> None:
|
|
330
|
+
self.auth_enabled_switch.value = value
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def auth_mode(self) -> AuthMode:
|
|
334
|
+
return self.auth_mode_select.value
|
|
335
|
+
|
|
336
|
+
@auth_mode.setter
|
|
337
|
+
def auth_mode(self, value: AuthMode) -> None:
|
|
338
|
+
self.auth_mode_select.value = value
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def auth_basic_username(self) -> str:
|
|
342
|
+
return self.auth_basic_username_input.value
|
|
343
|
+
|
|
344
|
+
@auth_basic_username.setter
|
|
345
|
+
def auth_basic_username(self, value: str) -> None:
|
|
346
|
+
self.auth_basic_username_input.value = value
|
|
347
|
+
|
|
348
|
+
@property
|
|
349
|
+
def auth_basic_password(self) -> str:
|
|
350
|
+
return self.auth_basic_password_input.value
|
|
351
|
+
|
|
352
|
+
@auth_basic_password.setter
|
|
353
|
+
def auth_basic_password(self, value: str) -> None:
|
|
354
|
+
self.auth_basic_password_input.value = value
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def auth_bearer_token(self) -> str:
|
|
358
|
+
return self.auth_bearer_token_input.value
|
|
359
|
+
|
|
360
|
+
@auth_bearer_token.setter
|
|
361
|
+
def auth_bearer_token(self, value: str) -> None:
|
|
362
|
+
self.auth_bearer_token_input.value = value
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def auth_api_key_key(self) -> str:
|
|
366
|
+
return self.auth_api_key_key_input.value
|
|
367
|
+
|
|
368
|
+
@auth_api_key_key.setter
|
|
369
|
+
def auth_api_key_key(self, value: str) -> None:
|
|
370
|
+
self.auth_api_key_key_input.value = value
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def auth_api_key_value(self) -> str:
|
|
374
|
+
return self.auth_api_key_value_input.value
|
|
375
|
+
|
|
376
|
+
@auth_api_key_value.setter
|
|
377
|
+
def auth_api_key_value(self, value: str) -> None:
|
|
378
|
+
self.auth_api_key_value_input.value = value
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def auth_api_key_where(self) -> str:
|
|
382
|
+
return self.auth_api_key_where_select.value
|
|
383
|
+
|
|
384
|
+
@auth_api_key_where.setter
|
|
385
|
+
def auth_api_key_where(self, value: str) -> None:
|
|
386
|
+
self.auth_api_key_where_select.value = value
|
|
387
|
+
|
|
388
|
+
@property
|
|
389
|
+
def auth_digest_username(self) -> str:
|
|
390
|
+
return self.auth_digest_username_input.value
|
|
391
|
+
|
|
392
|
+
@auth_digest_username.setter
|
|
393
|
+
def auth_digest_username(self, value: str) -> None:
|
|
394
|
+
self.auth_digest_username_input.value = value
|
|
395
|
+
|
|
396
|
+
@property
|
|
397
|
+
def auth_digest_password(self) -> str:
|
|
398
|
+
return self.auth_digest_password_input.value
|
|
399
|
+
|
|
400
|
+
@auth_digest_password.setter
|
|
401
|
+
def auth_digest_password(self, value: str) -> None:
|
|
402
|
+
self.auth_digest_password_input.value = value
|
|
403
|
+
|
|
404
|
+
@property
|
|
405
|
+
def body_enabled(self) -> bool:
|
|
406
|
+
return self.body_enabled_switch.value
|
|
407
|
+
|
|
408
|
+
@body_enabled.setter
|
|
409
|
+
def body_enabled(self, value: bool) -> None:
|
|
410
|
+
self.body_enabled_switch.value = value
|
|
411
|
+
|
|
412
|
+
@property
|
|
413
|
+
def body_mode(self) -> BodyMode:
|
|
414
|
+
return self.body_mode_select.value
|
|
415
|
+
|
|
416
|
+
@body_mode.setter
|
|
417
|
+
def body_mode(self, value: BodyMode) -> None:
|
|
418
|
+
self.body_mode_select.value = value
|
|
419
|
+
|
|
420
|
+
@property
|
|
421
|
+
def body_raw_language(self) -> BodyRawLanguage:
|
|
422
|
+
return self.body_raw_language_select.value
|
|
423
|
+
|
|
424
|
+
@body_raw_language.setter
|
|
425
|
+
def body_raw_language(self, value: BodyRawLanguage) -> None:
|
|
426
|
+
self.body_raw_language_select.value = value
|
|
427
|
+
|
|
428
|
+
@property
|
|
429
|
+
def body_raw(self) -> str:
|
|
430
|
+
return self.body_raw_editor.text
|
|
431
|
+
|
|
432
|
+
@body_raw.setter
|
|
433
|
+
def body_raw(self, value: str) -> None:
|
|
434
|
+
self.body_raw_editor.text = value
|
|
435
|
+
|
|
436
|
+
@property
|
|
437
|
+
def body_file(self) -> Path | None:
|
|
438
|
+
return self.body_file_path_chooser.path
|
|
439
|
+
|
|
440
|
+
@body_file.setter
|
|
441
|
+
def body_file(self, value: Path | None) -> None:
|
|
442
|
+
self.body_file_path_chooser.path = value
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def body_form_urlencoded(self) -> list[dict[str, str | bool]]:
|
|
446
|
+
return [
|
|
447
|
+
{
|
|
448
|
+
'enabled': field.enabled,
|
|
449
|
+
'key': field.key,
|
|
450
|
+
'value': field.value,
|
|
451
|
+
}
|
|
452
|
+
for field in self.body_form_urlencoded_fields.fields
|
|
453
|
+
if field.is_filled or field.enabled
|
|
454
|
+
]
|
|
455
|
+
|
|
456
|
+
@body_form_urlencoded.setter
|
|
457
|
+
def body_form_urlencoded(
|
|
458
|
+
self, values: list[dict[str, str | bool]]
|
|
459
|
+
) -> None:
|
|
460
|
+
for field in self.body_form_urlencoded_fields.fields:
|
|
461
|
+
self.body_form_urlencoded_fields.remove_field(field=field)
|
|
462
|
+
|
|
463
|
+
for value in values:
|
|
464
|
+
self.run_worker(
|
|
465
|
+
self.body_form_urlencoded_fields.add_field(
|
|
466
|
+
field=TextDynamicField(
|
|
467
|
+
enabled=value['enabled'],
|
|
468
|
+
key=value['key'],
|
|
469
|
+
value=value['value'],
|
|
470
|
+
),
|
|
471
|
+
before_last=True,
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
@property
|
|
476
|
+
def body_form_multipart(self) -> list[dict[str, str | bool, Path | None]]:
|
|
477
|
+
return [
|
|
478
|
+
{
|
|
479
|
+
'enabled': field.enabled,
|
|
480
|
+
'key': field.key,
|
|
481
|
+
'value': field.value,
|
|
482
|
+
'value_kind': field.value_kind,
|
|
483
|
+
}
|
|
484
|
+
for field in self.body_form_multipart_fields.fields
|
|
485
|
+
if field.is_filled or field.enabled
|
|
486
|
+
]
|
|
487
|
+
|
|
488
|
+
@body_form_multipart.setter
|
|
489
|
+
def body_form_multipart(
|
|
490
|
+
self, values: list[dict[str, str | bool, Path | None]]
|
|
491
|
+
) -> None:
|
|
492
|
+
for field in self.body_form_multipart_fields.fields:
|
|
493
|
+
self.body_form_multipart_fields.remove_field(field=field)
|
|
494
|
+
|
|
495
|
+
for value in values:
|
|
496
|
+
self.run_worker(
|
|
497
|
+
self.body_form_multipart_fields.add_field(
|
|
498
|
+
TextOrFileDynamicField(
|
|
499
|
+
enabled=value['enabled'],
|
|
500
|
+
key=value['key'],
|
|
501
|
+
value=value['value'],
|
|
502
|
+
value_kind=value['value_kind'],
|
|
503
|
+
),
|
|
504
|
+
before_last=True,
|
|
505
|
+
)
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
@property
|
|
509
|
+
def option_timeout(self) -> float | None:
|
|
510
|
+
try:
|
|
511
|
+
return float(self.options_timeout_input.value)
|
|
512
|
+
except ValueError:
|
|
513
|
+
return None
|
|
514
|
+
|
|
515
|
+
@option_timeout.setter
|
|
516
|
+
def option_timeout(self, value: float | None) -> None:
|
|
517
|
+
self.options_timeout_input.value = value
|
|
518
|
+
|
|
519
|
+
@property
|
|
520
|
+
def option_follow_redirects(self) -> bool:
|
|
521
|
+
return self.options_follow_redirects_switch.value
|
|
522
|
+
|
|
523
|
+
@option_follow_redirects.setter
|
|
524
|
+
def option_follow_redirects(self, value: bool) -> None:
|
|
525
|
+
self.options_follow_redirects_switch.value = value
|
|
526
|
+
|
|
527
|
+
@property
|
|
528
|
+
def option_verify_ssl(self) -> bool:
|
|
529
|
+
return self.options_verify_ssl_switch.value
|
|
530
|
+
|
|
531
|
+
@option_verify_ssl.setter
|
|
532
|
+
def option_verify_ssl(self, value: bool) -> None:
|
|
533
|
+
self.options_verify_ssl_switch.value = value
|
|
534
|
+
|
|
535
|
+
@on(Select.Changed, '#auth-mode')
|
|
536
|
+
def _on_change_auth_mode(self, message: Select.Changed) -> None:
|
|
537
|
+
if message.value == 'basic':
|
|
538
|
+
self.auth_mode_switcher.current = 'auth-basic'
|
|
539
|
+
elif message.value == 'bearer':
|
|
540
|
+
self.auth_mode_switcher.current = 'auth-bearer'
|
|
541
|
+
elif message.value == 'api_key':
|
|
542
|
+
self.auth_mode_switcher.current = 'auth-api-key'
|
|
543
|
+
elif message.value == 'digest':
|
|
544
|
+
self.auth_mode_switcher.current = 'auth-digest'
|
|
545
|
+
|
|
546
|
+
@on(Select.Changed, '#body-mode')
|
|
547
|
+
def _on_change_body_mode(self, message: Select.Changed) -> None:
|
|
548
|
+
if message.value == BodyMode.FILE:
|
|
549
|
+
self.body_mode_switcher.current = 'body-mode-file'
|
|
550
|
+
elif message.value == BodyMode.RAW:
|
|
551
|
+
self.body_mode_switcher.current = 'body-mode-raw'
|
|
552
|
+
elif message.value == BodyMode.FORM_URLENCODED:
|
|
553
|
+
self.body_mode_switcher.current = 'body-mode-form-urlencoded'
|
|
554
|
+
elif message.value == BodyMode.FORM_MULTIPART:
|
|
555
|
+
self.body_mode_switcher.current = 'body-mode-form-multipart'
|
|
556
|
+
|
|
557
|
+
@on(Select.Changed, '#body-raw-language')
|
|
558
|
+
def _on_change_body_raw_language(self, message: Select.Changed) -> None:
|
|
559
|
+
self.body_raw_editor.language = message.value
|
|
560
|
+
|
|
561
|
+
@on(DynamicFields.FieldFilled, '#body-form-urlencoded')
|
|
562
|
+
def _on_form_filled(self, message: DynamicFields.FieldFilled) -> None:
|
|
563
|
+
self.body_enabled_switch.value = True
|
|
564
|
+
|
|
565
|
+
@on(DynamicFields.FieldEmpty, '#body-form-urlencoded')
|
|
566
|
+
def _on_form_empty(self, message: DynamicFields.FieldEmpty) -> None:
|
|
567
|
+
if not message.control.filled_fields:
|
|
568
|
+
self.body_enabled_switch.value = False
|
|
569
|
+
|
|
570
|
+
@on(CustomTextArea.Changed, '#body-raw')
|
|
571
|
+
def _on_change_body_raw(self, message: CustomTextArea.Changed) -> None:
|
|
572
|
+
if self.body_raw_editor.text == '':
|
|
573
|
+
self.body_enabled_switch.value = False
|
|
574
|
+
else:
|
|
575
|
+
self.body_enabled_switch.value = True
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from textual import on
|
|
4
|
+
from textual.app import ComposeResult
|
|
5
|
+
from textual.binding import Binding
|
|
6
|
+
from textual.containers import Horizontal, Vertical
|
|
7
|
+
from textual.screen import ModalScreen
|
|
8
|
+
from textual.widgets import Button, Label, Select
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from restiny.ui.app import RESTinyApp
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SettingsScreen(ModalScreen):
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
app: RESTinyApp
|
|
17
|
+
|
|
18
|
+
DEFAULT_CSS = """
|
|
19
|
+
SettingsScreen {
|
|
20
|
+
align: center middle;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#modal-content {
|
|
24
|
+
width: auto;
|
|
25
|
+
height: auto;
|
|
26
|
+
max-width: 40%;
|
|
27
|
+
border: heavy black;
|
|
28
|
+
border-title-color: gray;
|
|
29
|
+
background: $surface;
|
|
30
|
+
}
|
|
31
|
+
"""
|
|
32
|
+
AUTO_FOCUS = '#theme'
|
|
33
|
+
|
|
34
|
+
BINDINGS = [
|
|
35
|
+
Binding(
|
|
36
|
+
key='escape',
|
|
37
|
+
action='dismiss',
|
|
38
|
+
description='Quit the screen',
|
|
39
|
+
show=False,
|
|
40
|
+
),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
themes: list[str],
|
|
46
|
+
theme: str,
|
|
47
|
+
) -> None:
|
|
48
|
+
super().__init__()
|
|
49
|
+
self._themes = themes
|
|
50
|
+
self._theme = theme
|
|
51
|
+
|
|
52
|
+
def compose(self) -> ComposeResult:
|
|
53
|
+
with Vertical(id='modal-content'):
|
|
54
|
+
with Horizontal(classes='w-auto h-auto mt-1 px-1'):
|
|
55
|
+
yield Label('theme', classes='mt-1')
|
|
56
|
+
yield Select(
|
|
57
|
+
((theme, theme) for theme in self._themes),
|
|
58
|
+
value=self._theme,
|
|
59
|
+
allow_blank=False,
|
|
60
|
+
id='theme',
|
|
61
|
+
)
|
|
62
|
+
with Horizontal(classes='w-auto h-auto mt-1'):
|
|
63
|
+
yield Button(label='Cancel', classes='w-1fr', id='cancel')
|
|
64
|
+
yield Button(label='Confirm', classes='w-1fr', id='confirm')
|
|
65
|
+
|
|
66
|
+
def on_mount(self) -> None:
|
|
67
|
+
self.modal_content = self.query_one('#modal-content', Vertical)
|
|
68
|
+
self.theme_select = self.query_one('#theme', Select)
|
|
69
|
+
self.cancel_button = self.query_one('#cancel', Button)
|
|
70
|
+
self.confirm_button = self.query_one('#confirm', Button)
|
|
71
|
+
|
|
72
|
+
self.modal_content.border_title = 'Settings'
|
|
73
|
+
|
|
74
|
+
@on(Button.Pressed, '#cancel')
|
|
75
|
+
def _on_cancel(self, message: Button.Pressed) -> None:
|
|
76
|
+
self.dismiss(result=None)
|
|
77
|
+
|
|
78
|
+
@on(Button.Pressed, '#confirm')
|
|
79
|
+
def _on_confirm(self, message: Button.Pressed) -> None:
|
|
80
|
+
self.dismiss(result={'theme': self.theme_select.value})
|