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.
- restiny/__about__.py +1 -1
- restiny/__main__.py +28 -14
- restiny/assets/style.tcss +56 -2
- restiny/consts.py +236 -0
- restiny/data/db.py +60 -0
- restiny/data/models.py +111 -0
- restiny/data/repos.py +455 -0
- restiny/data/sql/__init__.py +3 -0
- restiny/entities.py +438 -0
- restiny/enums.py +14 -5
- restiny/httpx_auths.py +52 -0
- restiny/ui/__init__.py +17 -0
- restiny/ui/app.py +586 -0
- restiny/ui/collections_area.py +594 -0
- restiny/ui/environments_screen.py +270 -0
- restiny/ui/request_area.py +602 -0
- restiny/{core → ui}/response_area.py +4 -1
- restiny/ui/settings_screen.py +73 -0
- restiny/ui/top_bar_area.py +60 -0
- restiny/{core → ui}/url_area.py +54 -38
- restiny/utils.py +52 -15
- restiny/widgets/__init__.py +15 -1
- restiny/widgets/collections_tree.py +74 -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.6.1.dist-info}/METADATA +7 -5
- restiny-0.6.1.dist-info/RECORD +38 -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-0.2.1.dist-info → restiny-0.6.1.dist-info}/WHEEL +0 -0
- {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/entry_points.txt +0 -0
- {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/top_level.txt +0 -0
restiny/ui/app.py
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections.abc import Iterable
|
|
3
|
+
from http import HTTPStatus
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import pyperclip
|
|
7
|
+
from textual import on
|
|
8
|
+
from textual.app import App, ComposeResult, SystemCommand
|
|
9
|
+
from textual.binding import Binding
|
|
10
|
+
from textual.containers import Horizontal, Vertical
|
|
11
|
+
from textual.events import DescendantFocus
|
|
12
|
+
from textual.screen import Screen
|
|
13
|
+
from textual.widget import Widget
|
|
14
|
+
from textual.widgets import Footer, Header
|
|
15
|
+
|
|
16
|
+
from restiny.__about__ import __version__
|
|
17
|
+
from restiny.assets import STYLE_TCSS
|
|
18
|
+
from restiny.consts import CUSTOM_THEMES
|
|
19
|
+
from restiny.data.repos import (
|
|
20
|
+
EnvironmentsSQLRepo,
|
|
21
|
+
FoldersSQLRepo,
|
|
22
|
+
RequestsSQLRepo,
|
|
23
|
+
SettingsSQLRepo,
|
|
24
|
+
)
|
|
25
|
+
from restiny.entities import Request, Settings
|
|
26
|
+
from restiny.enums import (
|
|
27
|
+
AuthMode,
|
|
28
|
+
BodyMode,
|
|
29
|
+
BodyRawLanguage,
|
|
30
|
+
ContentType,
|
|
31
|
+
)
|
|
32
|
+
from restiny.ui import (
|
|
33
|
+
CollectionsArea,
|
|
34
|
+
RequestArea,
|
|
35
|
+
ResponseArea,
|
|
36
|
+
TopBarArea,
|
|
37
|
+
URLArea,
|
|
38
|
+
)
|
|
39
|
+
from restiny.ui.environments_screen import EnvironmentsScreen
|
|
40
|
+
from restiny.ui.response_area import ResponseAreaData
|
|
41
|
+
from restiny.ui.settings_screen import SettingsScreen
|
|
42
|
+
from restiny.widgets.custom_text_area import CustomTextArea
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class RESTinyApp(App, inherit_bindings=False):
|
|
46
|
+
TITLE = f'RESTiny v{__version__}'
|
|
47
|
+
SUB_TITLE = 'Minimal HTTP client, no bullshit'
|
|
48
|
+
CSS_PATH = STYLE_TCSS
|
|
49
|
+
BINDINGS = [
|
|
50
|
+
Binding(
|
|
51
|
+
key='escape', action='quit', description='Quit the app', show=True
|
|
52
|
+
),
|
|
53
|
+
Binding(
|
|
54
|
+
key='ctrl+n',
|
|
55
|
+
action='prompt_add',
|
|
56
|
+
description='Add req/folder',
|
|
57
|
+
show=True,
|
|
58
|
+
),
|
|
59
|
+
Binding(
|
|
60
|
+
key='f2',
|
|
61
|
+
action='prompt_update',
|
|
62
|
+
description='Update req/folder',
|
|
63
|
+
show=True,
|
|
64
|
+
),
|
|
65
|
+
Binding(
|
|
66
|
+
key='delete',
|
|
67
|
+
action='prompt_delete',
|
|
68
|
+
description='Delete req/folder',
|
|
69
|
+
show=True,
|
|
70
|
+
),
|
|
71
|
+
Binding(
|
|
72
|
+
key='ctrl+s',
|
|
73
|
+
action='save',
|
|
74
|
+
description='Save req',
|
|
75
|
+
show=True,
|
|
76
|
+
),
|
|
77
|
+
Binding(
|
|
78
|
+
key='ctrl+b',
|
|
79
|
+
action='toggle_collections',
|
|
80
|
+
description='Toggle collections',
|
|
81
|
+
show=True,
|
|
82
|
+
),
|
|
83
|
+
Binding(
|
|
84
|
+
key='f10',
|
|
85
|
+
action='maximize_or_minimize_area',
|
|
86
|
+
description='Maximize/Minimize area',
|
|
87
|
+
show=True,
|
|
88
|
+
),
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
folders_repo: FoldersSQLRepo,
|
|
94
|
+
requests_repo: RequestsSQLRepo,
|
|
95
|
+
settings_repo: SettingsSQLRepo,
|
|
96
|
+
environments_repo: EnvironmentsSQLRepo,
|
|
97
|
+
*args,
|
|
98
|
+
**kwargs,
|
|
99
|
+
) -> None:
|
|
100
|
+
super().__init__(*args, **kwargs)
|
|
101
|
+
self.folders_repo = folders_repo
|
|
102
|
+
self.requests_repo = requests_repo
|
|
103
|
+
self.settings_repo = settings_repo
|
|
104
|
+
self.environments_repo = environments_repo
|
|
105
|
+
|
|
106
|
+
self.active_request_task: asyncio.Task | None = None
|
|
107
|
+
self.last_focused_widget: Widget | None = None
|
|
108
|
+
self.last_focused_maximizable_area: Widget | None = None
|
|
109
|
+
|
|
110
|
+
self._selected_request: Request | None = None
|
|
111
|
+
|
|
112
|
+
def compose(self) -> ComposeResult:
|
|
113
|
+
yield Header(show_clock=True)
|
|
114
|
+
with Horizontal():
|
|
115
|
+
yield CollectionsArea(classes='w-1fr')
|
|
116
|
+
with Vertical(classes='w-6fr'):
|
|
117
|
+
with Vertical(classes='h-auto'):
|
|
118
|
+
yield TopBarArea()
|
|
119
|
+
yield URLArea()
|
|
120
|
+
with Horizontal(classes='h-1fr'):
|
|
121
|
+
yield RequestArea()
|
|
122
|
+
yield ResponseArea()
|
|
123
|
+
yield Footer()
|
|
124
|
+
|
|
125
|
+
def on_mount(self) -> None:
|
|
126
|
+
self.collections_area = self.query_one(CollectionsArea)
|
|
127
|
+
self.top_bar_area = self.query_one(TopBarArea)
|
|
128
|
+
self.url_area = self.query_one(URLArea)
|
|
129
|
+
self.request_area = self.query_one(RequestArea)
|
|
130
|
+
self.response_area = self.query_one(ResponseArea)
|
|
131
|
+
|
|
132
|
+
self.selected_request = None
|
|
133
|
+
|
|
134
|
+
self._register_themes()
|
|
135
|
+
self._apply_settings()
|
|
136
|
+
|
|
137
|
+
def get_system_commands(self, screen: Screen) -> Iterable[SystemCommand]:
|
|
138
|
+
yield SystemCommand('Copy as cURL', None, self.action_copy_as_curl)
|
|
139
|
+
yield SystemCommand(
|
|
140
|
+
'Show/Hide keys and help panel',
|
|
141
|
+
None,
|
|
142
|
+
self.action_toggle_help_panel,
|
|
143
|
+
)
|
|
144
|
+
yield SystemCommand(
|
|
145
|
+
'Save screenshot',
|
|
146
|
+
None,
|
|
147
|
+
lambda: self.set_timer(0.1, self.deliver_screenshot),
|
|
148
|
+
)
|
|
149
|
+
yield SystemCommand(
|
|
150
|
+
'Manage environments', None, self.action_manage_envs
|
|
151
|
+
)
|
|
152
|
+
yield SystemCommand(
|
|
153
|
+
'Manage settings', None, self.action_manage_settings
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def action_toggle_collections(self) -> None:
|
|
157
|
+
if self.collections_area.display:
|
|
158
|
+
self.collections_area.display = False
|
|
159
|
+
else:
|
|
160
|
+
self.collections_area.display = True
|
|
161
|
+
|
|
162
|
+
def action_prompt_add(self) -> None:
|
|
163
|
+
self.collections_area.prompt_add()
|
|
164
|
+
|
|
165
|
+
def action_prompt_update(self) -> None:
|
|
166
|
+
if not self.selected_request:
|
|
167
|
+
self.notify('No request selected', severity='warning')
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
self.collections_area.prompt_update()
|
|
171
|
+
|
|
172
|
+
def action_prompt_delete(self) -> None:
|
|
173
|
+
if not self.selected_request:
|
|
174
|
+
self.notify('No request selected', severity='warning')
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
self.collections_area.prompt_delete()
|
|
178
|
+
|
|
179
|
+
def action_save(self) -> None:
|
|
180
|
+
if not self.selected_request:
|
|
181
|
+
self.notify('No request selected', severity='warning')
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
req = self.get_request()
|
|
185
|
+
self.requests_repo.update(request=req)
|
|
186
|
+
self.collections_area._populate_children(
|
|
187
|
+
self.collections_area.collections_tree.current_parent_folder
|
|
188
|
+
)
|
|
189
|
+
self.notify('Saved changes', severity='information')
|
|
190
|
+
|
|
191
|
+
def action_maximize_or_minimize_area(self) -> None:
|
|
192
|
+
if not self.last_focused_maximizable_area:
|
|
193
|
+
self.notify('No area focused', severity='warning')
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
if self.screen.maximized:
|
|
197
|
+
self.screen.minimize()
|
|
198
|
+
else:
|
|
199
|
+
self.screen.maximize(self.last_focused_maximizable_area)
|
|
200
|
+
|
|
201
|
+
def action_toggle_help_panel(self) -> None:
|
|
202
|
+
if self.query('HelpPanel'):
|
|
203
|
+
self.action_hide_help_panel()
|
|
204
|
+
else:
|
|
205
|
+
self.action_show_help_panel()
|
|
206
|
+
|
|
207
|
+
def action_copy_as_curl(self) -> None:
|
|
208
|
+
if not self.selected_request:
|
|
209
|
+
self.notify(
|
|
210
|
+
'No request selected',
|
|
211
|
+
severity='warning',
|
|
212
|
+
)
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
request = self.get_resolved_request()
|
|
216
|
+
self.copy_to_clipboard(request.to_curl())
|
|
217
|
+
self.notify(
|
|
218
|
+
'CURL command copied to clipboard',
|
|
219
|
+
severity='information',
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def action_manage_settings(self) -> None:
|
|
223
|
+
def on_settings_result(result: dict | None) -> None:
|
|
224
|
+
if not result:
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
self.settings_repo.set(Settings(theme=result['theme']))
|
|
228
|
+
self._apply_settings()
|
|
229
|
+
|
|
230
|
+
self.push_screen(
|
|
231
|
+
screen=SettingsScreen(),
|
|
232
|
+
callback=on_settings_result,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def action_manage_envs(self) -> None:
|
|
236
|
+
def on_manage_environments_result(result) -> None:
|
|
237
|
+
self.top_bar_area.populate()
|
|
238
|
+
|
|
239
|
+
self.push_screen(
|
|
240
|
+
screen=EnvironmentsScreen(), callback=on_manage_environments_result
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def copy_to_clipboard(self, text: str) -> None:
|
|
244
|
+
super().copy_to_clipboard(text)
|
|
245
|
+
try:
|
|
246
|
+
# Also copy to the system clipboard (outside of the app)
|
|
247
|
+
pyperclip.copy(text)
|
|
248
|
+
except Exception:
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
@on(DescendantFocus)
|
|
252
|
+
def _on_focus(self, event: DescendantFocus) -> None:
|
|
253
|
+
self.last_focused_widget = event.widget
|
|
254
|
+
last_focused_maximizable_area = self._find_maximizable_area_by_widget(
|
|
255
|
+
widget=event.widget
|
|
256
|
+
)
|
|
257
|
+
if last_focused_maximizable_area:
|
|
258
|
+
self.last_focused_maximizable_area = last_focused_maximizable_area
|
|
259
|
+
|
|
260
|
+
@on(URLArea.SendRequest)
|
|
261
|
+
def _on_send_request(self, message: URLArea.SendRequest) -> None:
|
|
262
|
+
self.active_request_task = asyncio.create_task(self._send_request())
|
|
263
|
+
|
|
264
|
+
@on(URLArea.CancelRequest)
|
|
265
|
+
def _on_cancel_request(self, message: URLArea.CancelRequest) -> None:
|
|
266
|
+
if self.active_request_task and not self.active_request_task.done():
|
|
267
|
+
self.active_request_task.cancel()
|
|
268
|
+
|
|
269
|
+
@on(CollectionsArea.RequestSelected)
|
|
270
|
+
def _on_request_selected(
|
|
271
|
+
self, message: CollectionsArea.RequestSelected
|
|
272
|
+
) -> None:
|
|
273
|
+
req = self.requests_repo.get_by_id(id=message.request_id).data
|
|
274
|
+
self.selected_request = req
|
|
275
|
+
self.set_request(request=req)
|
|
276
|
+
|
|
277
|
+
self.response_area.clear()
|
|
278
|
+
self.response_area.is_showing_response = False
|
|
279
|
+
|
|
280
|
+
@on(CollectionsArea.RequestUpdated)
|
|
281
|
+
def _on_request_updated(self, message) -> None:
|
|
282
|
+
req = self.requests_repo.get_by_id(id=message.request_id).data
|
|
283
|
+
self.selected_request = req
|
|
284
|
+
|
|
285
|
+
@on(CollectionsArea.RequestDeleted)
|
|
286
|
+
def _on_request_deleted(self, message) -> None:
|
|
287
|
+
self.selected_request = None
|
|
288
|
+
|
|
289
|
+
@on(CollectionsArea.FolderSelected)
|
|
290
|
+
def _on_folder_selected(self, message) -> None:
|
|
291
|
+
self.selected_request = None
|
|
292
|
+
|
|
293
|
+
def _apply_settings(self) -> None:
|
|
294
|
+
settings = self.settings_repo.get().data
|
|
295
|
+
self.theme = settings.theme
|
|
296
|
+
for text_area in self.query(CustomTextArea):
|
|
297
|
+
text_area.theme = settings.theme
|
|
298
|
+
|
|
299
|
+
def _register_themes(self) -> None:
|
|
300
|
+
for theme in CUSTOM_THEMES.values():
|
|
301
|
+
self.register_theme(theme=theme['global'])
|
|
302
|
+
|
|
303
|
+
for text_area in self.query(CustomTextArea):
|
|
304
|
+
for theme in CUSTOM_THEMES.values():
|
|
305
|
+
text_area.register_theme(theme=theme['text_area'])
|
|
306
|
+
|
|
307
|
+
def _find_maximizable_area_by_widget(
|
|
308
|
+
self, widget: Widget
|
|
309
|
+
) -> Widget | None:
|
|
310
|
+
while widget is not None:
|
|
311
|
+
if (
|
|
312
|
+
isinstance(widget, CollectionsArea)
|
|
313
|
+
or isinstance(widget, URLArea)
|
|
314
|
+
or isinstance(widget, RequestArea)
|
|
315
|
+
or isinstance(widget, ResponseArea)
|
|
316
|
+
):
|
|
317
|
+
return widget
|
|
318
|
+
widget = widget.parent
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def selected_request(self) -> Request | None:
|
|
322
|
+
return self._selected_request
|
|
323
|
+
|
|
324
|
+
@selected_request.setter
|
|
325
|
+
def selected_request(self, request: Request | None) -> None:
|
|
326
|
+
if request is None:
|
|
327
|
+
self.url_area.clear()
|
|
328
|
+
self.request_area.clear()
|
|
329
|
+
self.response_area.clear()
|
|
330
|
+
self.url_area.disabled = True
|
|
331
|
+
self.request_area.disabled = True
|
|
332
|
+
self.response_area.disabled = True
|
|
333
|
+
self.response_area.is_showing_response = False
|
|
334
|
+
else:
|
|
335
|
+
self.url_area.disabled = False
|
|
336
|
+
self.request_area.disabled = False
|
|
337
|
+
self.response_area.disabled = False
|
|
338
|
+
|
|
339
|
+
self._selected_request = request
|
|
340
|
+
|
|
341
|
+
def get_request(self) -> Request:
|
|
342
|
+
method = self.url_area.method
|
|
343
|
+
url = self.url_area.url
|
|
344
|
+
|
|
345
|
+
headers = [
|
|
346
|
+
Request.Header(
|
|
347
|
+
enabled=header['enabled'],
|
|
348
|
+
key=header['key'],
|
|
349
|
+
value=header['value'],
|
|
350
|
+
)
|
|
351
|
+
for header in self.request_area.headers
|
|
352
|
+
]
|
|
353
|
+
|
|
354
|
+
params = [
|
|
355
|
+
Request.Param(
|
|
356
|
+
enabled=param['enabled'],
|
|
357
|
+
key=param['key'],
|
|
358
|
+
value=param['value'],
|
|
359
|
+
)
|
|
360
|
+
for param in self.request_area.params
|
|
361
|
+
]
|
|
362
|
+
|
|
363
|
+
auth_enabled = self.request_area.auth_enabled
|
|
364
|
+
auth_mode = self.request_area.auth_mode
|
|
365
|
+
auth = None
|
|
366
|
+
if auth_mode == AuthMode.BASIC:
|
|
367
|
+
auth = Request.BasicAuth(
|
|
368
|
+
username=self.request_area.auth_basic_username,
|
|
369
|
+
password=self.request_area.auth_basic_password,
|
|
370
|
+
)
|
|
371
|
+
elif auth_mode == AuthMode.BEARER:
|
|
372
|
+
auth = Request.BearerAuth(
|
|
373
|
+
token=self.request_area.auth_bearer_token
|
|
374
|
+
)
|
|
375
|
+
elif auth_mode == AuthMode.API_KEY:
|
|
376
|
+
auth = Request.ApiKeyAuth(
|
|
377
|
+
key=self.request_area.auth_api_key_key,
|
|
378
|
+
value=self.request_area.auth_api_key_value,
|
|
379
|
+
where=self.request_area.auth_api_key_where,
|
|
380
|
+
)
|
|
381
|
+
elif auth_mode == AuthMode.DIGEST:
|
|
382
|
+
auth = Request.DigestAuth(
|
|
383
|
+
username=self.request_area.auth_digest_username,
|
|
384
|
+
password=self.request_area.auth_digest_password,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
body_enabled = self.request_area.body_enabled
|
|
388
|
+
body_mode = self.request_area.body_mode
|
|
389
|
+
body = None
|
|
390
|
+
if body_mode == BodyMode.RAW:
|
|
391
|
+
body = Request.RawBody(
|
|
392
|
+
language=BodyRawLanguage(self.request_area.body_raw_language),
|
|
393
|
+
value=self.request_area.body_raw,
|
|
394
|
+
)
|
|
395
|
+
elif body_mode == BodyMode.FILE:
|
|
396
|
+
body = Request.FileBody(file=self.request_area.body_file)
|
|
397
|
+
elif body_mode == BodyMode.FORM_URLENCODED:
|
|
398
|
+
body = Request.UrlEncodedFormBody(
|
|
399
|
+
fields=[
|
|
400
|
+
Request.UrlEncodedFormBody.Field(
|
|
401
|
+
enabled=form_field['enabled'],
|
|
402
|
+
key=form_field['key'],
|
|
403
|
+
value=form_field['value'],
|
|
404
|
+
)
|
|
405
|
+
for form_field in self.request_area.body_form_urlencoded
|
|
406
|
+
]
|
|
407
|
+
)
|
|
408
|
+
elif body_mode == BodyMode.FORM_MULTIPART:
|
|
409
|
+
body = Request.MultipartFormBody(
|
|
410
|
+
fields=[
|
|
411
|
+
Request.MultipartFormBody.Field(
|
|
412
|
+
enabled=form_field['enabled'],
|
|
413
|
+
key=form_field['key'],
|
|
414
|
+
value=form_field['value'],
|
|
415
|
+
value_kind=form_field['value_kind'],
|
|
416
|
+
)
|
|
417
|
+
for form_field in self.request_area.body_form_multipart
|
|
418
|
+
]
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
options = Request.Options(
|
|
422
|
+
timeout=self.request_area.option_timeout,
|
|
423
|
+
follow_redirects=self.request_area.option_follow_redirects,
|
|
424
|
+
verify_ssl=self.request_area.option_verify_ssl,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
return Request(
|
|
428
|
+
id=self.selected_request.id,
|
|
429
|
+
folder_id=self.selected_request.folder_id,
|
|
430
|
+
name=self.selected_request.name,
|
|
431
|
+
method=method,
|
|
432
|
+
url=url,
|
|
433
|
+
headers=headers,
|
|
434
|
+
params=params,
|
|
435
|
+
body_enabled=body_enabled,
|
|
436
|
+
body_mode=body_mode,
|
|
437
|
+
body=body,
|
|
438
|
+
auth_enabled=auth_enabled,
|
|
439
|
+
auth_mode=auth_mode,
|
|
440
|
+
auth=auth,
|
|
441
|
+
options=options,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
def get_resolved_request(self) -> Request:
|
|
445
|
+
global_environment = self.environments_repo.get_by_name(
|
|
446
|
+
name='global'
|
|
447
|
+
).data
|
|
448
|
+
request = self.get_request().resolve_variables(
|
|
449
|
+
global_environment.variables
|
|
450
|
+
)
|
|
451
|
+
if self.top_bar_area.environment:
|
|
452
|
+
environment = self.environments_repo.get_by_name(
|
|
453
|
+
name=self.top_bar_area.environment
|
|
454
|
+
).data
|
|
455
|
+
request = request.resolve_variables(environment.variables)
|
|
456
|
+
return request
|
|
457
|
+
|
|
458
|
+
def set_request(self, request: Request) -> None:
|
|
459
|
+
self.url_area.method = request.method
|
|
460
|
+
self.url_area.url = request.url
|
|
461
|
+
|
|
462
|
+
self.request_area.headers = [
|
|
463
|
+
{
|
|
464
|
+
'enabled': header.enabled,
|
|
465
|
+
'key': header.key,
|
|
466
|
+
'value': header.value,
|
|
467
|
+
}
|
|
468
|
+
for header in request.headers
|
|
469
|
+
]
|
|
470
|
+
self.request_area.params = [
|
|
471
|
+
{'enabled': param.enabled, 'key': param.key, 'value': param.value}
|
|
472
|
+
for param in request.params
|
|
473
|
+
]
|
|
474
|
+
|
|
475
|
+
self.request_area.auth_enabled = request.auth_enabled
|
|
476
|
+
self.request_area.auth_mode = request.auth_mode
|
|
477
|
+
if request.auth is not None:
|
|
478
|
+
if request.auth_mode == AuthMode.BASIC:
|
|
479
|
+
self.request_area.auth_basic_username = request.auth.username
|
|
480
|
+
self.request_area.auth_basic_password = request.auth.password
|
|
481
|
+
elif request.auth_mode == AuthMode.BEARER:
|
|
482
|
+
self.request_area.auth_bearer_token = request.auth.token
|
|
483
|
+
elif request.auth_mode == AuthMode.API_KEY:
|
|
484
|
+
self.request_area.auth_api_key_key = request.auth.key
|
|
485
|
+
self.request_area.auth_api_key_value = request.auth.value
|
|
486
|
+
self.request_area.auth_api_key_where = request.auth.where
|
|
487
|
+
elif request.auth_mode == AuthMode.DIGEST:
|
|
488
|
+
self.request_area.auth_digest_username = request.auth.username
|
|
489
|
+
self.request_area.auth_digest_password = request.auth.password
|
|
490
|
+
|
|
491
|
+
self.request_area.body_enabled = request.body_enabled
|
|
492
|
+
self.request_area.body_mode = request.body_mode
|
|
493
|
+
if request.body is not None:
|
|
494
|
+
if request.body_mode == BodyMode.RAW:
|
|
495
|
+
self.request_area.body_raw_language = request.body.language
|
|
496
|
+
self.request_area.body_raw = request.body.value
|
|
497
|
+
elif request.body_mode == BodyMode.FILE:
|
|
498
|
+
self.request_area.body_file = request.body.file
|
|
499
|
+
elif request.body_mode == BodyMode.FORM_URLENCODED:
|
|
500
|
+
self.request_area.body_form_urlencoded = [
|
|
501
|
+
{
|
|
502
|
+
'enabled': form_field.enabled,
|
|
503
|
+
'key': form_field.key,
|
|
504
|
+
'value': form_field.value,
|
|
505
|
+
}
|
|
506
|
+
for form_field in request.body.fields
|
|
507
|
+
]
|
|
508
|
+
elif request.body_mode == BodyMode.FORM_MULTIPART:
|
|
509
|
+
self.request_area.body_form_multipart = [
|
|
510
|
+
{
|
|
511
|
+
'enabled': form_field.enabled,
|
|
512
|
+
'key': form_field.key,
|
|
513
|
+
'value': form_field.value,
|
|
514
|
+
'value_kind': form_field.value_kind,
|
|
515
|
+
}
|
|
516
|
+
for form_field in request.body.fields
|
|
517
|
+
]
|
|
518
|
+
|
|
519
|
+
self.request_area.option_follow_redirects = (
|
|
520
|
+
request.options.follow_redirects
|
|
521
|
+
)
|
|
522
|
+
self.request_area.option_verify_ssl = request.options.verify_ssl
|
|
523
|
+
self.request_area.option_timeout = str(request.options.timeout)
|
|
524
|
+
|
|
525
|
+
async def _send_request(self) -> None:
|
|
526
|
+
self.response_area.clear()
|
|
527
|
+
self.response_area.loading = True
|
|
528
|
+
self.url_area.request_pending = True
|
|
529
|
+
try:
|
|
530
|
+
request = self.get_resolved_request()
|
|
531
|
+
async with httpx.AsyncClient(
|
|
532
|
+
timeout=request.options.timeout,
|
|
533
|
+
follow_redirects=request.options.follow_redirects,
|
|
534
|
+
verify=request.options.verify_ssl,
|
|
535
|
+
) as http_client:
|
|
536
|
+
response = await http_client.send(
|
|
537
|
+
request=request.to_httpx_req(),
|
|
538
|
+
auth=request.to_httpx_auth(),
|
|
539
|
+
)
|
|
540
|
+
self._display_response(response=response)
|
|
541
|
+
self.response_area.is_showing_response = True
|
|
542
|
+
except httpx.RequestError as error:
|
|
543
|
+
error_name = type(error).__name__
|
|
544
|
+
error_message = str(error)
|
|
545
|
+
if error_message:
|
|
546
|
+
self.notify(f'{error_name}: {error_message}', severity='error')
|
|
547
|
+
else:
|
|
548
|
+
self.notify(f'{error_name}', severity='error')
|
|
549
|
+
self.response_area.clear()
|
|
550
|
+
self.response_area.is_showing_response = False
|
|
551
|
+
except asyncio.CancelledError:
|
|
552
|
+
self.response_area.clear()
|
|
553
|
+
self.response_area.is_showing_response = False
|
|
554
|
+
finally:
|
|
555
|
+
self.response_area.loading = False
|
|
556
|
+
self.url_area.request_pending = False
|
|
557
|
+
|
|
558
|
+
def _display_response(self, response: httpx.Response) -> None:
|
|
559
|
+
status = HTTPStatus(response.status_code)
|
|
560
|
+
size = response.num_bytes_downloaded
|
|
561
|
+
elapsed_time = round(response.elapsed.total_seconds(), 2)
|
|
562
|
+
headers = {
|
|
563
|
+
header_key: header_value
|
|
564
|
+
for header_key, header_value in response.headers.multi_items()
|
|
565
|
+
}
|
|
566
|
+
content_type_to_body_language = {
|
|
567
|
+
ContentType.TEXT: BodyRawLanguage.PLAIN,
|
|
568
|
+
ContentType.HTML: BodyRawLanguage.HTML,
|
|
569
|
+
ContentType.JSON: BodyRawLanguage.JSON,
|
|
570
|
+
ContentType.YAML: BodyRawLanguage.YAML,
|
|
571
|
+
ContentType.XML: BodyRawLanguage.XML,
|
|
572
|
+
}
|
|
573
|
+
body_raw_language = content_type_to_body_language.get(
|
|
574
|
+
response.headers.get('Content-Type'), BodyRawLanguage.PLAIN
|
|
575
|
+
)
|
|
576
|
+
body_raw = response.text
|
|
577
|
+
self.response_area.set_data(
|
|
578
|
+
data=ResponseAreaData(
|
|
579
|
+
status=status,
|
|
580
|
+
size=size,
|
|
581
|
+
elapsed_time=elapsed_time,
|
|
582
|
+
headers=headers,
|
|
583
|
+
body_raw_language=body_raw_language,
|
|
584
|
+
body_raw=body_raw,
|
|
585
|
+
)
|
|
586
|
+
)
|