Schedule a function to be called in the next reactor loop, but only if it hasn't been already scheduled since the last time it ran.
| 46 | |
| 47 | |
| 48 | class CallLaterOnce(Generic[_T]): |
| 49 | """Schedule a function to be called in the next reactor loop, but only if |
| 50 | it hasn't been already scheduled since the last time it ran. |
| 51 | """ |
| 52 | |
| 53 | def __init__(self, func: Callable[_P, _T], *a: _P.args, **kw: _P.kwargs): |
| 54 | self._func: Callable[_P, _T] = func |
| 55 | self._a: tuple[Any, ...] = a |
| 56 | self._kw: dict[str, Any] = kw |
| 57 | self._call: CallLaterResult | None = None |
| 58 | self._deferreds: list[Deferred[None]] = [] |
| 59 | |
| 60 | def schedule(self, delay: float = 0) -> None: |
| 61 | # circular import |
| 62 | from scrapy.utils.asyncio import call_later # noqa: PLC0415 |
| 63 | |
| 64 | if self._call is None: |
| 65 | self._call = call_later(delay, self) |
| 66 | |
| 67 | def cancel(self) -> None: |
| 68 | if self._call: |
| 69 | self._call.cancel() |
| 70 | |
| 71 | def __call__(self) -> _T: |
| 72 | # circular import |
| 73 | from scrapy.utils.asyncio import call_later # noqa: PLC0415 |
| 74 | |
| 75 | self._call = None |
| 76 | result = self._func(*self._a, **self._kw) |
| 77 | |
| 78 | for d in self._deferreds: |
| 79 | call_later(0, d.callback, None) |
| 80 | self._deferreds.clear() |
| 81 | |
| 82 | return result |
| 83 | |
| 84 | async def wait(self) -> None: |
| 85 | # circular import |
| 86 | from scrapy.utils.defer import maybe_deferred_to_future # noqa: PLC0415 |
| 87 | |
| 88 | d: Deferred[None] = Deferred() |
| 89 | self._deferreds.append(d) |
| 90 | await maybe_deferred_to_future(d) |
| 91 | |
| 92 | |
| 93 | _asyncio_reactor_path = "twisted.internet.asyncioreactor.AsyncioSelectorReactor" |