restiny 0.2.0__tar.gz → 0.3.0__tar.gz
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-0.2.0 → restiny-0.3.0}/PKG-INFO +2 -2
- {restiny-0.2.0 → restiny-0.3.0}/README.md +1 -1
- restiny-0.3.0/restiny/__about__.py +1 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny/assets/style.tcss +13 -1
- restiny-0.3.0/restiny/core/app.py +423 -0
- restiny-0.3.0/restiny/core/request_area.py +507 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny/core/response_area.py +53 -20
- {restiny-0.2.0 → restiny-0.3.0}/restiny/core/url_area.py +28 -20
- {restiny-0.2.0 → restiny-0.3.0}/restiny/enums.py +7 -0
- restiny-0.3.0/restiny/httpx_auths.py +52 -0
- restiny-0.3.0/restiny/test.py +13 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny/utils.py +45 -15
- {restiny-0.2.0 → restiny-0.3.0}/restiny/widgets/__init__.py +2 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny/widgets/dynamic_fields.py +129 -103
- restiny-0.3.0/restiny/widgets/password_input.py +159 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny/widgets/path_chooser.py +49 -28
- {restiny-0.2.0 → restiny-0.3.0}/restiny.egg-info/PKG-INFO +2 -2
- {restiny-0.2.0 → restiny-0.3.0}/restiny.egg-info/SOURCES.txt +3 -2
- restiny-0.2.0/restiny/__about__.py +0 -1
- restiny-0.2.0/restiny/core/app.py +0 -342
- restiny-0.2.0/restiny/core/request_area.py +0 -338
- restiny-0.2.0/restiny/screens/__init__.py +0 -0
- restiny-0.2.0/restiny/screens/dialog.py +0 -109
- {restiny-0.2.0 → restiny-0.3.0}/LICENSE +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/pyproject.toml +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny/__init__.py +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny/__main__.py +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny/assets/__init__.py +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny/consts.py +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny/core/__init__.py +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny/widgets/custom_directory_tree.py +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny/widgets/custom_text_area.py +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny.egg-info/dependency_links.txt +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny.egg-info/entry_points.txt +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny.egg-info/requires.txt +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/restiny.egg-info/top_level.txt +0 -0
- {restiny-0.2.0 → restiny-0.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: restiny
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A minimalist HTTP client, no bullshit
|
|
5
5
|
Author-email: Kalebe Chimanski de Almeida <kalebe.chi.almeida@gmail.com>
|
|
6
6
|
License: Apache License
|
|
@@ -254,7 +254,7 @@ Dynamic: license-file
|
|
|
254
254
|
|
|
255
255
|
_A minimal, elegant HTTP client for Python — with a TUI interface powered by [Textual](https://github.com/Textualize/textual)._
|
|
256
256
|
|
|
257
|
-

|
|
258
258
|
|
|
259
259
|
## How to install
|
|
260
260
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
_A minimal, elegant HTTP client for Python — with a TUI interface powered by [Textual](https://github.com/Textualize/textual)._
|
|
17
17
|
|
|
18
|
-

|
|
19
19
|
|
|
20
20
|
## How to install
|
|
21
21
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.3.0'
|
|
@@ -3,6 +3,10 @@ Button {
|
|
|
3
3
|
max-width: 100%;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
Input {
|
|
7
|
+
width: 1fr;
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
.hidden {
|
|
7
11
|
display: none;
|
|
8
12
|
}
|
|
@@ -19,6 +23,10 @@ Button {
|
|
|
19
23
|
width: 2fr;
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
.w-3fr {
|
|
27
|
+
width: 3fr;
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
.h-auto {
|
|
23
31
|
height: auto;
|
|
24
32
|
}
|
|
@@ -28,7 +36,11 @@ Button {
|
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
.h-2fr {
|
|
31
|
-
|
|
39
|
+
height: 2fr;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.h-3fr {
|
|
43
|
+
height: 3fr;
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
.p-0 {
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import mimetypes
|
|
4
|
+
from http import HTTPStatus
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
import pyperclip
|
|
9
|
+
from textual import on
|
|
10
|
+
from textual.app import App, ComposeResult
|
|
11
|
+
from textual.binding import Binding
|
|
12
|
+
from textual.containers import Horizontal, Vertical
|
|
13
|
+
from textual.events import DescendantFocus
|
|
14
|
+
from textual.widget import Widget
|
|
15
|
+
from textual.widgets import Footer, Header
|
|
16
|
+
|
|
17
|
+
from restiny import httpx_auths
|
|
18
|
+
from restiny.__about__ import __version__
|
|
19
|
+
from restiny.assets import STYLE_TCSS
|
|
20
|
+
from restiny.core import (
|
|
21
|
+
RequestArea,
|
|
22
|
+
RequestAreaData,
|
|
23
|
+
ResponseArea,
|
|
24
|
+
URLArea,
|
|
25
|
+
URLAreaData,
|
|
26
|
+
)
|
|
27
|
+
from restiny.core.request_area import (
|
|
28
|
+
APIKeyAuth,
|
|
29
|
+
BasicAuth,
|
|
30
|
+
BearerAuth,
|
|
31
|
+
DigestAuth,
|
|
32
|
+
)
|
|
33
|
+
from restiny.core.response_area import ResponseAreaData
|
|
34
|
+
from restiny.enums import BodyMode, BodyRawLanguage, ContentType
|
|
35
|
+
from restiny.utils import build_curl_cmd
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RESTinyApp(App, inherit_bindings=False):
|
|
39
|
+
TITLE = f'RESTiny v{__version__}'
|
|
40
|
+
SUB_TITLE = 'Minimal HTTP client, no bullshit'
|
|
41
|
+
ENABLE_COMMAND_PALETTE = False
|
|
42
|
+
CSS_PATH = STYLE_TCSS
|
|
43
|
+
BINDINGS = [
|
|
44
|
+
Binding(
|
|
45
|
+
key='escape', action='quit', description='Quit the app', show=True
|
|
46
|
+
),
|
|
47
|
+
Binding(
|
|
48
|
+
key='f10',
|
|
49
|
+
action='maximize_or_minimize_area',
|
|
50
|
+
description='Maximize/Minimize area',
|
|
51
|
+
show=True,
|
|
52
|
+
),
|
|
53
|
+
Binding(
|
|
54
|
+
key='f9',
|
|
55
|
+
action='copy_as_curl',
|
|
56
|
+
description='Copy as curl',
|
|
57
|
+
show=True,
|
|
58
|
+
),
|
|
59
|
+
]
|
|
60
|
+
theme = 'textual-dark'
|
|
61
|
+
|
|
62
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
63
|
+
super().__init__(*args, **kwargs)
|
|
64
|
+
self.current_request: asyncio.Task | None = None
|
|
65
|
+
self.last_focused_widget: Widget | None = None
|
|
66
|
+
self.last_focused_maximizable_area: Widget | None = None
|
|
67
|
+
|
|
68
|
+
def compose(self) -> ComposeResult:
|
|
69
|
+
yield Header(show_clock=True)
|
|
70
|
+
with Vertical(id='main-content'):
|
|
71
|
+
with Horizontal(classes='h-auto'):
|
|
72
|
+
yield URLArea()
|
|
73
|
+
with Horizontal(classes='h-1fr'):
|
|
74
|
+
yield RequestArea()
|
|
75
|
+
yield ResponseArea()
|
|
76
|
+
yield Footer()
|
|
77
|
+
|
|
78
|
+
def on_mount(self) -> None:
|
|
79
|
+
self.url_area = self.query_one(URLArea)
|
|
80
|
+
self.request_area = self.query_one(RequestArea)
|
|
81
|
+
self.response_area = self.query_one(ResponseArea)
|
|
82
|
+
|
|
83
|
+
def action_maximize_or_minimize_area(self) -> None:
|
|
84
|
+
if self.screen.maximized:
|
|
85
|
+
self.screen.minimize()
|
|
86
|
+
else:
|
|
87
|
+
self.screen.maximize(self.last_focused_maximizable_area)
|
|
88
|
+
|
|
89
|
+
def action_copy_as_curl(self) -> None:
|
|
90
|
+
url_area_data = self.url_area.get_data()
|
|
91
|
+
request_area_data = self.request_area.get_data()
|
|
92
|
+
|
|
93
|
+
method = url_area_data.method
|
|
94
|
+
url = url_area_data.url
|
|
95
|
+
|
|
96
|
+
headers = {}
|
|
97
|
+
for header in request_area_data.headers:
|
|
98
|
+
if not header.enabled:
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
headers[header.key] = header.value
|
|
102
|
+
|
|
103
|
+
params = {}
|
|
104
|
+
for param in request_area_data.params:
|
|
105
|
+
if not param.enabled:
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
params[param.key] = param.value
|
|
109
|
+
|
|
110
|
+
body_raw = None
|
|
111
|
+
body_form_urlencoded = {}
|
|
112
|
+
body_form_multipart = {}
|
|
113
|
+
body_files = None
|
|
114
|
+
if request_area_data.body.enabled:
|
|
115
|
+
if request_area_data.body.mode == BodyMode.RAW:
|
|
116
|
+
body_raw = request_area_data.body.payload
|
|
117
|
+
elif request_area_data.body.mode == BodyMode.FORM_URLENCODED:
|
|
118
|
+
body_form_urlencoded = {
|
|
119
|
+
form_field.key: form_field.value
|
|
120
|
+
for form_field in request_area_data.body.payload
|
|
121
|
+
if form_field.enabled
|
|
122
|
+
}
|
|
123
|
+
elif request_area_data.body.mode == BodyMode.FORM_MULTIPART:
|
|
124
|
+
body_form_multipart = {
|
|
125
|
+
form_field.key: form_field.value
|
|
126
|
+
for form_field in request_area_data.body.payload
|
|
127
|
+
if form_field.enabled
|
|
128
|
+
}
|
|
129
|
+
elif request_area_data.body.mode == BodyMode.FILE:
|
|
130
|
+
body_files = [request_area_data.body.payload]
|
|
131
|
+
|
|
132
|
+
auth_basic = None
|
|
133
|
+
auth_bearer = None
|
|
134
|
+
auth_api_key_header = None
|
|
135
|
+
auth_api_key_param = None
|
|
136
|
+
auth_digest = None
|
|
137
|
+
if request_area_data.auth.enabled:
|
|
138
|
+
if isinstance(request_area_data.auth.value, BasicAuth):
|
|
139
|
+
auth_basic = (
|
|
140
|
+
request_area_data.auth.value.username,
|
|
141
|
+
request_area_data.auth.value.password,
|
|
142
|
+
)
|
|
143
|
+
elif isinstance(request_area_data.auth.value, BearerAuth):
|
|
144
|
+
auth_bearer = request_area_data.auth.value.token
|
|
145
|
+
elif isinstance(request_area_data.auth.value, APIKeyAuth):
|
|
146
|
+
if request_area_data.auth.value.where == 'header':
|
|
147
|
+
auth_api_key_header = (
|
|
148
|
+
request_area_data.auth.value.key,
|
|
149
|
+
request_area_data.auth.value.value,
|
|
150
|
+
)
|
|
151
|
+
elif request_area_data.auth.value.where == 'param':
|
|
152
|
+
auth_api_key_param = (
|
|
153
|
+
request_area_data.auth.value.key,
|
|
154
|
+
request_area_data.auth.value.value,
|
|
155
|
+
)
|
|
156
|
+
elif isinstance(request_area_data.auth.value, DigestAuth):
|
|
157
|
+
auth_digest = (
|
|
158
|
+
request_area_data.auth.value.username,
|
|
159
|
+
request_area_data.auth.value.password,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
curl_cmd = build_curl_cmd(
|
|
163
|
+
method=method,
|
|
164
|
+
url=url,
|
|
165
|
+
headers=headers,
|
|
166
|
+
params=params,
|
|
167
|
+
body_raw=body_raw,
|
|
168
|
+
body_form_urlencoded=body_form_urlencoded,
|
|
169
|
+
body_form_multipart=body_form_multipart,
|
|
170
|
+
body_files=body_files,
|
|
171
|
+
auth_basic=auth_basic,
|
|
172
|
+
auth_bearer=auth_bearer,
|
|
173
|
+
auth_api_key_header=auth_api_key_header,
|
|
174
|
+
auth_api_key_param=auth_api_key_param,
|
|
175
|
+
auth_digest=auth_digest,
|
|
176
|
+
)
|
|
177
|
+
self.copy_to_clipboard(curl_cmd)
|
|
178
|
+
self.notify(
|
|
179
|
+
'Command CURL copied to clipboard',
|
|
180
|
+
severity='information',
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def copy_to_clipboard(self, text: str) -> None:
|
|
184
|
+
super().copy_to_clipboard(text)
|
|
185
|
+
try:
|
|
186
|
+
# Also copy to the system clipboard (outside of the app)
|
|
187
|
+
pyperclip.copy(text)
|
|
188
|
+
except Exception:
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
@on(DescendantFocus)
|
|
192
|
+
def _on_focus(self, event: DescendantFocus) -> None:
|
|
193
|
+
self.last_focused_widget = event.widget
|
|
194
|
+
last_focused_maximizable_area = self._find_maximizable_area_by_widget(
|
|
195
|
+
widget=event.widget
|
|
196
|
+
)
|
|
197
|
+
if last_focused_maximizable_area:
|
|
198
|
+
self.last_focused_maximizable_area = last_focused_maximizable_area
|
|
199
|
+
|
|
200
|
+
@on(URLArea.SendRequest)
|
|
201
|
+
def _on_send_request(self, message: URLArea.SendRequest) -> None:
|
|
202
|
+
self.current_request = asyncio.create_task(self._send_request())
|
|
203
|
+
|
|
204
|
+
@on(URLArea.CancelRequest)
|
|
205
|
+
def _on_cancel_request(self, message: URLArea.CancelRequest) -> None:
|
|
206
|
+
if self.current_request and not self.current_request.done():
|
|
207
|
+
self.current_request.cancel()
|
|
208
|
+
|
|
209
|
+
def _find_maximizable_area_by_widget(
|
|
210
|
+
self, widget: Widget
|
|
211
|
+
) -> Widget | None:
|
|
212
|
+
while widget is not None:
|
|
213
|
+
if (
|
|
214
|
+
isinstance(widget, URLArea)
|
|
215
|
+
or isinstance(widget, RequestArea)
|
|
216
|
+
or isinstance(widget, ResponseArea)
|
|
217
|
+
):
|
|
218
|
+
return widget
|
|
219
|
+
widget = widget.parent
|
|
220
|
+
|
|
221
|
+
async def _send_request(self) -> None:
|
|
222
|
+
url_area_data = self.url_area.get_data()
|
|
223
|
+
request_area_data = self.request_area.get_data()
|
|
224
|
+
|
|
225
|
+
self.response_area.set_data(data=None)
|
|
226
|
+
self.response_area.loading = True
|
|
227
|
+
self.url_area.request_pending = True
|
|
228
|
+
try:
|
|
229
|
+
async with httpx.AsyncClient(
|
|
230
|
+
timeout=request_area_data.options.timeout,
|
|
231
|
+
follow_redirects=request_area_data.options.follow_redirects,
|
|
232
|
+
verify=request_area_data.options.verify_ssl,
|
|
233
|
+
) as http_client:
|
|
234
|
+
request = self._build_request(
|
|
235
|
+
http_client=http_client,
|
|
236
|
+
url_area_data=url_area_data,
|
|
237
|
+
request_area_data=request_area_data,
|
|
238
|
+
)
|
|
239
|
+
auth = self._build_auth(
|
|
240
|
+
request_area_data=request_area_data,
|
|
241
|
+
)
|
|
242
|
+
response = await http_client.send(request=request, auth=auth)
|
|
243
|
+
self._display_response(response=response)
|
|
244
|
+
self.response_area.is_showing_response = True
|
|
245
|
+
except httpx.RequestError as error:
|
|
246
|
+
error_name = type(error).__name__
|
|
247
|
+
error_message = str(error)
|
|
248
|
+
if error_message:
|
|
249
|
+
self.notify(f'{error_name}: {error_message}', severity='error')
|
|
250
|
+
else:
|
|
251
|
+
self.notify(f'{error_name}', severity='error')
|
|
252
|
+
self.response_area.set_data(data=None)
|
|
253
|
+
self.response_area.is_showing_response = False
|
|
254
|
+
except asyncio.CancelledError:
|
|
255
|
+
self.response_area.set_data(data=None)
|
|
256
|
+
self.response_area.is_showing_response = False
|
|
257
|
+
finally:
|
|
258
|
+
self.response_area.loading = False
|
|
259
|
+
self.url_area.request_pending = False
|
|
260
|
+
|
|
261
|
+
def _build_request(
|
|
262
|
+
self,
|
|
263
|
+
http_client: httpx.AsyncClient,
|
|
264
|
+
url_area_data: URLAreaData,
|
|
265
|
+
request_area_data: RequestAreaData,
|
|
266
|
+
) -> tuple[httpx.Request, httpx.Auth | None]:
|
|
267
|
+
headers: dict[str, str] = {
|
|
268
|
+
header.key: header.value
|
|
269
|
+
for header in request_area_data.headers
|
|
270
|
+
if header.enabled
|
|
271
|
+
}
|
|
272
|
+
params: dict[str, str] = {
|
|
273
|
+
param.key: param.value
|
|
274
|
+
for param in request_area_data.params
|
|
275
|
+
if param.enabled
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if not request_area_data.body.enabled:
|
|
279
|
+
return http_client.build_request(
|
|
280
|
+
method=url_area_data.method,
|
|
281
|
+
url=url_area_data.url,
|
|
282
|
+
headers=headers,
|
|
283
|
+
params=params,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if request_area_data.body.mode == BodyMode.RAW:
|
|
287
|
+
raw_language_to_content_type = {
|
|
288
|
+
BodyRawLanguage.JSON: ContentType.JSON,
|
|
289
|
+
BodyRawLanguage.YAML: ContentType.YAML,
|
|
290
|
+
BodyRawLanguage.HTML: ContentType.HTML,
|
|
291
|
+
BodyRawLanguage.XML: ContentType.XML,
|
|
292
|
+
BodyRawLanguage.PLAIN: ContentType.TEXT,
|
|
293
|
+
}
|
|
294
|
+
headers['content-type'] = raw_language_to_content_type.get(
|
|
295
|
+
request_area_data.body.raw_language, ContentType.TEXT
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
raw = request_area_data.body.payload
|
|
299
|
+
if headers['content-type'] == ContentType.JSON:
|
|
300
|
+
try:
|
|
301
|
+
raw = json.dumps(raw)
|
|
302
|
+
except Exception:
|
|
303
|
+
pass
|
|
304
|
+
|
|
305
|
+
return http_client.build_request(
|
|
306
|
+
method=url_area_data.method,
|
|
307
|
+
url=url_area_data.url,
|
|
308
|
+
headers=headers,
|
|
309
|
+
params=params,
|
|
310
|
+
content=raw,
|
|
311
|
+
)
|
|
312
|
+
elif request_area_data.body.mode == BodyMode.FILE:
|
|
313
|
+
file = request_area_data.body.payload
|
|
314
|
+
if 'content-type' not in headers:
|
|
315
|
+
headers['content-type'] = (
|
|
316
|
+
mimetypes.guess_type(file.name)[0]
|
|
317
|
+
or 'application/octet-stream'
|
|
318
|
+
)
|
|
319
|
+
return http_client.build_request(
|
|
320
|
+
method=url_area_data.method,
|
|
321
|
+
url=url_area_data.url,
|
|
322
|
+
headers=headers,
|
|
323
|
+
params=params,
|
|
324
|
+
content=file.read_bytes(),
|
|
325
|
+
)
|
|
326
|
+
elif request_area_data.body.mode == BodyMode.FORM_URLENCODED:
|
|
327
|
+
form_urlencoded = {
|
|
328
|
+
form_item.key: form_item.value
|
|
329
|
+
for form_item in request_area_data.body.payload
|
|
330
|
+
if form_item.enabled
|
|
331
|
+
}
|
|
332
|
+
return http_client.build_request(
|
|
333
|
+
method=url_area_data.method,
|
|
334
|
+
url=url_area_data.url,
|
|
335
|
+
headers=headers,
|
|
336
|
+
params=params,
|
|
337
|
+
data=form_urlencoded,
|
|
338
|
+
)
|
|
339
|
+
elif request_area_data.body.mode == BodyMode.FORM_MULTIPART:
|
|
340
|
+
form_multipart_str = {
|
|
341
|
+
form_item.key: form_item.value
|
|
342
|
+
for form_item in request_area_data.body.payload
|
|
343
|
+
if form_item.enabled and isinstance(form_item.value, str)
|
|
344
|
+
}
|
|
345
|
+
form_multipart_files = {
|
|
346
|
+
form_item.key: (
|
|
347
|
+
form_item.value.name,
|
|
348
|
+
form_item.value.read_bytes(),
|
|
349
|
+
mimetypes.guess_type(form_item.value.name)[0]
|
|
350
|
+
or 'application/octet-stream',
|
|
351
|
+
)
|
|
352
|
+
for form_item in request_area_data.body.payload
|
|
353
|
+
if form_item.enabled and isinstance(form_item.value, Path)
|
|
354
|
+
}
|
|
355
|
+
return http_client.build_request(
|
|
356
|
+
method=url_area_data.method,
|
|
357
|
+
url=url_area_data.url,
|
|
358
|
+
headers=headers,
|
|
359
|
+
params=params,
|
|
360
|
+
data=form_multipart_str,
|
|
361
|
+
files=form_multipart_files,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def _build_auth(
|
|
365
|
+
self,
|
|
366
|
+
request_area_data: RequestAreaData,
|
|
367
|
+
) -> httpx.Auth | None:
|
|
368
|
+
auth = request_area_data.auth
|
|
369
|
+
|
|
370
|
+
if not auth.enabled:
|
|
371
|
+
return
|
|
372
|
+
|
|
373
|
+
if isinstance(auth.value, BasicAuth):
|
|
374
|
+
return httpx.BasicAuth(
|
|
375
|
+
username=auth.value.username,
|
|
376
|
+
password=auth.value.password,
|
|
377
|
+
)
|
|
378
|
+
elif isinstance(auth.value, BearerAuth):
|
|
379
|
+
return httpx_auths.BearerAuth(token=auth.value.token)
|
|
380
|
+
elif isinstance(auth.value, APIKeyAuth):
|
|
381
|
+
if auth.value.where == 'header':
|
|
382
|
+
return httpx_auths.APIKeyHeaderAuth(
|
|
383
|
+
key=auth.value.key, value=auth.value.value
|
|
384
|
+
)
|
|
385
|
+
elif auth.value.where == 'param':
|
|
386
|
+
return httpx_auths.APIKeyParamAuth(
|
|
387
|
+
key=auth.value.key, value=auth.value.value
|
|
388
|
+
)
|
|
389
|
+
elif isinstance(auth.value, DigestAuth):
|
|
390
|
+
return httpx.DigestAuth(
|
|
391
|
+
username=auth.value.username,
|
|
392
|
+
password=auth.value.password,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
def _display_response(self, response: httpx.Response) -> None:
|
|
396
|
+
status = HTTPStatus(response.status_code)
|
|
397
|
+
size = response.num_bytes_downloaded
|
|
398
|
+
elapsed_time = round(response.elapsed.total_seconds(), 2)
|
|
399
|
+
headers = {
|
|
400
|
+
header_key: header_value
|
|
401
|
+
for header_key, header_value in response.headers.multi_items()
|
|
402
|
+
}
|
|
403
|
+
content_type_to_body_language = {
|
|
404
|
+
ContentType.TEXT: BodyRawLanguage.PLAIN,
|
|
405
|
+
ContentType.HTML: BodyRawLanguage.HTML,
|
|
406
|
+
ContentType.JSON: BodyRawLanguage.JSON,
|
|
407
|
+
ContentType.YAML: BodyRawLanguage.YAML,
|
|
408
|
+
ContentType.XML: BodyRawLanguage.XML,
|
|
409
|
+
}
|
|
410
|
+
body_raw_language = content_type_to_body_language.get(
|
|
411
|
+
response.headers.get('Content-Type'), BodyRawLanguage.PLAIN
|
|
412
|
+
)
|
|
413
|
+
body_raw = response.text
|
|
414
|
+
self.response_area.set_data(
|
|
415
|
+
data=ResponseAreaData(
|
|
416
|
+
status=status,
|
|
417
|
+
size=size,
|
|
418
|
+
elapsed_time=elapsed_time,
|
|
419
|
+
headers=headers,
|
|
420
|
+
body_raw_language=body_raw_language,
|
|
421
|
+
body_raw=body_raw,
|
|
422
|
+
)
|
|
423
|
+
)
|