|
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 |
|
@@ -212,7 +214,7 @@ async def patch( |
212 | 214 | headers: Headers = None, |
213 | 215 | data: DataType = None, |
214 | 216 | form: FormType = None, |
215 | | - multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, |
| 217 | + multipart: MultipartType = None, |
216 | 218 | timeout: float = None, |
217 | 219 | failOnStatusCode: bool = None, |
218 | 220 | ignoreHTTPSErrors: bool = None, |
@@ -241,7 +243,7 @@ async def put( |
241 | 243 | headers: Headers = None, |
242 | 244 | data: DataType = None, |
243 | 245 | form: FormType = None, |
244 | | - multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, |
| 246 | + multipart: MultipartType = None, |
245 | 247 | timeout: float = None, |
246 | 248 | failOnStatusCode: bool = None, |
247 | 249 | ignoreHTTPSErrors: bool = None, |
@@ -270,7 +272,7 @@ async def post( |
270 | 272 | headers: Headers = None, |
271 | 273 | data: DataType = None, |
272 | 274 | form: FormType = None, |
273 | | - multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, |
| 275 | + multipart: MultipartType = None, |
274 | 276 | timeout: float = None, |
275 | 277 | failOnStatusCode: bool = None, |
276 | 278 | ignoreHTTPSErrors: bool = None, |
@@ -300,7 +302,7 @@ async def fetch( |
300 | 302 | headers: Headers = None, |
301 | 303 | data: DataType = None, |
302 | 304 | form: FormType = None, |
303 | | - multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, |
| 305 | + multipart: MultipartType = None, |
304 | 306 | timeout: float = None, |
305 | 307 | failOnStatusCode: bool = None, |
306 | 308 | ignoreHTTPSErrors: bool = None, |
@@ -341,7 +343,7 @@ async def _inner_fetch( |
341 | 343 | data: DataType = None, |
342 | 344 | params: ParamsType = None, |
343 | 345 | form: FormType = None, |
344 | | - multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, |
| 346 | + multipart: MultipartType = None, |
345 | 347 | timeout: float = None, |
346 | 348 | failOnStatusCode: bool = None, |
347 | 349 | ignoreHTTPSErrors: bool = None, |
@@ -381,21 +383,36 @@ async def _inner_fetch( |
381 | 383 | else: |
382 | 384 | raise Error(f"Unsupported 'data' type: {type(data)}") |
383 | 385 | elif form: |
384 | | - form_data = object_to_array(form) |
| 386 | + if isinstance(form, FormData): |
| 387 | + form_data = [] |
| 388 | + for fd_name, fd_value in form._fields: |
| 389 | + if isinstance(fd_value, (pathlib.Path, dict)): |
| 390 | + raise Error( |
| 391 | + f"Form field {fd_name!r} must be a string, number or boolean. Use 'multipart' for file uploads." |
| 392 | + ) |
| 393 | + form_data.append(NameValue(name=fd_name, value=str(fd_value))) |
| 394 | + else: |
| 395 | + form_data = object_to_array(form) |
385 | 396 | elif multipart: |
386 | 397 | multipart_data = [] |
387 | | - # Convert file-like values to ServerFilePayload structs. |
388 | | - for name, value in multipart.items(): |
389 | | - if is_file_payload(value): |
390 | | - payload = cast(FilePayload, value) |
391 | | - assert isinstance( |
392 | | - payload["buffer"], bytes |
393 | | - ), f"Unexpected buffer type of 'data.{name}'" |
| 398 | + if isinstance(multipart, FormData): |
| 399 | + for fd_name, fd_value in multipart._fields: |
394 | 400 | multipart_data.append( |
395 | | - FormField(name=name, file=file_payload_to_json(payload)) |
| 401 | + await _form_data_field_to_form_field(fd_name, fd_value) |
396 | 402 | ) |
397 | | - elif isinstance(value, str): |
398 | | - multipart_data.append(FormField(name=name, value=value)) |
| 403 | + else: |
| 404 | + # Convert file-like values to ServerFilePayload structs. |
| 405 | + for name, value in multipart.items(): |
| 406 | + if is_file_payload(value): |
| 407 | + payload = cast(FilePayload, value) |
| 408 | + assert isinstance( |
| 409 | + payload["buffer"], bytes |
| 410 | + ), f"Unexpected buffer type of 'data.{name}'" |
| 411 | + multipart_data.append( |
| 412 | + FormField(name=name, file=file_payload_to_json(payload)) |
| 413 | + ) |
| 414 | + elif isinstance(value, str): |
| 415 | + multipart_data.append(FormField(name=name, value=value)) |
399 | 416 | if ( |
400 | 417 | post_data_buffer is None |
401 | 418 | and json_data is None |
@@ -450,6 +467,28 @@ def file_payload_to_json(payload: FilePayload) -> ServerFilePayload: |
450 | 467 | ) |
451 | 468 |
|
452 | 469 |
|
| 470 | +async def _form_data_field_to_form_field(name: str, value: Any) -> FormField: |
| 471 | + if isinstance(value, pathlib.Path): |
| 472 | + mime_type, _ = mimetypes.guess_type(str(value)) |
| 473 | + return FormField( |
| 474 | + name=name, |
| 475 | + file=ServerFilePayload( |
| 476 | + name=value.name, |
| 477 | + mimeType=mime_type or "application/octet-stream", |
| 478 | + buffer=base64.b64encode(await async_readfile(str(value))).decode(), |
| 479 | + ), |
| 480 | + ) |
| 481 | + if is_file_payload(value): |
| 482 | + payload = cast(FilePayload, value) |
| 483 | + assert isinstance( |
| 484 | + payload["buffer"], bytes |
| 485 | + ), f"Unexpected buffer type of form field {name!r}" |
| 486 | + return FormField(name=name, file=file_payload_to_json(payload)) |
| 487 | + if isinstance(value, (str, int, float, bool)): |
| 488 | + return FormField(name=name, value=str(value)) |
| 489 | + raise Error(f"Unsupported form field {name!r} value type: {type(value).__name__}") |
| 490 | + |
| 491 | + |
453 | 492 | class APIResponse: |
454 | 493 | def __init__(self, context: APIRequestContext, initializer: Dict) -> None: |
455 | 494 | self._loop = context._loop |
|
0 commit comments