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