@@ -129,10 +129,14 @@ async def _process_queue(cls) -> None:
129
129
cls ._coro_queue .task_done ()
130
130
131
131
@classmethod
132
- def _deferred_from_coro (cls , coro ) -> Deferred :
132
+ def _ensure_future (cls , coro : Awaitable ) -> asyncio . Future :
133
133
future : asyncio .Future = asyncio .Future ()
134
134
asyncio .run_coroutine_threadsafe (cls ._coro_queue .put ((coro , future )), cls ._loop )
135
- return scrapy .utils .defer .deferred_from_coro (future )
135
+ return future
136
+
137
+ @classmethod
138
+ def _deferred_from_coro (cls , coro : Awaitable ) -> Deferred :
139
+ return scrapy .utils .defer .deferred_from_coro (cls ._ensure_future (coro ))
136
140
137
141
@classmethod
138
142
def start (cls , caller_id : int ) -> None :
@@ -158,3 +162,24 @@ def stop(cls, caller_id: int) -> None:
158
162
asyncio .run_coroutine_threadsafe (cls ._coro_queue .join (), cls ._loop )
159
163
cls ._loop .call_soon_threadsafe (cls ._loop .stop )
160
164
cls ._thread .join ()
165
+
166
+
167
+ def ensure_future (coro : Awaitable ) -> asyncio .Future :
168
+ """Wrap a coroutine in a Future assigned to the threaded event loop.
169
+
170
+ On windows, Playwright runs in an event loop of its own in a separate thread.
171
+ If Playwright coroutines are awaited directly, they are assigned to the main
172
+ thread's event loop, resulting in: "ValueError: The future belongs to a
173
+ different loop than the one specified as the loop argument"
174
+
175
+ Usage:
176
+ ```
177
+ from playwright.async_api import Page
178
+ from scrapy_playwright import ensure_future
179
+
180
+ async def parse(self, response):
181
+ page: Page = response.meta["playwright_page"]
182
+ await ensure_future(page.screenshot(path="example.png", full_page=True))
183
+ ```
184
+ """
185
+ return _ThreadedLoopAdapter ._ensure_future (coro )
0 commit comments