hierarchy.py: BodyRow, Levels, стратегии outline (сдвиг к 0), indent (ранги различных отступов — единицы alignment.indent и пробелов не смешиваются), account_pattern (ранг кода + жирность, ASSUMPTION в docstring), sums (жадно, без рекурсии); is_consistent, parents (по позициям и уровням, Review Focus 3), resolve_levels: «Итого» в стратегии не передаётся и получает уровень 0; меньше двух строк кроме «Итого» — дерево «trivial». deadline.py: кооперативный Deadline (R-3). Unit-тесты; fixtures-тест — в Задаче 8 (R-2). Отметки плана и tasks.md 2.3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
27 lines
1.2 KiB
Python
27 lines
1.2 KiB
Python
"""Кооперативный таймаут разбора (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}")
|