diff options
| author | 2025-01-04 22:38:23 +0800 | |
|---|---|---|
| committer | 2025-01-04 22:38:23 +0800 | |
| commit | c990518cb533a793399e44edbb4bc036342c7175 (patch) | |
| tree | 8e2bd0f833b803a73dea7d88e7c294cf3d078d4d /src/hrc/rule/__init__.py | |
| parent | bc57c1410c08323ba37114082d0fe609fafc2c5d (diff) | |
| download | HydroRollCore-c990518cb533a793399e44edbb4bc036342c7175.tar.gz HydroRollCore-c990518cb533a793399e44edbb4bc036342c7175.zip | |
Diffstat (limited to 'src/hrc/rule/__init__.py')
| -rw-r--r-- | src/hrc/rule/__init__.py | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/src/hrc/rule/__init__.py b/src/hrc/rule/__init__.py new file mode 100644 index 0000000..282e3e2 --- /dev/null +++ b/src/hrc/rule/__init__.py @@ -0,0 +1,164 @@ +import functools # noqa: F401 +from typing import Generic, Any, Type + +from abc import ABC + +from hrc.rule import BaseRule # noqa: F401 +from hrc.typing import RuleT # noqa: F401 + +import inspect +from abc import abstractmethod # noqa: F401 +from enum import Enum +from typing import ( + TYPE_CHECKING, + ClassVar, + NoReturn, + Optional, + Tuple, + cast, + final, +) +from typing_extensions import Annotated, get_args, get_origin + +from hrc.config import ConfigModel + +from hrc.dependencies import Depends +from hrc.event import Event +from hrc.exceptions import SkipException, StopException +from hrc.typing import ConfigT, EventT, StateT +from hrc.utils import is_config_class + +if TYPE_CHECKING: + from hrc.core import Core + + +class RuleLoadType(Enum): + """Rules loaded types.""" + + DIR = "dir" + NAME = "name" + FILE = "file" + CLASS = "class" + + +class Rule(ABC, Generic[EventT, StateT, ConfigT]): + priority: ClassVar[int] = 0 + block: ClassVar[bool] = False + + # Cannot use ClassVar because PEP 526 does not allow it + Config: Type[ConfigT] + + __rule_load_type__: ClassVar[RuleLoadType] + __rule_file_path__: ClassVar[Optional[str]] + + if TYPE_CHECKING: + event: EventT + else: + event = Depends(Event) # noqa: F821 + + def __init_state__(self) -> Optional[StateT]: + """Initialize rule state.""" + + def __init_subclass__( + cls, + config: Optional[Type[ConfigT]] = None, + init_state: Optional[StateT] = None, + **_kwargs: Any, + ) -> None: + super().__init_subclass__() + + orig_bases: Tuple[type, ...] = getattr(cls, "__orig_bases__", ()) + for orig_base in orig_bases: + origin_class = get_origin(orig_base) + if inspect.isclass(origin_class) and issubclass(origin_class, Rule): + try: + _event_t, state_t, config_t = cast( + Tuple[EventT, StateT, ConfigT], get_args(orig_base) + ) + except ValueError: # pragma: no cover + continue + if ( + config is None + and inspect.isclass(config_t) + and issubclass(config_t, ConfigModel) + ): + config = config_t # pyright: ignore + if ( + init_state is None + and get_origin(state_t) is Annotated + and hasattr(state_t, "__metadata__") + ): + init_state = state_t.__metadata__[0] # pyright: ignore + + if not hasattr(cls, "Config") and config is not None: + cls.Config = config + if cls.__init_state__ is Rule.__init_state__ and init_state is not None: + cls.__init_state__ = lambda _: init_state # type: ignore + + @final + @property + def name(self) -> str: + """rule class name.""" + return self.__class__.__name__ + + @final + @property + def core(self) -> "Core": + """core object.""" + return self.event.core # pylint: disable=no-member + + @final + @property + def config(self) -> ConfigT: + """rule configuration.""" + default: Any = None + config_class = getattr(self, "Config", None) + if is_config_class(config_class): + return getattr( + self.core.config.rule, + config_class.__config_name__, + default, + ) + return default + + @final + def stop(self) -> NoReturn: + """Stop propagation of current events.""" + raise StopException + + @final + def skip(self) -> NoReturn: + """Skips itself and continues propagation of the current event.""" + raise SkipException + + @property + def state(self) -> StateT: + """rule status.""" + return self.core.rule_state[self.name] + + @state.setter + @final + def state(self, value: StateT) -> None: + self.core.rule_state[self.name] = value + + async def enable(self): ... + + async def disable(self): ... + + @staticmethod + def aliases(names, ignore_case=False): + def decorator(func): + func._aliases = names + func._ignore_case = ignore_case + return func + + return decorator + + @final + async def safe_run(self) -> None: + try: + await self.enable() + except Exception as e: + self.bot.error_or_exception( + f"Enable rule {self.__class__.__name__} failed:", e + ) |
