Skip to content

Commit f9abeae

Browse files
fix: run blocking call in threadpool (#780)
1 parent 39a9cb1 commit f9abeae

10 files changed

+73
-45
lines changed

playwright/_impl/_browser.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from playwright._impl._helper import (
3232
ColorScheme,
3333
ReducedMotion,
34+
async_readfile,
3435
is_safe_close_error,
3536
locals_to_params,
3637
)
@@ -106,7 +107,7 @@ async def new_context(
106107
storageState: Union[StorageState, str, Path] = None,
107108
) -> BrowserContext:
108109
params = locals_to_params(locals())
109-
normalize_context_params(self._connection._is_sync, params)
110+
await normalize_context_params(self._connection._is_sync, params)
110111

111112
channel = await self._channel.send("newContext", params)
112113
context = from_channel(channel)
@@ -190,7 +191,7 @@ async def stop_tracing(self) -> bytes:
190191
return base64.b64decode(encoded_binary)
191192

192193

193-
def normalize_context_params(is_sync: bool, params: Dict) -> None:
194+
async def normalize_context_params(is_sync: bool, params: Dict) -> None:
194195
params["sdkLanguage"] = "python" if is_sync else "python-async"
195196
if params.get("noViewport"):
196197
del params["noViewport"]
@@ -214,5 +215,6 @@ def normalize_context_params(is_sync: bool, params: Dict) -> None:
214215
if "storageState" in params:
215216
storageState = params["storageState"]
216217
if not isinstance(storageState, dict):
217-
with open(storageState, "r") as f:
218-
params["storageState"] = json.load(f)
218+
params["storageState"] = json.loads(
219+
(await async_readfile(storageState)).decode()
220+
)

playwright/_impl/_browser_context.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
TimeoutSettings,
3535
URLMatch,
3636
URLMatcher,
37+
async_readfile,
38+
async_writefile,
3739
is_safe_close_error,
3840
locals_to_params,
3941
)
@@ -214,8 +216,7 @@ async def add_init_script(
214216
self, script: str = None, path: Union[str, Path] = None
215217
) -> None:
216218
if path:
217-
with open(path, "r") as file:
218-
script = file.read()
219+
script = (await async_readfile(path)).decode()
219220
if not isinstance(script, str):
220221
raise Error("Either path or script parameter must be specified")
221222
await self._channel.send("addInitScript", dict(source=script))
@@ -298,8 +299,7 @@ async def _pause(self) -> None:
298299
async def storage_state(self, path: Union[str, Path] = None) -> StorageState:
299300
result = await self._channel.send_return_as_dict("storageState")
300301
if path:
301-
with open(path, "w") as f:
302-
json.dump(result, f)
302+
await async_writefile(path, json.dumps(result))
303303
return result
304304

305305
async def wait_for_event(

playwright/_impl/_browser_type.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ async def launch_persistent_context(
135135
) -> BrowserContext:
136136
userDataDir = str(Path(userDataDir))
137137
params = locals_to_params(locals())
138-
normalize_context_params(self._connection._is_sync, params)
138+
await normalize_context_params(self._connection._is_sync, params)
139139
normalize_launch_params(params)
140140
try:
141141
context = from_channel(

playwright/_impl/_element_handle.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from playwright._impl._helper import (
2424
KeyboardModifier,
2525
MouseButton,
26+
async_writefile,
2627
locals_to_params,
2728
make_dirs_for_file,
2829
)
@@ -187,7 +188,7 @@ async def set_input_files(
187188
noWaitAfter: bool = None,
188189
) -> None:
189190
params = locals_to_params(locals())
190-
params["files"] = normalize_file_payloads(files)
191+
params["files"] = await normalize_file_payloads(files)
191192
await self._channel.send("setInputFiles", params)
192193

193194
async def focus(self) -> None:
@@ -249,8 +250,7 @@ async def screenshot(
249250
decoded_binary = base64.b64decode(encoded_binary)
250251
if path:
251252
make_dirs_for_file(path)
252-
with open(path, "wb") as fd:
253-
fd.write(decoded_binary)
253+
await async_writefile(path, decoded_binary)
254254
return decoded_binary
255255

256256
async def query_selector(self, selector: str) -> Optional["ElementHandle"]:

playwright/_impl/_file_chooser.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from typing import TYPE_CHECKING, List, Union
1919

2020
from playwright._impl._api_structures import FilePayload
21+
from playwright._impl._helper import async_readfile
2122

2223
if TYPE_CHECKING: # pragma: no cover
2324
from playwright._impl._element_handle import ElementHandle
@@ -57,20 +58,19 @@ async def set_files(
5758
await self._element_handle.set_input_files(files, timeout, noWaitAfter)
5859

5960

60-
def normalize_file_payloads(
61+
async def normalize_file_payloads(
6162
files: Union[str, Path, FilePayload, List[Union[str, Path]], List[FilePayload]]
6263
) -> List:
6364
file_list = files if isinstance(files, list) else [files]
6465
file_payloads: List = []
6566
for item in file_list:
66-
if isinstance(item, str) or isinstance(item, Path):
67-
with open(item, mode="rb") as fd:
68-
file_payloads.append(
69-
{
70-
"name": os.path.basename(item),
71-
"buffer": base64.b64encode(fd.read()).decode(),
72-
}
73-
)
67+
if isinstance(item, (str, Path)):
68+
file_payloads.append(
69+
{
70+
"name": os.path.basename(item),
71+
"buffer": base64.b64encode(await async_readfile(item)).decode(),
72+
}
73+
)
7474
else:
7575
file_payloads.append(
7676
{

playwright/_impl/_frame.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
MouseButton,
3737
URLMatch,
3838
URLMatcher,
39+
async_readfile,
3940
locals_to_params,
4041
monotonic_time,
4142
)
@@ -360,21 +361,26 @@ async def add_script_tag(
360361
) -> ElementHandle:
361362
params = locals_to_params(locals())
362363
if path:
363-
with open(path, "r") as file:
364-
params["content"] = file.read() + "\n//# sourceURL=" + str(Path(path))
365-
del params["path"]
364+
params["content"] = (
365+
(await async_readfile(path)).decode()
366+
+ "\n//# sourceURL="
367+
+ str(Path(path))
368+
)
369+
del params["path"]
366370
return from_channel(await self._channel.send("addScriptTag", params))
367371

368372
async def add_style_tag(
369373
self, url: str = None, path: Union[str, Path] = None, content: str = None
370374
) -> ElementHandle:
371375
params = locals_to_params(locals())
372376
if path:
373-
with open(path, "r") as file:
374-
params["content"] = (
375-
file.read() + "\n/*# sourceURL=" + str(Path(path)) + "*/"
376-
)
377-
del params["path"]
377+
params["content"] = (
378+
(await async_readfile(path)).decode()
379+
+ "\n/*# sourceURL="
380+
+ str(Path(path))
381+
+ "*/"
382+
)
383+
del params["path"]
378384
return from_channel(await self._channel.send("addStyleTag", params))
379385

380386
async def click(
@@ -492,7 +498,7 @@ async def set_input_files(
492498
noWaitAfter: bool = None,
493499
) -> None:
494500
params = locals_to_params(locals())
495-
params["files"] = normalize_file_payloads(files)
501+
params["files"] = await normalize_file_payloads(files)
496502
await self._channel.send("setInputFiles", params)
497503

498504
async def type(

playwright/_impl/_helper.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
14+
import asyncio
1515
import fnmatch
1616
import math
1717
import os
@@ -231,3 +231,21 @@ def make_dirs_for_file(path: Union[Path, str]) -> None:
231231
if not os.path.isabs(path):
232232
path = Path.cwd() / path
233233
os.makedirs(os.path.dirname(path), exist_ok=True)
234+
235+
236+
async def async_writefile(file: Union[str, Path], data: Union[str, bytes]) -> None:
237+
def inner() -> None:
238+
with open(file, "w" if isinstance(data, str) else "wb") as fh:
239+
fh.write(data)
240+
241+
loop = asyncio.get_running_loop()
242+
await loop.run_in_executor(None, inner)
243+
244+
245+
async def async_readfile(file: Union[str, Path]) -> bytes:
246+
def inner() -> bytes:
247+
with open(file, "rb") as fh:
248+
return fh.read()
249+
250+
loop = asyncio.get_running_loop()
251+
return await loop.run_in_executor(None, inner)

playwright/_impl/_page.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
URLMatcher,
5555
URLMatchRequest,
5656
URLMatchResponse,
57+
async_readfile,
58+
async_writefile,
5759
is_safe_close_error,
5860
locals_to_params,
5961
make_dirs_for_file,
@@ -498,8 +500,7 @@ async def add_init_script(
498500
self, script: str = None, path: Union[str, Path] = None
499501
) -> None:
500502
if path:
501-
with open(path, "r") as file:
502-
script = file.read()
503+
script = (await async_readfile(path)).decode()
503504
if not isinstance(script, str):
504505
raise Error("Either path or script parameter must be specified")
505506
await self._channel.send("addInitScript", dict(source=script))
@@ -542,8 +543,7 @@ async def screenshot(
542543
decoded_binary = base64.b64decode(encoded_binary)
543544
if path:
544545
make_dirs_for_file(path)
545-
with open(path, "wb") as fd:
546-
fd.write(decoded_binary)
546+
await async_writefile(path, decoded_binary)
547547
return decoded_binary
548548

549549
async def title(self) -> str:
@@ -751,8 +751,7 @@ async def pdf(
751751
decoded_binary = base64.b64decode(encoded_binary)
752752
if path:
753753
make_dirs_for_file(path)
754-
with open(path, "wb") as fd:
755-
fd.write(decoded_binary)
754+
await async_writefile(path, decoded_binary)
756755
return decoded_binary
757756

758757
@property

playwright/_impl/_selectors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from playwright._impl._api_types import Error
1919
from playwright._impl._connection import ChannelOwner
20+
from playwright._impl._helper import async_readfile
2021

2122

2223
class Selectors(ChannelOwner):
@@ -35,8 +36,7 @@ async def register(
3536
if not script and not path:
3637
raise Error("Either source or path should be specified")
3738
if path:
38-
with open(path, "r") as file:
39-
script = file.read()
39+
script = (await async_readfile(path)).decode()
4040
params: Dict = dict(name=name, source=script)
4141
if contentScript:
4242
params["contentScript"] = True

playwright/_impl/_stream.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ def __init__(
2626
super().__init__(parent, type, guid, initializer)
2727

2828
async def save_as(self, path: Union[str, Path]) -> None:
29-
with open(path, mode="wb") as file:
30-
while True:
31-
binary = await self._channel.send("read")
32-
if not binary:
33-
break
34-
file.write(base64.b64decode(binary))
29+
file = await self._loop.run_in_executor(None, lambda: open(path, "wb"))
30+
while True:
31+
binary = await self._channel.send("read")
32+
if not binary:
33+
break
34+
await self._loop.run_in_executor(
35+
None, lambda: file.write(base64.b64decode(binary))
36+
)
37+
await self._loop.run_in_executor(None, lambda: file.close())

0 commit comments

Comments
 (0)