Исходный код raito.plugins.scenes.steps
from __future__ import annotations
from aiogram.fsm.state import State, StatesGroup
__all__ = ("SceneSteps",)
[документация]
class SceneSteps:
"""The ordered steps of a scene, derived from an aiogram ``StatesGroup``.
Owns everything about the step layout — the order used by ``scene.next`` and
``scene.back``, the membership checks that detect a stale scene, and the
namespace prefix that tells a scene's own states apart from anything else in
FSM storage.
"""
def __init__(self, states: object) -> None:
"""Read the ordered steps out of a ``StatesGroup``.
Typed as ``object`` because this is the runtime boundary: the public
``Router.scene`` types ``states`` as ``type[StatesGroup]``, but a dynamic
caller can still pass anything, so the class is validated here.
:param states: ``StatesGroup`` whose states are the scene's steps, in order
:raises TypeError: if ``states`` is not a ``StatesGroup`` subclass
:raises ValueError: if ``states`` declares no states
"""
if not (isinstance(states, type) and issubclass(states, StatesGroup)):
msg = "Scene states must be a StatesGroup subclass."
raise TypeError(msg)
if not states.__states__:
msg = f"StatesGroup {states.__full_group_name__!r} declares no states."
raise ValueError(msg)
self.id = states.__full_group_name__
self.prefix = f"{self.id}:"
self.states = states.__states__
self._names = frozenset(states.__state_names__)
self._index = {state.state: position for position, state in enumerate(self.states)}
[документация]
def owns(self, raw_state: str) -> bool:
"""Whether ``raw_state`` lives in this scene's namespace (current or removed)."""
return raw_state.startswith(self.prefix)
[документация]
def is_active(self, raw_state: str) -> bool:
"""Whether ``raw_state`` is a step this scene still declares."""
return raw_state in self._names
[документация]
def ensure(self, state: State) -> None:
"""Validate that ``state`` is a step of this scene.
:param state: state to check
:raises ValueError: if ``state`` is not one of the scene's steps
"""
if not isinstance(state, State) or state.state not in self._names:
msg = f"{state!r} is not a step of scene {self.id!r}."
raise ValueError(msg)
[документация]
def step_for(self, raw_state: str) -> State:
"""Return the step matching ``raw_state``.
Trusts that ``raw_state`` is already an active step of this scene — the
``StateFilter`` that matched the handler already established that.
:param raw_state: state aiogram already resolved for this update
:return: the matching step
"""
return self.states[self._index[raw_state]]
[документация]
def after(self, step: State | None) -> State:
"""Return the step following ``step`` (the first step, from entry).
:param step: current step, or ``None`` at entry
:return: next step in declaration order
:raises RuntimeError: if ``step`` is already the last step
"""
position = 0 if step is None else self._index[step.state] + 1
if position >= len(self.states):
msg = f"Scene {self.id!r}: no step after {step!r}; call scene.finish()."
raise RuntimeError(msg)
return self.states[position]
[документация]
def before(self, step: State | None) -> State:
"""Return the step preceding ``step``.
:param step: current step
:return: previous step in declaration order
:raises RuntimeError: if there is no earlier step
"""
position = -1 if step is None else self._index[step.state] - 1
if position < 0:
msg = f"Scene {self.id!r}: no step before {step!r}."
raise RuntimeError(msg)
return self.states[position]