restiny 0.2.0__py3-none-any.whl → 0.3.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.
Potentially problematic release.
This version of restiny might be problematic. Click here for more details.
- restiny/__about__.py +1 -1
- restiny/assets/style.tcss +13 -1
- restiny/core/app.py +288 -207
- restiny/core/request_area.py +281 -112
- restiny/core/response_area.py +53 -20
- restiny/core/url_area.py +28 -20
- restiny/enums.py +7 -0
- restiny/httpx_auths.py +52 -0
- restiny/test.py +13 -0
- restiny/utils.py +45 -15
- restiny/widgets/__init__.py +2 -0
- restiny/widgets/dynamic_fields.py +129 -103
- restiny/widgets/password_input.py +159 -0
- restiny/widgets/path_chooser.py +49 -28
- {restiny-0.2.0.dist-info → restiny-0.3.0.dist-info}/METADATA +2 -2
- restiny-0.3.0.dist-info/RECORD +27 -0
- restiny/screens/__init__.py +0 -0
- restiny/screens/dialog.py +0 -109
- restiny-0.2.0.dist-info/RECORD +0 -26
- {restiny-0.2.0.dist-info → restiny-0.3.0.dist-info}/WHEEL +0 -0
- {restiny-0.2.0.dist-info → restiny-0.3.0.dist-info}/entry_points.txt +0 -0
- {restiny-0.2.0.dist-info → restiny-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {restiny-0.2.0.dist-info → restiny-0.3.0.dist-info}/top_level.txt +0 -0
restiny/core/request_area.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
from typing import Literal
|
|
3
4
|
|
|
4
5
|
from textual import on
|
|
5
6
|
from textual.app import ComposeResult
|
|
@@ -15,10 +16,11 @@ from textual.widgets import (
|
|
|
15
16
|
TabPane,
|
|
16
17
|
)
|
|
17
18
|
|
|
18
|
-
from restiny.enums import BodyMode, BodyRawLanguage
|
|
19
|
+
from restiny.enums import AuthMode, BodyMode, BodyRawLanguage
|
|
19
20
|
from restiny.widgets import (
|
|
20
21
|
CustomTextArea,
|
|
21
22
|
DynamicFields,
|
|
23
|
+
PasswordInput,
|
|
22
24
|
PathChooser,
|
|
23
25
|
TextDynamicField,
|
|
24
26
|
)
|
|
@@ -33,7 +35,7 @@ class HeaderField:
|
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
@dataclass
|
|
36
|
-
class
|
|
38
|
+
class ParamField:
|
|
37
39
|
enabled: bool
|
|
38
40
|
key: str
|
|
39
41
|
value: str
|
|
@@ -54,28 +56,64 @@ class FormMultipartField:
|
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
@dataclass
|
|
57
|
-
class
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
59
|
+
class BasicAuth:
|
|
60
|
+
username: str
|
|
61
|
+
password: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class BearerAuth:
|
|
66
|
+
token: str
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class APIKeyAuth:
|
|
71
|
+
key: str
|
|
72
|
+
value: str
|
|
73
|
+
where: Literal['header', 'param']
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class DigestAuth:
|
|
78
|
+
username: str
|
|
79
|
+
password: str
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class Options:
|
|
84
|
+
timeout: int | float | None
|
|
85
|
+
follow_redirects: bool
|
|
86
|
+
verify_ssl: bool
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class Body:
|
|
91
|
+
enabled: bool
|
|
92
|
+
raw_language: BodyRawLanguage | None
|
|
93
|
+
mode: BodyMode
|
|
94
|
+
payload: (
|
|
95
|
+
str
|
|
96
|
+
| Path
|
|
97
|
+
| list[FormUrlEncodedField]
|
|
98
|
+
| list[FormMultipartField]
|
|
99
|
+
| None
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
_AuthType = BasicAuth | BearerAuth | APIKeyAuth | DigestAuth
|
|
104
|
+
|
|
76
105
|
|
|
106
|
+
@dataclass
|
|
107
|
+
class Auth:
|
|
108
|
+
enabled: bool
|
|
109
|
+
value: _AuthType
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class RequestAreaData:
|
|
77
114
|
headers: list[HeaderField]
|
|
78
|
-
|
|
115
|
+
params: list[ParamField]
|
|
116
|
+
auth: Auth
|
|
79
117
|
body: Body
|
|
80
118
|
options: Options
|
|
81
119
|
|
|
@@ -101,11 +139,80 @@ class RequestArea(Static):
|
|
|
101
139
|
fields=[TextDynamicField(enabled=False, key='', value='')],
|
|
102
140
|
id='headers',
|
|
103
141
|
)
|
|
104
|
-
with TabPane('
|
|
142
|
+
with TabPane('Params'):
|
|
105
143
|
yield DynamicFields(
|
|
106
144
|
fields=[TextDynamicField(enabled=False, key='', value='')],
|
|
107
145
|
id='params',
|
|
108
146
|
)
|
|
147
|
+
with TabPane('Auth'):
|
|
148
|
+
with Horizontal(classes='h-auto'):
|
|
149
|
+
yield Switch(tooltip='Enabled', id='auth-enabled')
|
|
150
|
+
yield Select(
|
|
151
|
+
(
|
|
152
|
+
('Basic', AuthMode.BASIC),
|
|
153
|
+
('Bearer', AuthMode.BEARER),
|
|
154
|
+
('API Key', AuthMode.API_KEY),
|
|
155
|
+
('Digest', AuthMode.DIGEST),
|
|
156
|
+
),
|
|
157
|
+
allow_blank=False,
|
|
158
|
+
tooltip='Auth mode',
|
|
159
|
+
id='auth-mode',
|
|
160
|
+
)
|
|
161
|
+
with ContentSwitcher(
|
|
162
|
+
initial='auth-basic', id='auth-mode-switcher'
|
|
163
|
+
):
|
|
164
|
+
with Horizontal(id='auth-basic', classes='mt-1'):
|
|
165
|
+
yield Input(
|
|
166
|
+
placeholder='Username',
|
|
167
|
+
select_on_focus=False,
|
|
168
|
+
classes='w-1fr',
|
|
169
|
+
id='auth-basic-username',
|
|
170
|
+
)
|
|
171
|
+
yield PasswordInput(
|
|
172
|
+
placeholder='Password',
|
|
173
|
+
select_on_focus=False,
|
|
174
|
+
classes='w-2fr',
|
|
175
|
+
id='auth-basic-password',
|
|
176
|
+
)
|
|
177
|
+
with Horizontal(id='auth-bearer', classes='mt-1'):
|
|
178
|
+
yield PasswordInput(
|
|
179
|
+
placeholder='Token',
|
|
180
|
+
select_on_focus=False,
|
|
181
|
+
id='auth-bearer-token',
|
|
182
|
+
)
|
|
183
|
+
with Horizontal(id='auth-api-key', classes='mt-1'):
|
|
184
|
+
yield Select(
|
|
185
|
+
(('Header', 'header'), ('Param', 'param')),
|
|
186
|
+
allow_blank=False,
|
|
187
|
+
tooltip='Where',
|
|
188
|
+
classes='w-1fr',
|
|
189
|
+
id='auth-api-key-where',
|
|
190
|
+
)
|
|
191
|
+
yield Input(
|
|
192
|
+
placeholder='Key',
|
|
193
|
+
classes='w-2fr',
|
|
194
|
+
id='auth-api-key-key',
|
|
195
|
+
)
|
|
196
|
+
yield PasswordInput(
|
|
197
|
+
placeholder='Value',
|
|
198
|
+
classes='w-3fr',
|
|
199
|
+
id='auth-api-key-value',
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
with Horizontal(id='auth-digest', classes='mt-1'):
|
|
203
|
+
yield Input(
|
|
204
|
+
placeholder='Username',
|
|
205
|
+
select_on_focus=False,
|
|
206
|
+
classes='w-1fr',
|
|
207
|
+
id='auth-digest-username',
|
|
208
|
+
)
|
|
209
|
+
yield PasswordInput(
|
|
210
|
+
placeholder='Password',
|
|
211
|
+
select_on_focus=False,
|
|
212
|
+
classes='w-2fr',
|
|
213
|
+
id='auth-digest-password',
|
|
214
|
+
)
|
|
215
|
+
|
|
109
216
|
with TabPane('Body'):
|
|
110
217
|
with Horizontal(classes='h-auto'):
|
|
111
218
|
yield Switch(id='body-enabled', tooltip='Send body?')
|
|
@@ -117,7 +224,7 @@ class RequestArea(Static):
|
|
|
117
224
|
('Form (multipart)', BodyMode.FORM_MULTIPART),
|
|
118
225
|
),
|
|
119
226
|
allow_blank=False,
|
|
120
|
-
tooltip='Body
|
|
227
|
+
tooltip='Body mode',
|
|
121
228
|
id='body-mode',
|
|
122
229
|
)
|
|
123
230
|
with ContentSwitcher(
|
|
@@ -174,8 +281,11 @@ class RequestArea(Static):
|
|
|
174
281
|
yield Input(
|
|
175
282
|
'5.5',
|
|
176
283
|
placeholder='5.5',
|
|
177
|
-
|
|
284
|
+
select_on_focus=False,
|
|
285
|
+
type='number',
|
|
286
|
+
valid_empty=True,
|
|
178
287
|
classes='w-1fr',
|
|
288
|
+
id='options-timeout',
|
|
179
289
|
)
|
|
180
290
|
with Horizontal(classes='mt-1 h-auto'):
|
|
181
291
|
yield Switch(id='options-follow-redirects')
|
|
@@ -189,6 +299,36 @@ class RequestArea(Static):
|
|
|
189
299
|
|
|
190
300
|
self.param_fields = self.query_one('#params', DynamicFields)
|
|
191
301
|
|
|
302
|
+
self.auth_enabled_switch = self.query_one('#auth-enabled', Switch)
|
|
303
|
+
self.auth_mode_switcher = self.query_one(
|
|
304
|
+
'#auth-mode-switcher', ContentSwitcher
|
|
305
|
+
)
|
|
306
|
+
self.auth_mode_select = self.query_one('#auth-mode', Select)
|
|
307
|
+
self.auth_basic_username_input = self.query_one(
|
|
308
|
+
'#auth-basic-username', Input
|
|
309
|
+
)
|
|
310
|
+
self.auth_basic_password_input = self.query_one(
|
|
311
|
+
'#auth-basic-password', PasswordInput
|
|
312
|
+
)
|
|
313
|
+
self.auth_bearer_token_input = self.query_one(
|
|
314
|
+
'#auth-bearer-token', PasswordInput
|
|
315
|
+
)
|
|
316
|
+
self.auth_api_key_key_input = self.query_one(
|
|
317
|
+
'#auth-api-key-key', Input
|
|
318
|
+
)
|
|
319
|
+
self.auth_api_key_value_input = self.query_one(
|
|
320
|
+
'#auth-api-key-value', PasswordInput
|
|
321
|
+
)
|
|
322
|
+
self.auth_api_key_where_select = self.query_one(
|
|
323
|
+
'#auth-api-key-where', Select
|
|
324
|
+
)
|
|
325
|
+
self.auth_digest_username_input = self.query_one(
|
|
326
|
+
'#auth-digest-username', Input
|
|
327
|
+
)
|
|
328
|
+
self.auth_digest_password_input = self.query_one(
|
|
329
|
+
'#auth-digest-password', PasswordInput
|
|
330
|
+
)
|
|
331
|
+
|
|
192
332
|
self.body_enabled_switch = self.query_one('#body-enabled', Switch)
|
|
193
333
|
self.body_mode_select = self.query_one('#body-mode', Select)
|
|
194
334
|
self.body_mode_switcher = self.query_one(
|
|
@@ -214,8 +354,28 @@ class RequestArea(Static):
|
|
|
214
354
|
'#options-verify-ssl', Switch
|
|
215
355
|
)
|
|
216
356
|
|
|
357
|
+
def get_data(self) -> RequestAreaData:
|
|
358
|
+
return RequestAreaData(
|
|
359
|
+
headers=self._get_headers(),
|
|
360
|
+
params=self._get_params(),
|
|
361
|
+
auth=self._get_auth(),
|
|
362
|
+
body=self._get_body(),
|
|
363
|
+
options=self._get_options(),
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
@on(Select.Changed, '#auth-mode')
|
|
367
|
+
def _on_change_auth_mode(self, message: Select.Changed) -> None:
|
|
368
|
+
if message.value == 'basic':
|
|
369
|
+
self.auth_mode_switcher.current = 'auth-basic'
|
|
370
|
+
elif message.value == 'bearer':
|
|
371
|
+
self.auth_mode_switcher.current = 'auth-bearer'
|
|
372
|
+
elif message.value == 'api_key':
|
|
373
|
+
self.auth_mode_switcher.current = 'auth-api-key'
|
|
374
|
+
elif message.value == 'digest':
|
|
375
|
+
self.auth_mode_switcher.current = 'auth-digest'
|
|
376
|
+
|
|
217
377
|
@on(Select.Changed, '#body-mode')
|
|
218
|
-
def
|
|
378
|
+
def _on_change_body_mode(self, message: Select.Changed) -> None:
|
|
219
379
|
if message.value == BodyMode.FILE:
|
|
220
380
|
self.body_mode_switcher.current = 'body-mode-file'
|
|
221
381
|
elif message.value == BodyMode.RAW:
|
|
@@ -226,113 +386,122 @@ class RequestArea(Static):
|
|
|
226
386
|
self.body_mode_switcher.current = 'body-mode-form-multipart'
|
|
227
387
|
|
|
228
388
|
@on(Select.Changed, '#body-raw-language')
|
|
229
|
-
def
|
|
389
|
+
def _on_change_body_raw_language(self, message: Select.Changed) -> None:
|
|
230
390
|
self.body_raw_editor.language = message.value
|
|
231
391
|
|
|
232
392
|
@on(DynamicFields.FieldFilled, '#body-form-urlencoded')
|
|
233
|
-
def
|
|
393
|
+
def _on_form_filled(self, message: DynamicFields.FieldFilled) -> None:
|
|
234
394
|
self.body_enabled_switch.value = True
|
|
235
395
|
|
|
236
396
|
@on(DynamicFields.FieldEmpty, '#body-form-urlencoded')
|
|
237
|
-
def
|
|
397
|
+
def _on_form_empty(self, message: DynamicFields.FieldEmpty) -> None:
|
|
238
398
|
if not message.control.filled_fields:
|
|
239
399
|
self.body_enabled_switch.value = False
|
|
240
400
|
|
|
241
401
|
@on(CustomTextArea.Changed, '#body-raw')
|
|
242
|
-
def
|
|
402
|
+
def _on_change_body_raw(self, message: CustomTextArea.Changed) -> None:
|
|
243
403
|
if self.body_raw_editor.text == '':
|
|
244
404
|
self.body_enabled_switch.value = False
|
|
245
405
|
else:
|
|
246
406
|
self.body_enabled_switch.value = True
|
|
247
407
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
408
|
+
def _get_headers(self) -> list[HeaderField]:
|
|
409
|
+
return [
|
|
410
|
+
HeaderField(
|
|
411
|
+
enabled=header_field['enabled'],
|
|
412
|
+
key=header_field['key'],
|
|
413
|
+
value=header_field['value'],
|
|
414
|
+
)
|
|
415
|
+
for header_field in self.header_fields.values
|
|
416
|
+
]
|
|
417
|
+
|
|
418
|
+
def _get_params(self) -> list[ParamField]:
|
|
419
|
+
return [
|
|
420
|
+
ParamField(
|
|
421
|
+
enabled=param_field['enabled'],
|
|
422
|
+
key=param_field['key'],
|
|
423
|
+
value=param_field['value'],
|
|
424
|
+
)
|
|
425
|
+
for param_field in self.param_fields.values
|
|
426
|
+
]
|
|
427
|
+
|
|
428
|
+
def _get_auth(self) -> _AuthType:
|
|
429
|
+
if self.auth_mode_select.value == AuthMode.BASIC:
|
|
430
|
+
return Auth(
|
|
431
|
+
enabled=self.auth_enabled_switch.value,
|
|
432
|
+
value=BasicAuth(
|
|
433
|
+
username=self.auth_basic_username_input.value,
|
|
434
|
+
password=self.auth_basic_password_input.value,
|
|
435
|
+
),
|
|
436
|
+
)
|
|
437
|
+
elif self.auth_mode_select.value == AuthMode.BEARER:
|
|
438
|
+
return Auth(
|
|
439
|
+
enabled=self.auth_enabled_switch.value,
|
|
440
|
+
value=BearerAuth(token=self.auth_bearer_token_input.value),
|
|
441
|
+
)
|
|
442
|
+
elif self.auth_mode_select.value == AuthMode.API_KEY:
|
|
443
|
+
return Auth(
|
|
444
|
+
enabled=self.auth_enabled_switch.value,
|
|
445
|
+
value=APIKeyAuth(
|
|
446
|
+
key=self.auth_api_key_key_input.value,
|
|
447
|
+
value=self.auth_api_key_value_input.value,
|
|
448
|
+
where=self.auth_api_key_where_select.value,
|
|
449
|
+
),
|
|
450
|
+
)
|
|
451
|
+
elif self.auth_mode_select.value == AuthMode.DIGEST:
|
|
452
|
+
return Auth(
|
|
453
|
+
enabled=self.auth_enabled_switch.value,
|
|
454
|
+
value=DigestAuth(
|
|
455
|
+
username=self.auth_digest_username_input.value,
|
|
456
|
+
password=self.auth_digest_password_input.value,
|
|
457
|
+
),
|
|
260
458
|
)
|
|
261
459
|
|
|
262
|
-
def
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
)
|
|
280
|
-
for query_param_field in self.param_fields.values
|
|
281
|
-
]
|
|
282
|
-
|
|
283
|
-
def get_body() -> RequestAreaData.Body:
|
|
284
|
-
body_send: bool = self.body_enabled_switch.value
|
|
285
|
-
body_type: str = BodyMode(self.body_mode_select.value)
|
|
286
|
-
|
|
287
|
-
payload = None
|
|
288
|
-
if body_type == BodyMode.RAW:
|
|
289
|
-
payload = self.body_raw_editor.text
|
|
290
|
-
elif body_type == BodyMode.FILE:
|
|
291
|
-
payload = self.body_file_path_chooser.path
|
|
292
|
-
elif body_type == BodyMode.FORM_URLENCODED:
|
|
293
|
-
payload = []
|
|
294
|
-
for form_item in self.body_form_urlencoded_fields.values:
|
|
295
|
-
payload.append(
|
|
296
|
-
FormUrlEncodedField(
|
|
297
|
-
enabled=form_item['enabled'],
|
|
298
|
-
key=form_item['key'],
|
|
299
|
-
value=form_item['value'],
|
|
300
|
-
)
|
|
460
|
+
def _get_body(self) -> Body:
|
|
461
|
+
body_send: bool = self.body_enabled_switch.value
|
|
462
|
+
body_mode: str = BodyMode(self.body_mode_select.value)
|
|
463
|
+
|
|
464
|
+
payload = None
|
|
465
|
+
if body_mode == BodyMode.RAW:
|
|
466
|
+
payload = self.body_raw_editor.text
|
|
467
|
+
elif body_mode == BodyMode.FILE:
|
|
468
|
+
payload = self.body_file_path_chooser.path
|
|
469
|
+
elif body_mode == BodyMode.FORM_URLENCODED:
|
|
470
|
+
payload = []
|
|
471
|
+
for form_item in self.body_form_urlencoded_fields.values:
|
|
472
|
+
payload.append(
|
|
473
|
+
FormUrlEncodedField(
|
|
474
|
+
enabled=form_item['enabled'],
|
|
475
|
+
key=form_item['key'],
|
|
476
|
+
value=form_item['value'],
|
|
301
477
|
)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
478
|
+
)
|
|
479
|
+
elif body_mode == BodyMode.FORM_MULTIPART:
|
|
480
|
+
payload = []
|
|
481
|
+
for form_item in self.body_form_multipart_fields.values:
|
|
482
|
+
payload.append(
|
|
483
|
+
FormMultipartField(
|
|
484
|
+
enabled=form_item['enabled'],
|
|
485
|
+
key=form_item['key'],
|
|
486
|
+
value=form_item['value'],
|
|
311
487
|
)
|
|
488
|
+
)
|
|
312
489
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
payload=payload,
|
|
320
|
-
)
|
|
490
|
+
return Body(
|
|
491
|
+
enabled=body_send,
|
|
492
|
+
raw_language=BodyRawLanguage(self.body_raw_language_select.value),
|
|
493
|
+
mode=body_mode,
|
|
494
|
+
payload=payload,
|
|
495
|
+
)
|
|
321
496
|
|
|
322
|
-
|
|
497
|
+
def _get_options(self) -> Options:
|
|
498
|
+
try:
|
|
499
|
+
timeout = float(self.options_timeout_input.value)
|
|
500
|
+
except ValueError:
|
|
323
501
|
timeout = None
|
|
324
|
-
if self.options_timeout_input.value:
|
|
325
|
-
timeout = float(self.options_timeout_input.value)
|
|
326
502
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
return RequestAreaData(
|
|
334
|
-
headers=get_headers(),
|
|
335
|
-
query_params=get_query_params(),
|
|
336
|
-
body=get_body(),
|
|
337
|
-
options=get_options(),
|
|
503
|
+
return Options(
|
|
504
|
+
timeout=timeout,
|
|
505
|
+
follow_redirects=self.options_follow_redirects_switch.value,
|
|
506
|
+
verify_ssl=self.options_verify_ssl_switch.value,
|
|
338
507
|
)
|
restiny/core/response_area.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from http import HTTPStatus
|
|
3
|
+
|
|
1
4
|
from textual import on
|
|
2
5
|
from textual.app import ComposeResult
|
|
3
|
-
from textual.
|
|
6
|
+
from textual.containers import VerticalScroll
|
|
4
7
|
from textual.widgets import (
|
|
5
8
|
ContentSwitcher,
|
|
6
9
|
DataTable,
|
|
@@ -15,6 +18,16 @@ from restiny.enums import BodyRawLanguage
|
|
|
15
18
|
from restiny.widgets import CustomTextArea
|
|
16
19
|
|
|
17
20
|
|
|
21
|
+
@dataclass
|
|
22
|
+
class ResponseAreaData:
|
|
23
|
+
status: HTTPStatus
|
|
24
|
+
size: int
|
|
25
|
+
elapsed_time: float | int
|
|
26
|
+
headers: dict
|
|
27
|
+
body_raw_language: BodyRawLanguage
|
|
28
|
+
body_raw: str
|
|
29
|
+
|
|
30
|
+
|
|
18
31
|
# TODO: Implement 'Trace' tab pane
|
|
19
32
|
class ResponseArea(Static):
|
|
20
33
|
ALLOW_MAXIMIZE = True
|
|
@@ -38,8 +51,6 @@ class ResponseArea(Static):
|
|
|
38
51
|
|
|
39
52
|
"""
|
|
40
53
|
|
|
41
|
-
has_response: bool = Reactive(False, layout=True, init=True)
|
|
42
|
-
|
|
43
54
|
def compose(self) -> ComposeResult:
|
|
44
55
|
with ContentSwitcher(id='response-switcher', initial='no-content'):
|
|
45
56
|
yield Label(
|
|
@@ -49,7 +60,8 @@ class ResponseArea(Static):
|
|
|
49
60
|
|
|
50
61
|
with TabbedContent(id='content'):
|
|
51
62
|
with TabPane('Headers'):
|
|
52
|
-
|
|
63
|
+
with VerticalScroll():
|
|
64
|
+
yield DataTable(show_cursor=False, id='headers')
|
|
53
65
|
with TabPane('Body'):
|
|
54
66
|
yield Select(
|
|
55
67
|
(
|
|
@@ -60,11 +72,11 @@ class ResponseArea(Static):
|
|
|
60
72
|
('XML', BodyRawLanguage.XML),
|
|
61
73
|
),
|
|
62
74
|
allow_blank=False,
|
|
63
|
-
tooltip='
|
|
64
|
-
id='body-
|
|
75
|
+
tooltip='Syntax highlighting for the response body',
|
|
76
|
+
id='body-raw-language',
|
|
65
77
|
)
|
|
66
78
|
yield CustomTextArea.code_editor(
|
|
67
|
-
id='body', read_only=True, classes='mt-1'
|
|
79
|
+
id='body-raw', read_only=True, classes='mt-1'
|
|
68
80
|
)
|
|
69
81
|
|
|
70
82
|
def on_mount(self) -> None:
|
|
@@ -73,25 +85,46 @@ class ResponseArea(Static):
|
|
|
73
85
|
)
|
|
74
86
|
|
|
75
87
|
self.headers_data_table = self.query_one('#headers', DataTable)
|
|
76
|
-
self.
|
|
77
|
-
|
|
88
|
+
self.body_raw_language_select = self.query_one(
|
|
89
|
+
'#body-raw-language', Select
|
|
90
|
+
)
|
|
91
|
+
self.body_raw_editor = self.query_one('#body-raw', CustomTextArea)
|
|
78
92
|
|
|
79
93
|
self.headers_data_table.add_columns('Key', 'Value')
|
|
80
94
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
self.
|
|
95
|
+
def set_data(self, data: ResponseAreaData | None) -> None:
|
|
96
|
+
self.border_title = self.BORDER_TITLE
|
|
97
|
+
self.border_subtitle = ''
|
|
98
|
+
self.headers_data_table.clear()
|
|
99
|
+
self.body_raw_language_select.value = BodyRawLanguage.PLAIN
|
|
100
|
+
self.body_raw_editor.clear()
|
|
84
101
|
|
|
85
|
-
|
|
102
|
+
if data is None:
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
self.border_title = f'Response - {data.status} {data.status.phrase}'
|
|
106
|
+
self.border_subtitle = (
|
|
107
|
+
f'{data.size} bytes in {data.elapsed_time} seconds'
|
|
108
|
+
)
|
|
109
|
+
for header_key, header_value in data.headers.items():
|
|
110
|
+
self.headers_data_table.add_row(header_key, header_value)
|
|
111
|
+
self.body_raw_language_select.value = data.body_raw_language
|
|
112
|
+
self.body_raw_editor.text = data.body_raw
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def is_showing_response(self) -> bool:
|
|
116
|
+
if self._response_switcher.current == 'content':
|
|
117
|
+
return True
|
|
118
|
+
elif self._response_switcher.current == 'no-content':
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
@is_showing_response.setter
|
|
122
|
+
def is_showing_response(self, value: bool) -> None:
|
|
86
123
|
if value is True:
|
|
87
124
|
self._response_switcher.current = 'content'
|
|
88
125
|
elif value is False:
|
|
89
126
|
self._response_switcher.current = 'no-content'
|
|
90
|
-
self.reset_response()
|
|
91
127
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
self.
|
|
95
|
-
self.headers_data_table.clear()
|
|
96
|
-
self.body_type_select.value = BodyRawLanguage.PLAIN
|
|
97
|
-
self.body_text_area.clear()
|
|
128
|
+
@on(Select.Changed, '#body-raw-language')
|
|
129
|
+
def _on_body_raw_language_changed(self, message: Select.Changed) -> None:
|
|
130
|
+
self.body_raw_editor.language = self.body_raw_language_select.value
|