|
14 | 14 |
|
15 | 15 | import base64 |
16 | 16 | import json |
| 17 | +import mimetypes |
17 | 18 | import pathlib |
18 | 19 | import typing |
19 | 20 | from pathlib import Path |
|
32 | 33 | ) |
33 | 34 | from playwright._impl._connection import ChannelOwner, from_channel |
34 | 35 | from playwright._impl._errors import is_target_closed_error |
| 36 | +from playwright._impl._form_data import FormData |
35 | 37 | from playwright._impl._helper import ( |
36 | 38 | Error, |
37 | 39 | NameValue, |
|
51 | 53 | from playwright._impl._playwright import Playwright |
52 | 54 |
|
53 | 55 |
|
54 | | -FormType = Dict[str, Union[bool, float, str]] |
| 56 | +FormType = Union[Dict[str, Union[bool, float, str]], FormData] |
55 | 57 | DataType = Union[Any, bytes, str] |
56 | | -MultipartType = Dict[str, Union[bytes, bool, float, str, FilePayload]] |
| 58 | +MultipartType = Union[Dict[str, Union[bytes, bool, float, str, FilePayload]], FormData] |
57 | 59 | ParamsType = Union[Dict[str, Union[bool, float, str]], str] |
58 | 60 |
|
59 | 61 |
|
@@ -217,7 +219,7 @@ async def patch( |
217 | 219 | headers: Headers = None, |
218 | 220 | data: DataType = None, |
219 | 221 | form: FormType = None, |
220 | | - multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, |
| 222 | + multipart: MultipartType = None, |
221 | 223 | timeout: float = None, |
222 | 224 | failOnStatusCode: bool = None, |
223 | 225 | ignoreHTTPSErrors: bool = None, |
@@ -246,7 +248,7 @@ async def put( |
246 | 248 | headers: Headers = None, |
247 | 249 | data: DataType = None, |
248 | 250 | form: FormType = None, |
249 | | - multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, |
| 251 | + multipart: MultipartType = None, |
250 | 252 | timeout: float = None, |
251 | 253 | failOnStatusCode: bool = None, |
252 | 254 | ignoreHTTPSErrors: bool = None, |
@@ -275,7 +277,7 @@ async def post( |
275 | 277 | headers: Headers = None, |
276 | 278 | data: DataType = None, |
277 | 279 | form: FormType = None, |
278 | | - multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, |
| 280 | + multipart: MultipartType = None, |
279 | 281 | timeout: float = None, |
280 | 282 | failOnStatusCode: bool = None, |
281 | 283 | ignoreHTTPSErrors: bool = None, |
@@ -305,7 +307,7 @@ async def fetch( |
305 | 307 | headers: Headers = None, |
306 | 308 | data: DataType = None, |
307 | 309 | form: FormType = None, |
308 | | - multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, |
| 310 | + multipart: MultipartType = None, |
309 | 311 | timeout: float = None, |
310 | 312 | failOnStatusCode: bool = None, |
311 | 313 | ignoreHTTPSErrors: bool = None, |
@@ -346,7 +348,7 @@ async def _inner_fetch( |
346 | 348 | data: DataType = None, |
347 | 349 | params: ParamsType = None, |
348 | 350 | form: FormType = None, |
349 | | - multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, |
| 351 | + multipart: MultipartType = None, |
350 | 352 | timeout: float = None, |
351 | 353 | failOnStatusCode: bool = None, |
352 | 354 | ignoreHTTPSErrors: bool = None, |
@@ -386,21 +388,36 @@ async def _inner_fetch( |
386 | 388 | else: |
387 | 389 | raise Error(f"Unsupported 'data' type: {type(data)}") |
388 | 390 | elif form: |
389 | | - form_data = object_to_array(form) |
| 391 | + if isinstance(form, FormData): |
| 392 | + form_data = [] |
| 393 | + for fd_name, fd_value in form._fields: |
| 394 | + if isinstance(fd_value, (pathlib.Path, dict)): |
| 395 | + raise Error( |
| 396 | + f"Form field {fd_name!r} must be a string, number or boolean. Use 'multipart' for file uploads." |
| 397 | + ) |
| 398 | + form_data.append(NameValue(name=fd_name, value=str(fd_value))) |
| 399 | + else: |
| 400 | + form_data = object_to_array(form) |
390 | 401 | elif multipart: |
391 | 402 | multipart_data = [] |
392 | | - # Convert file-like values to ServerFilePayload structs. |
393 | | - for name, value in multipart.items(): |
394 | | - if is_file_payload(value): |
395 | | - payload = cast(FilePayload, value) |
396 | | - assert isinstance( |
397 | | - payload["buffer"], bytes |
398 | | - ), f"Unexpected buffer type of 'data.{name}'" |
| 403 | + if isinstance(multipart, FormData): |
| 404 | + for fd_name, fd_value in multipart._fields: |
399 | 405 | multipart_data.append( |
400 | | - FormField(name=name, file=file_payload_to_json(payload)) |
| 406 | + await _form_data_field_to_form_field(fd_name, fd_value) |
401 | 407 | ) |
402 | | - elif isinstance(value, str): |
403 | | - multipart_data.append(FormField(name=name, value=value)) |
| 408 | + else: |
| 409 | + # Convert file-like values to ServerFilePayload structs. |
| 410 | + for name, value in multipart.items(): |
| 411 | + if is_file_payload(value): |
| 412 | + payload = cast(FilePayload, value) |
| 413 | + assert isinstance( |
| 414 | + payload["buffer"], bytes |
| 415 | + ), f"Unexpected buffer type of 'data.{name}'" |
| 416 | + multipart_data.append( |
| 417 | + FormField(name=name, file=file_payload_to_json(payload)) |
| 418 | + ) |
| 419 | + elif isinstance(value, str): |
| 420 | + multipart_data.append(FormField(name=name, value=value)) |
404 | 421 | if ( |
405 | 422 | post_data_buffer is None |
406 | 423 | and json_data is None |
@@ -455,6 +472,28 @@ def file_payload_to_json(payload: FilePayload) -> ServerFilePayload: |
455 | 472 | ) |
456 | 473 |
|
457 | 474 |
|
| 475 | +async def _form_data_field_to_form_field(name: str, value: Any) -> FormField: |
| 476 | + if isinstance(value, pathlib.Path): |
| 477 | + mime_type, _ = mimetypes.guess_type(str(value)) |
| 478 | + return FormField( |
| 479 | + name=name, |
| 480 | + file=ServerFilePayload( |
| 481 | + name=value.name, |
| 482 | + mimeType=mime_type or "application/octet-stream", |
| 483 | + buffer=base64.b64encode(await async_readfile(str(value))).decode(), |
| 484 | + ), |
| 485 | + ) |
| 486 | + if is_file_payload(value): |
| 487 | + payload = cast(FilePayload, value) |
| 488 | + assert isinstance( |
| 489 | + payload["buffer"], bytes |
| 490 | + ), f"Unexpected buffer type of form field {name!r}" |
| 491 | + return FormField(name=name, file=file_payload_to_json(payload)) |
| 492 | + if isinstance(value, (str, int, float, bool)): |
| 493 | + return FormField(name=name, value=str(value)) |
| 494 | + raise Error(f"Unsupported form field {name!r} value type: {type(value).__name__}") |
| 495 | + |
| 496 | + |
458 | 497 | class APIResponse: |
459 | 498 | def __init__(self, context: APIRequestContext, initializer: Dict) -> None: |
460 | 499 | self._loop = context._loop |
|
0 commit comments