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.
Files changed (38) hide show
  1. restiny/__about__.py +1 -1
  2. restiny/__main__.py +28 -14
  3. restiny/assets/style.tcss +56 -2
  4. restiny/consts.py +236 -0
  5. restiny/data/db.py +60 -0
  6. restiny/data/models.py +111 -0
  7. restiny/data/repos.py +455 -0
  8. restiny/data/sql/__init__.py +3 -0
  9. restiny/entities.py +438 -0
  10. restiny/enums.py +14 -5
  11. restiny/httpx_auths.py +52 -0
  12. restiny/ui/__init__.py +17 -0
  13. restiny/ui/app.py +586 -0
  14. restiny/ui/collections_area.py +594 -0
  15. restiny/ui/environments_screen.py +270 -0
  16. restiny/ui/request_area.py +602 -0
  17. restiny/{core → ui}/response_area.py +4 -1
  18. restiny/ui/settings_screen.py +73 -0
  19. restiny/ui/top_bar_area.py +60 -0
  20. restiny/{core → ui}/url_area.py +54 -38
  21. restiny/utils.py +52 -15
  22. restiny/widgets/__init__.py +15 -1
  23. restiny/widgets/collections_tree.py +74 -0
  24. restiny/widgets/confirm_prompt.py +76 -0
  25. restiny/widgets/custom_input.py +20 -0
  26. restiny/widgets/dynamic_fields.py +65 -70
  27. restiny/widgets/password_input.py +161 -0
  28. restiny/widgets/path_chooser.py +12 -12
  29. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/METADATA +7 -5
  30. restiny-0.6.1.dist-info/RECORD +38 -0
  31. restiny/core/__init__.py +0 -15
  32. restiny/core/app.py +0 -348
  33. restiny/core/request_area.py +0 -337
  34. restiny-0.2.1.dist-info/RECORD +0 -24
  35. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/WHEEL +0 -0
  36. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/entry_points.txt +0 -0
  37. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/licenses/LICENSE +0 -0
  38. {restiny-0.2.1.dist-info → restiny-0.6.1.dist-info}/top_level.txt +0 -0
restiny/core/app.py DELETED
@@ -1,348 +0,0 @@
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.__about__ import __version__
18
- from restiny.assets import STYLE_TCSS
19
- from restiny.core import (
20
- RequestArea,
21
- RequestAreaData,
22
- ResponseArea,
23
- URLArea,
24
- URLAreaData,
25
- )
26
- from restiny.core.response_area import ResponseAreaData
27
- from restiny.enums import BodyMode, BodyRawLanguage, ContentType
28
- from restiny.utils import build_curl_cmd
29
-
30
-
31
- class RESTinyApp(App, inherit_bindings=False):
32
- TITLE = f'RESTiny v{__version__}'
33
- SUB_TITLE = 'Minimal HTTP client, no bullshit'
34
- ENABLE_COMMAND_PALETTE = False
35
- CSS_PATH = STYLE_TCSS
36
- BINDINGS = [
37
- Binding(
38
- key='escape', action='quit', description='Quit the app', show=True
39
- ),
40
- Binding(
41
- key='f10',
42
- action='maximize_or_minimize_area',
43
- description='Maximize/Minimize area',
44
- show=True,
45
- ),
46
- Binding(
47
- key='f9',
48
- action='copy_as_curl',
49
- description='Copy as curl',
50
- show=True,
51
- ),
52
- ]
53
- theme = 'textual-dark'
54
-
55
- def __init__(self, *args, **kwargs) -> None:
56
- super().__init__(*args, **kwargs)
57
- self.current_request: asyncio.Task | None = None
58
- self.last_focused_widget: Widget | None = None
59
- self.last_focused_maximizable_area: Widget | None = None
60
-
61
- def compose(self) -> ComposeResult:
62
- yield Header(show_clock=True)
63
- with Vertical(id='main-content'):
64
- with Horizontal(classes='h-auto'):
65
- yield URLArea()
66
- with Horizontal(classes='h-1fr'):
67
- with Vertical():
68
- yield RequestArea()
69
- with Vertical():
70
- yield ResponseArea()
71
- yield Footer()
72
-
73
- def on_mount(self) -> None:
74
- self.url_area = self.query_one(URLArea)
75
- self.request_area = self.query_one(RequestArea)
76
- self.response_area = self.query_one(ResponseArea)
77
-
78
- def action_maximize_or_minimize_area(self) -> None:
79
- if self.screen.maximized:
80
- self.screen.minimize()
81
- else:
82
- self.screen.maximize(self.last_focused_maximizable_area)
83
-
84
- def action_copy_as_curl(self) -> None:
85
- url_area_data = self.url_area.get_data()
86
- request_area_data = self.request_area.get_data()
87
-
88
- method = url_area_data.method
89
- url = url_area_data.url
90
-
91
- headers = {}
92
- for header in request_area_data.headers:
93
- if not header.enabled:
94
- continue
95
-
96
- headers[header.key] = header.value
97
-
98
- params = {}
99
- for param in request_area_data.query_params:
100
- if not param.enabled:
101
- continue
102
-
103
- params[param.key] = param.value
104
-
105
- raw_body = None
106
- form_urlencoded = {}
107
- form_multipart = {}
108
- files = None
109
- if request_area_data.body.type == BodyMode.RAW:
110
- raw_body = request_area_data.body.payload
111
- elif request_area_data.body.type == BodyMode.FORM_URLENCODED:
112
- form_urlencoded = {
113
- form_field.key: form_field.value
114
- for form_field in request_area_data.body.payload
115
- if form_field.enabled
116
- }
117
- elif request_area_data.body.type == BodyMode.FORM_MULTIPART:
118
- form_multipart = {
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.type == BodyMode.FILE:
124
- files = [request_area_data.body.payload]
125
-
126
- curl_cmd = build_curl_cmd(
127
- method=method,
128
- url=url,
129
- headers=headers,
130
- params=params,
131
- raw_body=raw_body,
132
- form_urlencoded=form_urlencoded,
133
- form_multipart=form_multipart,
134
- files=files,
135
- )
136
- self.copy_to_clipboard(curl_cmd)
137
- self.notify(
138
- 'Command CURL copied to clipboard',
139
- severity='information',
140
- )
141
-
142
- def copy_to_clipboard(self, text: str) -> None:
143
- super().copy_to_clipboard(text)
144
- try:
145
- # Also copy to the system clipboard (outside of the app)
146
- pyperclip.copy(text)
147
- except Exception:
148
- pass
149
-
150
- @on(DescendantFocus)
151
- def _on_focus(self, event: DescendantFocus) -> None:
152
- self.last_focused_widget = event.widget
153
- last_focused_maximizable_area = self._find_maximizable_area_by_widget(
154
- widget=event.widget
155
- )
156
- if last_focused_maximizable_area:
157
- self.last_focused_maximizable_area = last_focused_maximizable_area
158
-
159
- @on(URLArea.SendRequest)
160
- def _on_send_request(self, message: URLArea.SendRequest) -> None:
161
- self.current_request = asyncio.create_task(self._send_request())
162
-
163
- @on(URLArea.CancelRequest)
164
- def _on_cancel_request(self, message: URLArea.CancelRequest) -> None:
165
- if self.current_request and not self.current_request.done():
166
- self.current_request.cancel()
167
-
168
- def _find_maximizable_area_by_widget(
169
- self, widget: Widget
170
- ) -> Widget | None:
171
- while widget is not None:
172
- if (
173
- isinstance(widget, URLArea)
174
- or isinstance(widget, RequestArea)
175
- or isinstance(widget, ResponseArea)
176
- ):
177
- return widget
178
- widget = widget.parent
179
-
180
- async def _send_request(self) -> None:
181
- url_area_data = self.url_area.get_data()
182
- request_area_data = self.request_area.get_data()
183
-
184
- self.response_area.set_data(data=None)
185
- self.response_area.loading = True
186
- self.url_area.request_pending = True
187
- try:
188
- async with httpx.AsyncClient(
189
- timeout=request_area_data.options.timeout,
190
- follow_redirects=request_area_data.options.follow_redirects,
191
- verify=request_area_data.options.verify_ssl,
192
- ) as http_client:
193
- request = self._build_request(
194
- http_client=http_client,
195
- url_area_data=url_area_data,
196
- request_area_data=request_area_data,
197
- )
198
- response = await http_client.send(request=request)
199
- self._display_response(response=response)
200
- self.response_area.is_showing_response = True
201
- except httpx.RequestError as error:
202
- error_name = type(error).__name__
203
- error_message = str(error)
204
- if error_message:
205
- self.notify(f'{error_name}: {error_message}', severity='error')
206
- else:
207
- self.notify(f'{error_name}', severity='error')
208
- self.response_area.set_data(data=None)
209
- self.response_area.is_showing_response = False
210
- except asyncio.CancelledError:
211
- self.response_area.set_data(data=None)
212
- self.response_area.is_showing_response = False
213
- finally:
214
- self.response_area.loading = False
215
- self.url_area.request_pending = False
216
-
217
- def _build_request(
218
- self,
219
- http_client: httpx.Client,
220
- url_area_data: URLAreaData,
221
- request_area_data: RequestAreaData,
222
- ) -> httpx.Request:
223
- headers: dict[str, str] = {
224
- header.key: header.value
225
- for header in request_area_data.headers
226
- if header.enabled
227
- }
228
- query_params: dict[str, str] = {
229
- param.key: param.value
230
- for param in request_area_data.query_params
231
- if param.enabled
232
- }
233
-
234
- if not request_area_data.body.enabled:
235
- return http_client.build_request(
236
- method=url_area_data.method,
237
- url=url_area_data.url,
238
- headers=headers,
239
- params=query_params,
240
- )
241
-
242
- if request_area_data.body.type == BodyMode.RAW:
243
- raw_language_to_content_type = {
244
- BodyRawLanguage.JSON: ContentType.JSON,
245
- BodyRawLanguage.YAML: ContentType.YAML,
246
- BodyRawLanguage.HTML: ContentType.HTML,
247
- BodyRawLanguage.XML: ContentType.XML,
248
- BodyRawLanguage.PLAIN: ContentType.TEXT,
249
- }
250
- headers['content-type'] = raw_language_to_content_type.get(
251
- request_area_data.body.raw_language, ContentType.TEXT
252
- )
253
-
254
- raw = request_area_data.body.payload
255
- if headers['content-type'] == ContentType.JSON:
256
- try:
257
- raw = json.dumps(raw)
258
- except Exception:
259
- pass
260
-
261
- return http_client.build_request(
262
- method=url_area_data.method,
263
- url=url_area_data.url,
264
- headers=headers,
265
- params=query_params,
266
- content=raw,
267
- )
268
- elif request_area_data.body.type == BodyMode.FILE:
269
- file = request_area_data.body.payload
270
- if 'content-type' not in headers:
271
- headers['content-type'] = (
272
- mimetypes.guess_type(file.name)[0]
273
- or 'application/octet-stream'
274
- )
275
- return http_client.build_request(
276
- method=url_area_data.method,
277
- url=url_area_data.url,
278
- headers=headers,
279
- params=query_params,
280
- content=file.read_bytes(),
281
- )
282
- elif request_area_data.body.type == BodyMode.FORM_URLENCODED:
283
- form_urlencoded = {
284
- form_item.key: form_item.value
285
- for form_item in request_area_data.body.payload
286
- if form_item.enabled
287
- }
288
- return http_client.build_request(
289
- method=url_area_data.method,
290
- url=url_area_data.url,
291
- headers=headers,
292
- params=query_params,
293
- data=form_urlencoded,
294
- )
295
- elif request_area_data.body.type == BodyMode.FORM_MULTIPART:
296
- form_multipart_str = {
297
- form_item.key: form_item.value
298
- for form_item in request_area_data.body.payload
299
- if form_item.enabled and isinstance(form_item.value, str)
300
- }
301
- form_multipart_files = {
302
- form_item.key: (
303
- form_item.value.name,
304
- form_item.value.read_bytes(),
305
- mimetypes.guess_type(form_item.value.name)[0]
306
- or 'application/octet-stream',
307
- )
308
- for form_item in request_area_data.body.payload
309
- if form_item.enabled and isinstance(form_item.value, Path)
310
- }
311
- return http_client.build_request(
312
- method=url_area_data.method,
313
- url=url_area_data.url,
314
- headers=headers,
315
- params=query_params,
316
- data=form_multipart_str,
317
- files=form_multipart_files,
318
- )
319
-
320
- def _display_response(self, response: httpx.Response) -> None:
321
- status = HTTPStatus(response.status_code)
322
- size = response.num_bytes_downloaded
323
- elapsed_time = round(response.elapsed.total_seconds(), 2)
324
- headers = {
325
- header_key: header_value
326
- for header_key, header_value in response.headers.multi_items()
327
- }
328
- content_type_to_body_language = {
329
- ContentType.TEXT: BodyRawLanguage.PLAIN,
330
- ContentType.HTML: BodyRawLanguage.HTML,
331
- ContentType.JSON: BodyRawLanguage.JSON,
332
- ContentType.YAML: BodyRawLanguage.YAML,
333
- ContentType.XML: BodyRawLanguage.XML,
334
- }
335
- body_raw_language = content_type_to_body_language.get(
336
- response.headers.get('Content-Type'), BodyRawLanguage.PLAIN
337
- )
338
- body_raw = response.text
339
- self.response_area.set_data(
340
- data=ResponseAreaData(
341
- status=status,
342
- size=size,
343
- elapsed_time=elapsed_time,
344
- headers=headers,
345
- body_raw_language=body_raw_language,
346
- body_raw=body_raw,
347
- )
348
- )
@@ -1,337 +0,0 @@
1
- from dataclasses import dataclass
2
- from pathlib import Path
3
-
4
- from textual import on
5
- from textual.app import ComposeResult
6
- from textual.containers import Container, Horizontal
7
- from textual.widgets import (
8
- ContentSwitcher,
9
- Input,
10
- Label,
11
- Select,
12
- Static,
13
- Switch,
14
- TabbedContent,
15
- TabPane,
16
- )
17
-
18
- from restiny.enums import BodyMode, BodyRawLanguage
19
- from restiny.widgets import (
20
- CustomTextArea,
21
- DynamicFields,
22
- PathChooser,
23
- TextDynamicField,
24
- )
25
- from restiny.widgets.dynamic_fields import TextOrFileDynamicField
26
-
27
-
28
- @dataclass
29
- class HeaderField:
30
- enabled: bool
31
- key: str
32
- value: str
33
-
34
-
35
- @dataclass
36
- class QueryParamField:
37
- enabled: bool
38
- key: str
39
- value: str
40
-
41
-
42
- @dataclass
43
- class FormUrlEncodedField:
44
- enabled: bool
45
- key: str
46
- value: str
47
-
48
-
49
- @dataclass
50
- class FormMultipartField:
51
- enabled: bool
52
- key: str
53
- value: str | Path
54
-
55
-
56
- @dataclass
57
- class RequestAreaData:
58
- @dataclass
59
- class Options:
60
- timeout: int | float | None
61
- follow_redirects: bool
62
- verify_ssl: bool
63
-
64
- @dataclass
65
- class Body:
66
- enabled: bool
67
- raw_language: BodyRawLanguage | None
68
- type: BodyMode
69
- payload: (
70
- str
71
- | Path
72
- | list[FormUrlEncodedField]
73
- | list[FormMultipartField]
74
- | None
75
- )
76
-
77
- headers: list[HeaderField]
78
- query_params: list[QueryParamField]
79
- body: Body
80
- options: Options
81
-
82
-
83
- class RequestArea(Static):
84
- ALLOW_MAXIMIZE = True
85
- focusable = True
86
- BORDER_TITLE = 'Request'
87
- DEFAULT_CSS = """
88
- RequestArea {
89
- width: 1fr;
90
- height: 1fr;
91
- border: heavy black;
92
- border-title-color: gray;
93
- padding: 1;
94
- }
95
- """
96
-
97
- def compose(self) -> ComposeResult:
98
- with TabbedContent():
99
- with TabPane('Headers'):
100
- yield DynamicFields(
101
- fields=[TextDynamicField(enabled=False, key='', value='')],
102
- id='headers',
103
- )
104
- with TabPane('Query params'):
105
- yield DynamicFields(
106
- fields=[TextDynamicField(enabled=False, key='', value='')],
107
- id='params',
108
- )
109
- with TabPane('Body'):
110
- with Horizontal(classes='h-auto'):
111
- yield Switch(id='body-enabled', tooltip='Send body?')
112
- yield Select(
113
- (
114
- ('Raw', BodyMode.RAW),
115
- ('File', BodyMode.FILE),
116
- ('Form (urlencoded)', BodyMode.FORM_URLENCODED),
117
- ('Form (multipart)', BodyMode.FORM_MULTIPART),
118
- ),
119
- allow_blank=False,
120
- tooltip='Body type',
121
- id='body-mode',
122
- )
123
- with ContentSwitcher(
124
- id='body-mode-switcher',
125
- initial='body-mode-raw',
126
- classes='h-1fr',
127
- ):
128
- with Container(id='body-mode-raw', classes='pt-1'):
129
- yield Select(
130
- (
131
- ('Plain', BodyRawLanguage.PLAIN),
132
- ('JSON', BodyRawLanguage.JSON),
133
- ('YAML', BodyRawLanguage.YAML),
134
- ('XML', BodyRawLanguage.XML),
135
- ('HTML', BodyRawLanguage.HTML),
136
- ),
137
- allow_blank=False,
138
- tooltip='Text type',
139
- id='body-raw-language',
140
- )
141
- yield CustomTextArea.code_editor(
142
- language='json', id='body-raw', classes='mt-1'
143
- )
144
- with Horizontal(
145
- id='body-mode-file', classes='h-auto mt-1'
146
- ):
147
- yield PathChooser.file(id='body-file')
148
- with Horizontal(
149
- id='body-mode-form-urlencoded', classes='h-auto mt-1'
150
- ):
151
- yield DynamicFields(
152
- [
153
- TextDynamicField(
154
- enabled=False, key='', value=''
155
- )
156
- ],
157
- id='body-form-urlencoded',
158
- )
159
- with Horizontal(
160
- id='body-mode-form-multipart', classes='h-auto mt-1'
161
- ):
162
- yield DynamicFields(
163
- [
164
- TextOrFileDynamicField(
165
- enabled=False, key='', value=''
166
- )
167
- ],
168
- id='body-form-multipart',
169
- )
170
-
171
- with TabPane('Options'):
172
- with Horizontal(classes='h-auto'):
173
- yield Label('Timeout', classes='pt-1 ml-1')
174
- yield Input(
175
- '5.5',
176
- placeholder='5.5',
177
- select_on_focus=False,
178
- id='options-timeout',
179
- classes='w-1fr',
180
- )
181
- with Horizontal(classes='mt-1 h-auto'):
182
- yield Switch(id='options-follow-redirects')
183
- yield Label('Follow redirects', classes='pt-1')
184
- with Horizontal(classes='h-auto'):
185
- yield Switch(id='options-verify-ssl')
186
- yield Label('Verify SSL', classes='pt-1')
187
-
188
- def on_mount(self) -> None:
189
- self.header_fields = self.query_one('#headers', DynamicFields)
190
-
191
- self.param_fields = self.query_one('#params', DynamicFields)
192
-
193
- self.body_enabled_switch = self.query_one('#body-enabled', Switch)
194
- self.body_mode_select = self.query_one('#body-mode', Select)
195
- self.body_mode_switcher = self.query_one(
196
- '#body-mode-switcher', ContentSwitcher
197
- )
198
- self.body_raw_editor = self.query_one('#body-raw', CustomTextArea)
199
- self.body_raw_language_select = self.query_one(
200
- '#body-raw-language', Select
201
- )
202
- self.body_file_path_chooser = self.query_one('#body-file', PathChooser)
203
- self.body_form_urlencoded_fields = self.query_one(
204
- '#body-form-urlencoded', DynamicFields
205
- )
206
- self.body_form_multipart_fields = self.query_one(
207
- '#body-form-multipart', DynamicFields
208
- )
209
-
210
- self.options_timeout_input = self.query_one('#options-timeout', Input)
211
- self.options_follow_redirects_switch = self.query_one(
212
- '#options-follow-redirects', Switch
213
- )
214
- self.options_verify_ssl_switch = self.query_one(
215
- '#options-verify-ssl', Switch
216
- )
217
-
218
- def get_data(self) -> RequestAreaData:
219
- return RequestAreaData(
220
- headers=self._get_headers(),
221
- query_params=self._get_query_params(),
222
- body=self._get_body(),
223
- options=self._get_options(),
224
- )
225
-
226
- @on(Select.Changed, '#body-mode')
227
- def _on_change_body_type(self, message: Select.Changed) -> None:
228
- if message.value == BodyMode.FILE:
229
- self.body_mode_switcher.current = 'body-mode-file'
230
- elif message.value == BodyMode.RAW:
231
- self.body_mode_switcher.current = 'body-mode-raw'
232
- elif message.value == BodyMode.FORM_URLENCODED:
233
- self.body_mode_switcher.current = 'body-mode-form-urlencoded'
234
- elif message.value == BodyMode.FORM_MULTIPART:
235
- self.body_mode_switcher.current = 'body-mode-form-multipart'
236
-
237
- @on(Select.Changed, '#body-raw-language')
238
- def _on_change_body_raw_language(self, message: Select.Changed) -> None:
239
- self.body_raw_editor.language = message.value
240
-
241
- @on(DynamicFields.FieldFilled, '#body-form-urlencoded')
242
- def _on_form_filled(self, message: DynamicFields.FieldFilled) -> None:
243
- self.body_enabled_switch.value = True
244
-
245
- @on(DynamicFields.FieldEmpty, '#body-form-urlencoded')
246
- def _on_form_empty(self, message: DynamicFields.FieldEmpty) -> None:
247
- if not message.control.filled_fields:
248
- self.body_enabled_switch.value = False
249
-
250
- @on(CustomTextArea.Changed, '#body-raw')
251
- def _on_change_body_raw(self, message: CustomTextArea.Changed) -> None:
252
- if self.body_raw_editor.text == '':
253
- self.body_enabled_switch.value = False
254
- else:
255
- self.body_enabled_switch.value = True
256
-
257
- @on(Input.Changed, '#options-timeout')
258
- def _on_change_timeout(self, message: Input.Changed) -> None:
259
- new_value = message.value
260
-
261
- if new_value == '':
262
- return
263
-
264
- try:
265
- float(new_value)
266
- except Exception:
267
- self.options_timeout_input.value = (
268
- self.options_timeout_input.value[:-1]
269
- )
270
-
271
- def _get_headers(self) -> list[HeaderField]:
272
- return [
273
- HeaderField(
274
- enabled=header_field['enabled'],
275
- key=header_field['key'],
276
- value=header_field['value'],
277
- )
278
- for header_field in self.header_fields.values
279
- ]
280
-
281
- def _get_query_params(self) -> list[QueryParamField]:
282
- return [
283
- QueryParamField(
284
- enabled=query_param_field['enabled'],
285
- key=query_param_field['key'],
286
- value=query_param_field['value'],
287
- )
288
- for query_param_field in self.param_fields.values
289
- ]
290
-
291
- def _get_body(self) -> RequestAreaData.Body:
292
- body_send: bool = self.body_enabled_switch.value
293
- body_type: str = BodyMode(self.body_mode_select.value)
294
-
295
- payload = None
296
- if body_type == BodyMode.RAW:
297
- payload = self.body_raw_editor.text
298
- elif body_type == BodyMode.FILE:
299
- payload = self.body_file_path_chooser.path
300
- elif body_type == BodyMode.FORM_URLENCODED:
301
- payload = []
302
- for form_item in self.body_form_urlencoded_fields.values:
303
- payload.append(
304
- FormUrlEncodedField(
305
- enabled=form_item['enabled'],
306
- key=form_item['key'],
307
- value=form_item['value'],
308
- )
309
- )
310
- elif body_type == BodyMode.FORM_MULTIPART:
311
- payload = []
312
- for form_item in self.body_form_multipart_fields.values:
313
- payload.append(
314
- FormMultipartField(
315
- enabled=form_item['enabled'],
316
- key=form_item['key'],
317
- value=form_item['value'],
318
- )
319
- )
320
-
321
- return RequestAreaData.Body(
322
- enabled=body_send,
323
- raw_language=BodyRawLanguage(self.body_raw_language_select.value),
324
- type=body_type,
325
- payload=payload,
326
- )
327
-
328
- def _get_options(self) -> RequestAreaData.Options:
329
- timeout = None
330
- if self.options_timeout_input.value:
331
- timeout = float(self.options_timeout_input.value)
332
-
333
- return RequestAreaData.Options(
334
- timeout=timeout,
335
- follow_redirects=self.options_follow_redirects_switch.value,
336
- verify_ssl=self.options_verify_ssl_switch.value,
337
- )