"""Кооперативный таймаут разбора (design D-8, ruling R-3). Разбор идёт в `asyncio.to_thread`, поток нельзя прервать снаружи, поэтому шаги парсера сами вызывают `Deadline.check(<шаг>)` между этапами; превышение — `ParseTimeoutError` (код контракта `TIMEOUT`). """ import time from collections.abc import Callable from src.parser.errors import ParseTimeoutError __all__ = ["Deadline"] class Deadline: """Лимит времени, отсчитываемый от создания; часы подменяются в тестах.""" def __init__(self, seconds: float, clock: Callable[[], float] = time.monotonic) -> None: self._seconds = seconds self._clock = clock self._expires_at = clock() + seconds def check(self, where: str) -> None: """Бросает `ParseTimeoutError`, если лимит превышен; `where` — имя шага для сообщения.""" if self._clock() > self._expires_at: raise ParseTimeoutError(f"превышен лимит {self._seconds} с на шаге {where}")