From 1107dad9f99ef657d5b51ab041accae0e169bfdc Mon Sep 17 00:00:00 2001 From: Elaina Date: Sat, 16 Mar 2024 23:35:17 +0800 Subject: [PATCH 01/24] chore: time to up. --- avilla/core/flywheel/__init__.py | 0 avilla/core/flywheel/context.py | 61 ++++++++++++++++ avilla/core/flywheel/entity.py | 56 ++++++++++++++ avilla/core/flywheel/fn/__init__.py | 0 avilla/core/flywheel/fn/base.py | 75 +++++++++++++++++++ avilla/core/flywheel/fn/compose.py | 83 +++++++++++++++++++++ avilla/core/flywheel/fn/implement.py | 76 +++++++++++++++++++ avilla/core/flywheel/fn/overload.py | 47 ++++++++++++ avilla/core/flywheel/fn/record.py | 20 +++++ avilla/core/flywheel/globals.py | 57 +++++++++++++++ avilla/core/flywheel/instance_of.py | 30 ++++++++ avilla/core/flywheel/overloads.py | 91 +++++++++++++++++++++++ avilla/core/flywheel/scoped.py | 105 +++++++++++++++++++++++++++ avilla/core/flywheel/typing.py | 52 +++++++++++++ 14 files changed, 753 insertions(+) create mode 100644 avilla/core/flywheel/__init__.py create mode 100644 avilla/core/flywheel/context.py create mode 100644 avilla/core/flywheel/entity.py create mode 100644 avilla/core/flywheel/fn/__init__.py create mode 100644 avilla/core/flywheel/fn/base.py create mode 100644 avilla/core/flywheel/fn/compose.py create mode 100644 avilla/core/flywheel/fn/implement.py create mode 100644 avilla/core/flywheel/fn/overload.py create mode 100644 avilla/core/flywheel/fn/record.py create mode 100644 avilla/core/flywheel/globals.py create mode 100644 avilla/core/flywheel/instance_of.py create mode 100644 avilla/core/flywheel/overloads.py create mode 100644 avilla/core/flywheel/scoped.py create mode 100644 avilla/core/flywheel/typing.py diff --git a/avilla/core/flywheel/__init__.py b/avilla/core/flywheel/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/avilla/core/flywheel/context.py b/avilla/core/flywheel/context.py new file mode 100644 index 00000000..5c6f5301 --- /dev/null +++ b/avilla/core/flywheel/context.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from contextlib import contextmanager +from typing import TYPE_CHECKING, Any + +from .typing import TEntity + +if TYPE_CHECKING: + from .fn.record import FnImplement, FnRecord + + +class CollectContext: + fn_implements: dict[FnImplement, FnRecord] + + def __init__(self): + self.fn_implements = {} + + def collect(self, entity: TEntity) -> TEntity: + return entity.collect(self) + + @contextmanager + def collect_scope(self): + from .globals import COLLECTING_CONTEXT_VAR + + token = COLLECTING_CONTEXT_VAR.set(self) + try: + yield self + finally: + COLLECTING_CONTEXT_VAR.reset(token) + + @contextmanager + def lookup_scope(self): + from .globals import LOOKUP_LAYOUT_VAR + + token = LOOKUP_LAYOUT_VAR.set((self, *LOOKUP_LAYOUT_VAR.get())) + try: + yield self + finally: + LOOKUP_LAYOUT_VAR.reset(token) + + +class InstanceContext: + instances: dict[type, Any] + + def __init__(self): + self.instances = {} + + @contextmanager + def scope(self, *, inherit: bool = True): + from .globals import INSTANCE_CONTEXT_VAR + + original = self.instances + if inherit: + self.instances = {**INSTANCE_CONTEXT_VAR.get().instances, **self.instances} + + token = INSTANCE_CONTEXT_VAR.set(self) + try: + yield self + finally: + INSTANCE_CONTEXT_VAR.reset(token) + self.instances = original diff --git a/avilla/core/flywheel/entity.py b/avilla/core/flywheel/entity.py new file mode 100644 index 00000000..c0316832 --- /dev/null +++ b/avilla/core/flywheel/entity.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, TypedDict, final + +from .scoped import scoped_collect + +if TYPE_CHECKING: + from .context import CollectContext + + +class EntityAssignInfo(TypedDict, total=False): + cls: type + name: str + annotation: Any + + +class BaseEntity: + collect_context: CollectContext | None = None + + def collect(self, collector: CollectContext): + self.collect_context = collector + + if isinstance(self.collect_context, scoped_collect): + self.collect_context.on_collected(self._fallback_collected_callback) + + return self + + def assign_callback(self, info: EntityAssignInfo): + ... + + @final + def _fallback_collected_callback(self, collector): + self.assign_callback({}) + + def __set_name__(self, owner: type, name: str): + if self.collect_context is None: + return + + if isinstance(self.collect_context, scoped_collect): + if owner is not self.collect_context.cls: + return + + try: + self.collect_context.remove_collected_callback(self._fallback_collected_callback) + except ValueError: + pass + + @self.collect_context.on_collected + def _(collector: scoped_collect): + if collector.cls is None: + return + + if name in collector.cls.__annotations__: + self.assign_callback({"cls": collector.cls, "name": name, "annotation": collector.cls.__annotations__[name]}) + else: + self.assign_callback({"cls": collector.cls, "name": name}) diff --git a/avilla/core/flywheel/fn/__init__.py b/avilla/core/flywheel/fn/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/avilla/core/flywheel/fn/base.py b/avilla/core/flywheel/fn/base.py new file mode 100644 index 00000000..45428e66 --- /dev/null +++ b/avilla/core/flywheel/fn/base.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Callable, Generic, Protocol, Type, TypeVar + +from typing_extensions import Concatenate + +from ..entity import BaseEntity +from ..globals import iter_layout +from ..typing import CR, AssignKeeper, Call, InP, OutP, P, R +from .compose import FnCompose +from .implement import FnImplementEntity, OverloadRecorder + +if TYPE_CHECKING: + from .record import FnRecord + +K = TypeVar("K") + +CCall = TypeVar("CCall", bound=Callable, covariant=True) +CCollect = TypeVar("CCollect", bound=Callable, covariant=True) + + +class ComposeShape(Protocol[CCollect, CCall]): + @property + def collect(self) -> CCollect: + ... + + @property + def call(self) -> CCall: + ... + + +FnDef = Type[ComposeShape[Callable[InP, None], Callable[Concatenate["FnRecord", OutP], R]]] + + +class Fn(Generic[CCollect, CCall], BaseEntity): + desc: FnCompose + + def __init__(self, compose: type[FnCompose]): + self.desc = compose(self) + + @classmethod + def declare(cls, desc: FnDef[InP, OutP, R]) -> Fn[Callable[InP, None], Callable[OutP, R]]: + return cls(desc) # type: ignore + + @property + def impl( + self: Fn[Callable[Concatenate[OverloadRecorder[CR], OutP], Any], Any], + ) -> Callable[OutP, AssignKeeper[Call[..., CR]]]: + def wrapper(*args: OutP.args, **kwargs: OutP.kwargs): + def inner(impl: Callable[P, R] | FnImplementEntity[Callable[P, R]]): + if not isinstance(impl, FnImplementEntity): + impl = FnImplementEntity(impl) + + impl.add_target(self, *args, **kwargs) + return impl + + return inner + + return wrapper # type: ignore + + def _call(self, *args, **kwargs): + signature = self.desc.signature() + + for context in iter_layout(signature): + if signature not in context.fn_implements: + continue + + record = context.fn_implements[signature] + return record.spec.desc.call(record, *args, **kwargs) + else: + raise NotImplementedError("cannot find any record with given fn declaration") + + @property + def __call__(self) -> CCall: + return self._call # type: ignore diff --git a/avilla/core/flywheel/fn/compose.py b/avilla/core/flywheel/fn/compose.py new file mode 100644 index 00000000..a32d00cd --- /dev/null +++ b/avilla/core/flywheel/fn/compose.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +from functools import reduce +from typing import TYPE_CHECKING, Any, Callable, Final, Generic, TypeVar, overload + +from typing_extensions import Concatenate + +from ..overloads import SINGLETON_OVERLOAD +from ..typing import CT, P1, Collectable, ImplementSample, P, R +from .record import FnImplement, FnRecord + +if TYPE_CHECKING: + from .base import Fn + from .implement import OverloadRecorder + +FC = TypeVar("FC", bound="FnCompose") + + +class FnCompose: + singleton: Final = SINGLETON_OVERLOAD + fn: Fn + + def __init__(self, fn: Fn): + self.fn = fn + + def call(self, record: FnRecord, *args, **kwargs) -> Any: + return next(iter(SINGLETON_OVERLOAD.dig(record, None).keys()))(*args, **kwargs) + + def collect(self, recorder: OverloadRecorder) -> None: + recorder.use(self.singleton, None) + + def signature(self): + return FnImplement(self.fn) + + @overload + def load(self: ImplementSample[CT], *collections: dict[Callable, None]) -> HarvestWrapper[CT]: + ... + + @overload + def load( + self: Collectable[Concatenate[OverloadRecorder[Callable[P, R]], P1]], *collections: dict[Callable, None] + ) -> HarvestWrapper[Callable[P, R]]: + ... + + def load(self, *collections: dict[Callable, None]): # type: ignore + if not collections: + raise TypeError("at least one collection is required") + + col = list(collections) + init = col.pop(0) + + if not col: + return HarvestWrapper(init) + + r = reduce(lambda x, y: {i: None for i in x if i in y}, col, init) + return HarvestWrapper(r) + + +class HarvestWrapper(Generic[CT]): + harvest: dict[CT, None] + + def __init__(self, harvest: dict[CT, None]): + self.harvest = harvest + + @property + def first(self) -> CT: + if not self.harvest: + raise NotImplementedError("cannot lookup any implementation with given arguments") + + return next(iter(self.harvest)) + + @property + def __call__(self): + return self.first + + def __iter__(self): + if not self.harvest: + raise NotImplementedError("cannot lookup any implementation with given arguments") + + return iter(self.harvest) + + def __bool__(self): + return bool(self.harvest) diff --git a/avilla/core/flywheel/fn/implement.py b/avilla/core/flywheel/fn/implement.py new file mode 100644 index 00000000..3fc25c94 --- /dev/null +++ b/avilla/core/flywheel/fn/implement.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any, Callable, Generic + +from typing_extensions import Concatenate + +from ..entity import BaseEntity +from ..scoped import scoped_collect +from ..typing import CR, P +from .record import FnRecord + +if TYPE_CHECKING: + from .base import Fn + from .overload import FnOverload, TCollectValue + + +@dataclass(slots=True) +class OverloadRecorder(Generic[CR]): + target: FnRecord + implement: CR + operators: list[tuple[str, FnOverload, Any]] = field(default_factory=list) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.done() + + def done(self): + for name, rule, value in self.operators: + rule.lay(self.target, value, self.implement, name=name) + + self.target.entities[frozenset(self.operators)] = self.implement + + def use(self, target: FnOverload[Any, TCollectValue, Any], value: TCollectValue, *, name: str | None = None): + self.operators.append((name or target.name, target, value)) + return self + + +class FnImplementEntity(Generic[CR], BaseEntity): + targets: list[tuple[Fn, tuple[Any, ...], dict[str, Any]]] + impl: CR + + def __init__(self, impl: CR): + self.targets = [] + self.impl = impl + + def add_target(self, fn: Fn[Callable[Concatenate[Any, P], Any], Any], *args: P.args, **kwargs: P.kwargs): + self.targets.append((fn, args, kwargs)) + + def collect(self, collector: scoped_collect): + super().collect(collector) + + for fn, args, kwargs in self.targets: + record_signature = fn.desc.signature() + if record_signature in collector.fn_implements: + record = collector.fn_implements[record_signature] + else: + record = collector.fn_implements[record_signature] = FnRecord(fn) + + with OverloadRecorder(record, self.impl) as recorder: + fn.desc.collect(recorder, *args, **kwargs) + + return self + + def _call(self, *args, **kwargs): + return self.impl(*args, **kwargs) + + @property + def __call__(self) -> CR: + return self._call # type: ignore + + @property + def super(self) -> CR: + return self.targets[0][0].__call__ diff --git a/avilla/core/flywheel/fn/overload.py b/avilla/core/flywheel/fn/overload.py new file mode 100644 index 00000000..20fe411e --- /dev/null +++ b/avilla/core/flywheel/fn/overload.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, Generic, TypeVar + +from typing_extensions import final + +if TYPE_CHECKING: + from .record import FnRecord + +TOverload = TypeVar("TOverload", bound="FnOverload", covariant=True) +TCallValue = TypeVar("TCallValue") +TCollectValue = TypeVar("TCollectValue") +TSignature = TypeVar("TSignature") + + +class FnOverload(Generic[TSignature, TCollectValue, TCallValue]): + def __init__(self, name: str) -> None: + self.name = name + + @final + def dig(self, record: FnRecord, call_value: TCallValue, *, name: str | None = None) -> dict[Callable, None]: + name = name or self.name + if name not in record.scopes: + raise NotImplementedError("cannot lookup any implementation with given arguments") + + return self.harvest(record.scopes[name], call_value) + + @final + def lay(self, record: FnRecord, collect_value: TCollectValue, implement: Callable, *, name: str | None = None): + name = name or self.name + if name not in record.scopes: + record.scopes[name] = {} + + collection = self.collect(record.scopes[name], self.digest(collect_value)) + collection[implement] = None + + def digest(self, collect_value: TCollectValue) -> TSignature: + raise NotImplementedError + + def collect(self, scope: dict, signature: TSignature) -> dict[Callable, None]: + raise NotImplementedError + + def harvest(self, scope: dict, call_value: TCallValue) -> dict[Callable, None]: + raise NotImplementedError + + def access(self, scope: dict, signature: TSignature) -> dict[Callable, None] | None: + raise NotImplementedError diff --git a/avilla/core/flywheel/fn/record.py b/avilla/core/flywheel/fn/record.py new file mode 100644 index 00000000..d6c8a2b4 --- /dev/null +++ b/avilla/core/flywheel/fn/record.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any, Callable + +if TYPE_CHECKING: + from .base import Fn + from .overload import FnOverload + + +@dataclass(slots=True) +class FnRecord: + spec: Fn + scopes: dict[str, dict[Any, Any]] = field(default_factory=dict) + entities: dict[frozenset[tuple[str, "FnOverload", Any]], Callable] = field(default_factory=dict) + + +@dataclass(slots=True, unsafe_hash=True) +class FnImplement: + fn: Fn diff --git a/avilla/core/flywheel/globals.py b/avilla/core/flywheel/globals.py new file mode 100644 index 00000000..a1c40a81 --- /dev/null +++ b/avilla/core/flywheel/globals.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +import functools +from collections import defaultdict +from contextvars import ContextVar, copy_context +from typing import Any, Callable, Generator, Tuple + +from .context import CollectContext, InstanceContext +from .typing import P, R, TEntity + +GLOBAL_COLLECT_CONTEXT = CollectContext() +GLOBAL_INSTANCE_CONTEXT = InstanceContext() + +COLLECTING_CONTEXT_VAR = ContextVar("CollectingContext", default=GLOBAL_COLLECT_CONTEXT) +LOOKUP_LAYOUT_VAR = ContextVar[Tuple[CollectContext, ...]]("LookupContext", default=(GLOBAL_COLLECT_CONTEXT,)) +INSTANCE_CONTEXT_VAR = ContextVar("InstanceContext", default=GLOBAL_INSTANCE_CONTEXT) + +ITER_BUCKET_VAR: ContextVar[defaultdict[Any, list[int]]] = ContextVar("LAYOUT_ITER_COLLECTIONS") + + +def _standalone_context(func: Callable[P, R]) -> Callable[P, R]: + @functools.wraps(func) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + return copy_context().run(func, *args, **kwargs) + + return wrapper + + +@_standalone_context +def iter_layout(session_id: Any | None = None) -> Generator[CollectContext, None, None]: + bucket = ITER_BUCKET_VAR.get(None) + if bucket is None: + bucket = defaultdict(lambda: [-1]) + ITER_BUCKET_VAR.set(bucket) + + stack = bucket[session_id] + + contexts = LOOKUP_LAYOUT_VAR.get() + index = stack[-1] + stack.append(index) + + try: + for content in contexts[index + 1 :]: + stack[-1] += 1 + yield content + finally: + stack.pop() + if not stack: + bucket.pop(session_id, None) + + +def global_collect(entity: TEntity) -> TEntity: + return GLOBAL_COLLECT_CONTEXT.collect(entity) + + +def local_collect(entity: TEntity) -> TEntity: + return COLLECTING_CONTEXT_VAR.get().collect(entity) diff --git a/avilla/core/flywheel/instance_of.py b/avilla/core/flywheel/instance_of.py new file mode 100644 index 00000000..e84abf6e --- /dev/null +++ b/avilla/core/flywheel/instance_of.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from typing import Any, Generic, TypeVar, overload + +from typing_extensions import Self + +from .globals import INSTANCE_CONTEXT_VAR + +T = TypeVar("T") + + +class InstanceOf(Generic[T]): + target: type[T] + + def __init__(self, target: type[T]) -> None: + self.target = target + + @overload + def __get__(self, instance: None, owner: type) -> Self: + ... + + @overload + def __get__(self, instance: Any, owner: type) -> T: + ... + + def __get__(self, instance: Any, owner: type): + if instance is None: + return self + + return INSTANCE_CONTEXT_VAR.get().instances[self.target] diff --git a/avilla/core/flywheel/overloads.py b/avilla/core/flywheel/overloads.py new file mode 100644 index 00000000..29ce872c --- /dev/null +++ b/avilla/core/flywheel/overloads.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Callable, Type + +from .fn.overload import FnOverload + + +@dataclass(eq=True, frozen=True) +class SimpleOverloadSignature: + value: Any + + +class SimpleOverload(FnOverload[SimpleOverloadSignature, Any, Any]): + def digest(self, collect_value: Any) -> SimpleOverloadSignature: + return SimpleOverloadSignature(collect_value) + + def collect(self, scope: dict, signature: SimpleOverloadSignature) -> dict[Callable, None]: + if signature.value not in scope: + target = scope[signature.value] = {} + else: + target = scope[signature.value] + + return target + + def harvest(self, scope: dict, call_value: Any) -> dict[Callable, None]: + if call_value in scope: + return scope[call_value] + + return {} + + def access(self, scope: dict, signature: SimpleOverloadSignature) -> dict[Callable, None] | None: + if signature.value in scope: + return scope[signature.value] + + +@dataclass(eq=True, frozen=True) +class TypeOverloadSignature: + type: type[Any] + + +class TypeOverload(FnOverload[TypeOverloadSignature, Type[Any], Any]): + def digest(self, collect_value: type) -> TypeOverloadSignature: + return TypeOverloadSignature(collect_value) + + def collect(self, scope: dict, signature: TypeOverloadSignature) -> dict[Callable, None]: + if signature.type not in scope: + target = scope[signature.type] = {} + else: + target = scope[signature.type] + + return target + + def harvest(self, scope: dict, call_value: Any) -> dict[Callable, None]: + t = type(call_value) + if t in scope: + return scope[t] + + return {} + + def access(self, scope: dict, signature: TypeOverloadSignature) -> dict[Callable, None] | None: + if signature.type in scope: + return scope[signature.type] + + +class _SingletonOverloadSignature: + ... + + +SINGLETON_SIGN = _SingletonOverloadSignature() + + +class SingletonOverload(FnOverload[_SingletonOverloadSignature, None, None]): + SIGNATURE = SINGLETON_SIGN + + def digest(self, collect_value) -> _SingletonOverloadSignature: + return SINGLETON_SIGN + + def collect(self, scope: dict, signature) -> dict[Callable, None]: + s = scope[None] = {} + return s + + def harvest(self, scope: dict, call_value) -> dict[Callable, None]: + return scope[None] + + def access(self, scope: dict, signature) -> dict[Callable, None] | None: + if None in scope: + return scope[None] + + +SINGLETON_OVERLOAD = SingletonOverload("singleton") diff --git a/avilla/core/flywheel/scoped.py b/avilla/core/flywheel/scoped.py new file mode 100644 index 00000000..3be3d743 --- /dev/null +++ b/avilla/core/flywheel/scoped.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +import functools +from typing import Any, Callable + +from .context import CollectContext +from .globals import COLLECTING_CONTEXT_VAR, GLOBAL_COLLECT_CONTEXT, GLOBAL_INSTANCE_CONTEXT, INSTANCE_CONTEXT_VAR +from .typing import TYPE_CHECKING, AssignKeeperCls, P, R, TEntity + +if TYPE_CHECKING: + from typing_extensions import Concatenate + + from .fn.base import Call, Fn + from .fn.implement import FnImplementEntity + from .fn.record import FnImplement, FnRecord + from .typing import CR, OutP + + +class scoped_collect(CollectContext): + fn_implements: dict[FnImplement, FnRecord] + _tocollect_list: dict[FnImplementEntity, None] + finalize_cbs: list[Callable[[scoped_collect], Any]] + cls: type | None = None + + def __init__(self) -> None: + self.fn_implements = {} + self.finalize_cbs = [] + self._tocollect_list = {} + + @classmethod + def globals(cls): + instance = cls() + instance.fn_implements = GLOBAL_COLLECT_CONTEXT.fn_implements + return instance + + @classmethod + def env(cls): + instance = cls() + instance.fn_implements = COLLECTING_CONTEXT_VAR.get().fn_implements + return instance + + def finalize(self): + for cb in self.finalize_cbs: + cb(self) + + for impl in self._tocollect_list: + impl.collect(self) + + def on_collected(self, func: Callable[[scoped_collect], Any]): + self.finalize_cbs.append(func) + return func + + def remove_collected_callback(self, func: Callable[[scoped_collect], Any]): + self.finalize_cbs.remove(func) + return func + + @property + def target(self): + from .fn.implement import FnImplementEntity + + class LocalEndpoint: + collector = self + + def __init_subclass__(cls, *, static: bool = False) -> None: + self.cls = cls + self.finalize() + + if static: + GLOBAL_INSTANCE_CONTEXT.instances[cls] = cls() + + @staticmethod + def collect(entity: TEntity) -> TEntity: # type: ignore + return entity.collect(self) + + @staticmethod + def ensure_self(func: Callable[Concatenate[Any, P], R]) -> Callable[P, R]: + @functools.wraps(func) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + assert self.cls is not None + + instance = INSTANCE_CONTEXT_VAR.get().instances[self.cls] + return func(instance, *args, **kwargs) + + return wrapper + + @staticmethod + def impl( + fn: Fn[Callable[Concatenate[CR, OutP], Any], Any], *args: OutP.args, **kwargs: OutP.kwargs + ) -> AssignKeeperCls[Call[..., CR]]: + def inner(impl: Callable[Concatenate[Any, P], R] | FnImplementEntity[Callable[P, R]]): + if not isinstance(impl, FnImplementEntity): + if not hasattr(impl, "__wrapped__"): + impl_call = LocalEndpoint.ensure_self(impl) + else: + impl_call: Callable[P, R] = impl # type: ignore + + impl = FnImplementEntity(impl_call) + + impl.add_target(fn, *args, **kwargs) + self._tocollect_list[impl] = None + return impl + + return inner # type: ignore + + return LocalEndpoint diff --git a/avilla/core/flywheel/typing.py b/avilla/core/flywheel/typing.py new file mode 100644 index 00000000..4170b2b6 --- /dev/null +++ b/avilla/core/flywheel/typing.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Callable, Protocol, TypeVar + +from typing_extensions import Concatenate, ParamSpec + +if TYPE_CHECKING: + from .entity import BaseEntity + from .fn.implement import FnImplementEntity + + +R = TypeVar("R", covariant=True) +R1 = TypeVar("R1", covariant=True) +P = ParamSpec("P") +P1 = ParamSpec("P1") +InP = ParamSpec("InP") +OutP = ParamSpec("OutP") +CR = TypeVar("CR", covariant=True, bound=Callable) +CT = TypeVar("CT", bound=Callable) +TEntity = TypeVar("TEntity", bound="BaseEntity") + + +class Collectable(Protocol[InP]): + def collect(self, *args: InP.args, **kwargs: InP.kwargs) -> Any: + ... + + +class Call(Protocol[P, R]): + def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: + ... + + +class AssignKeeper(Protocol[R]): + def __call__( + self: AssignKeeper[Call[..., Callable[P1, R1]]], + implement: Callable[P1, R1] | FnImplementEntity[Callable[P1, R1]], + ) -> FnImplementEntity[Callable[P1, R1]]: + ... + + +class AssignKeeperCls(Protocol[R]): + def __call__( + self: AssignKeeperCls[Call[..., Callable[P1, R1]]], + implement: Callable[Concatenate[Any, P1], R1] | FnImplementEntity[Callable[P1, R1]], + ) -> FnImplementEntity[Callable[P1, R1]]: + ... + + +class ImplementSample(Protocol[CR]): + @property + def implement_sample(self) -> CR: + ... From cc13ecb04d183893b21646d188331b7193c37439 Mon Sep 17 00:00:00 2001 From: Elaina Date: Sat, 16 Mar 2024 23:42:59 +0800 Subject: [PATCH 02/24] fix: remove slots --- avilla/core/flywheel/fn/implement.py | 2 +- avilla/core/flywheel/fn/record.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/avilla/core/flywheel/fn/implement.py b/avilla/core/flywheel/fn/implement.py index 3fc25c94..ea443302 100644 --- a/avilla/core/flywheel/fn/implement.py +++ b/avilla/core/flywheel/fn/implement.py @@ -15,7 +15,7 @@ from .overload import FnOverload, TCollectValue -@dataclass(slots=True) +@dataclass class OverloadRecorder(Generic[CR]): target: FnRecord implement: CR diff --git a/avilla/core/flywheel/fn/record.py b/avilla/core/flywheel/fn/record.py index d6c8a2b4..edf744a9 100644 --- a/avilla/core/flywheel/fn/record.py +++ b/avilla/core/flywheel/fn/record.py @@ -8,13 +8,13 @@ from .overload import FnOverload -@dataclass(slots=True) +@dataclass(eq=True, frozen=True) class FnRecord: spec: Fn scopes: dict[str, dict[Any, Any]] = field(default_factory=dict) entities: dict[frozenset[tuple[str, "FnOverload", Any]], Callable] = field(default_factory=dict) -@dataclass(slots=True, unsafe_hash=True) +@dataclass(eq=True, frozen=True) class FnImplement: fn: Fn From fc953695b7fd086d6e95b40a90b6610566fdc821 Mon Sep 17 00:00:00 2001 From: Elaina Date: Sun, 17 Mar 2024 00:58:17 +0800 Subject: [PATCH 03/24] docs(wip): flywheel-migration --- docs/flywheel-migration.md | 144 +++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/flywheel-migration.md diff --git a/docs/flywheel-migration.md b/docs/flywheel-migration.md new file mode 100644 index 00000000..e7c5ad19 --- /dev/null +++ b/docs/flywheel-migration.md @@ -0,0 +1,144 @@ +# Migrate to Flywheel + +Flywheel 致力于用轻巧灵活的设计来迅捷而准确的构筑 Avilla 的所有设施,至少可以成为其中一些的重要基础;其部分内容将直接面向用户对接暴露。 + +对于被分配至此任务的开发者,需要先阅读 [Flywheel 的文档](https://github.com/GreyElaina/RvFlywheel#readme)。 +同时,考虑到 Flywheel 的体量极小,也建议配合文档阅读其代码,透过此了解其基本原理。 + +## Todo List + +- Overloads + - [ ] `SelectorOverload`,取代现有的 `TargetOverload` 设施。 + - ~~`QueryOverload`~~,参考下文详细叙述。 + - ~~`MetadataOverload`~~,已可被 Flywheel 中的 `SimpleOverload` 取代。 + - ~~`ResourceOverload`~~,已可被 Flywheel 中的 `TypeOverload` 或 `SimpleOverload` 取代。 + +### Overloads + +Overload 描述对单个参数的重载规则与模式。 + +#### 关于 Query + +Avilla 中的 Query 机制致力于通过有规则的构造 Selector,以提供发现其他实体的方法。 + +在 Ryanvk v1.2 及之前版本中,这一行为需要直接从底层逻辑编写,但 Flywheel 的 Overload 形式或许提供了新的,更高效的方法。 + +```python +@Fn.declare +class query(FnCompose): + src = TargetOverload("src") + dst = TargetOverload("dst") +``` + +我们规定当注册实现...按照 Flywheel 的说法,应该是*收集*实现,我们总是获得完整的 `src` 与 `dst` 指定。 + +```python +@query.impl(src="::", dst="::group") + +@query.impl(src="::group", dst="::group.member") + +@query.impl(src="::group.member", dst="::group.member.message") +``` + +通过一定的算法,我们可以构造一条从 `::` 通向 `::group.member.message` 的道路, +这对于目前 Flywheel 来说不成问题。 + +### 基类 + +- [ ] Core +- 协议实现 + - [ ] qqapi + - [ ] onebot + - [ ] satori + - and more... + +在 Flywheel 中,我们需要构造一些包含了 `InstanceOf` 描述的基类,并让我们的实现继承。 +`scoped_context` 的构造虽然可以使用非静态方法(`static=False`), +但在 Avilla 中这种方法可以被更易用,且更适合操作的 `InstanceOf + InstanceContext` 完成。 + +在现有 Ryanvk 1.2 情况中,我们通常使用 `avilla.core.ryanvk.collector` 中的各式 `Collector` 来辅助指定所使用的类型。 + +```python +# file: avilla.qqapi.perform.context + +class QQAPIContextPerform((m := AccountCollector["QQAPIProtocol", "QQAPIAccount"]())._): + ... +``` + +这在 Flywheel 中不再推荐,我们现在推荐使用这种形式。 + +```python +# file: avilla.qqapi.perform.base +class QQAPIPerformAccountBase: + protocol = InstanceOf(QQAPIProtocol) + account = InstanceOf(QQAPIAccount) + +# file: avilla.qqapi.perform.account +class QQAPIAccountPerform(m := scoped_context().target, QQAPIPerformAccountBase): + @some_fn.impl(...) + async def some_impl(self): + reveal_type(self.protocol) + reveal_type(self.account) +``` + +由于我们一直以来推荐使用 *lazy loading* 模式,这套体系可以良好的组织以避免 circular import 的问题。 + +### Staff + +在 Flywheel 中已经不存在 `Staff`,因为不再需要这样来封装对 `Fn` 的复杂封装。 + +相对的,我们建议协议实现的维护者提供形似 `TelegramAction` 的集合类来方便进行各种内部与外部的操作,如同之前的形似 `TelegramCapability` 的各式 Capability,只是这个名字更切题。 + +```python +class TelegramAction: + @Fn.declare + class serialize_message(FnCompose): + ... + + @Fn.declare + class deserialize_message(FnCompose): + ... + + @Fn.declare + class parse_event(FnCompose): + ... +``` + +我们推荐在行为集合声明类中仅声明 `Fn` 的操作,而不是各种静态方法全部塞里面,那样的情况更适合存放在单独模块内。 + +因为 `FnOverload` 并不拘束于仅能在 `FnCompose` 内声明,你可以考虑在形似 `TelegramAction` 处声明共用的 `Overload` 集合。 + +当调用时,我们只需要根据类型检查器的提示使用即可,但注意,你依旧需要考虑前面提到过的 `InstanceOf` 情况,使用 `InstanceContext.scope` 上下文管理器注入。 + +另外,`InstanceContext` 可以长久持有,你完全可以直接在 `__init__` 之类的地方创建好,然后直接用。 +这对于后面提到的 `CollectContext` 也是类似管用的。 + +```python +# when init +self.instance_ctx = InstanceContext() +self.instance_ctx[TelegramNetwork] = self + +# when received data +with connection.instance_ctx: + TelegramAction.parse_event(data) +``` + +### Endpoint + +本来应该有的,现在已经被 `InstanceOf`…… 好像本来就是。 + +### 具体实现的模块如何导入 + +我们推荐声明一个 `_import_performs` 方法,然后在 `Protocol.ensure` 配合 `CollectContext` 使用。 + +当然,前提是你里面的全部用了 `@local_collect` 或是 `scoped_context.env()`。 + +```python +def _import_performs(): + ... + +class Protocol: + def ensure(self, avilla): + with CollectContext() as self.perform_context: + _import_performs() +``` From 6ebc3c3cfd5911cc33803cb1af5da35d1da1e673 Mon Sep 17 00:00:00 2001 From: Elaina Date: Sun, 17 Mar 2024 00:59:47 +0800 Subject: [PATCH 04/24] docs: fix impossible call --- docs/flywheel-migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/flywheel-migration.md b/docs/flywheel-migration.md index e7c5ad19..fc43c066 100644 --- a/docs/flywheel-migration.md +++ b/docs/flywheel-migration.md @@ -74,7 +74,7 @@ class QQAPIPerformAccountBase: account = InstanceOf(QQAPIAccount) # file: avilla.qqapi.perform.account -class QQAPIAccountPerform(m := scoped_context().target, QQAPIPerformAccountBase): +class QQAPIAccountPerform(m := scoped_context.env().target, QQAPIPerformAccountBase): @some_fn.impl(...) async def some_impl(self): reveal_type(self.protocol) From f8135f453364f03a10a1bf49471fd633e93b00f5 Mon Sep 17 00:00:00 2001 From: Elaina Date: Mon, 18 Mar 2024 17:35:40 +0800 Subject: [PATCH 05/24] feat: sync flywheel migrate process --- avilla/console/capability.py | 2 +- avilla/console/perform/action/activity.py | 2 +- avilla/console/perform/action/message.py | 2 +- avilla/console/perform/action/profile.py | 2 +- avilla/console/perform/context.py | 2 +- avilla/console/perform/event/message.py | 2 +- avilla/console/perform/message/deserialize.py | 2 +- avilla/console/perform/message/serialize.py | 2 +- avilla/core/account.py | 2 +- avilla/core/application.py | 2 +- avilla/core/builtins/capability.py | 151 ++++++++++++++---- avilla/core/builtins/resource_fetch.py | 23 +-- avilla/core/context/__init__.py | 4 +- avilla/core/context/_roles.py | 18 +-- avilla/core/context/_selector.py | 2 +- avilla/core/flywheel/fn/base.py | 7 +- avilla/core/flywheel/scoped.py | 3 +- avilla/core/ryanvk/__init__.py | 13 -- avilla/core/ryanvk/overloads.py | 113 +++++++++++++ avilla/core/ryanvk_old/__init__.py | 13 ++ .../behavior/__init__.py | 0 .../{ryanvk => ryanvk_old}/behavior/query.py | 2 +- .../collector/__init__.py | 0 .../collector/account.py | 0 .../collector/application.py | 0 .../{ryanvk => ryanvk_old}/collector/base.py | 0 .../collector/context.py | 0 .../collector/protocol.py | 0 .../descriptor/__init__.py | 0 .../descriptor/query.py | 0 .../endpoint/__init__.py | 0 .../endpoint/launart.py | 0 .../overload/__init__.py | 0 .../overload/metadata.py | 0 .../{ryanvk => ryanvk_old}/overload/target.py | 0 avilla/core/{ryanvk => ryanvk_old}/staff.py | 0 avilla/core/{ryanvk => ryanvk_old}/util.py | 0 avilla/elizabeth/capability.py | 2 +- avilla/elizabeth/collector/connection.py | 2 +- avilla/elizabeth/connection/base.py | 2 +- avilla/elizabeth/file/capability.py | 2 +- avilla/elizabeth/perform/action/activity.py | 2 +- .../elizabeth/perform/action/announcement.py | 2 +- avilla/elizabeth/perform/action/contact.py | 2 +- avilla/elizabeth/perform/action/file.py | 2 +- avilla/elizabeth/perform/action/friend.py | 2 +- avilla/elizabeth/perform/action/group.py | 2 +- avilla/elizabeth/perform/action/member.py | 2 +- avilla/elizabeth/perform/action/message.py | 2 +- avilla/elizabeth/perform/action/request.py | 2 +- avilla/elizabeth/perform/context.py | 2 +- .../elizabeth/perform/message/deserialize.py | 2 +- avilla/elizabeth/perform/message/serialize.py | 2 +- .../elizabeth/perform/query/announcement.py | 2 +- avilla/elizabeth/perform/query/bot.py | 2 +- avilla/elizabeth/perform/query/file.py | 2 +- avilla/elizabeth/perform/query/friend.py | 2 +- avilla/elizabeth/perform/query/group.py | 2 +- avilla/elizabeth/perform/resource_fetch.py | 2 +- avilla/nonebridge/capability.py | 2 +- avilla/nonebridge/perform/event/message.py | 2 +- avilla/nonebridge/service.py | 2 +- avilla/onebot/v11/capability.py | 2 +- avilla/onebot/v11/collector/connection.py | 2 +- avilla/onebot/v11/net/base.py | 2 +- avilla/onebot/v11/perform/action/admin.py | 2 +- avilla/onebot/v11/perform/action/ban.py | 2 +- avilla/onebot/v11/perform/action/leave.py | 2 +- avilla/onebot/v11/perform/action/message.py | 2 +- avilla/onebot/v11/perform/action/mute.py | 2 +- avilla/onebot/v11/perform/context.py | 2 +- .../onebot/v11/perform/message/deserialize.py | 2 +- .../onebot/v11/perform/message/serialize.py | 2 +- avilla/onebot/v11/perform/query/group.py | 2 +- avilla/onebot/v11/perform/resource_fetch.py | 2 +- avilla/qqapi/capability.py | 4 +- avilla/qqapi/collector/connection.py | 2 +- avilla/qqapi/connection/base.py | 2 +- avilla/qqapi/perform/action/channel.py | 2 +- avilla/qqapi/perform/action/guild.py | 2 +- avilla/qqapi/perform/action/guild_member.py | 2 +- avilla/qqapi/perform/action/message.py | 2 +- avilla/qqapi/perform/action/role.py | 2 +- avilla/qqapi/perform/action/user.py | 2 +- avilla/qqapi/perform/context.py | 2 +- avilla/qqapi/perform/message/deserialize.py | 2 +- avilla/qqapi/perform/message/serialize.py | 2 +- avilla/qqapi/perform/query.py | 2 +- avilla/qqapi/perform/resource_fetch.py | 2 +- avilla/qqapi/role/capability.py | 2 +- avilla/red/capability.py | 4 +- avilla/red/collector/connection.py | 2 +- avilla/red/net/base.py | 2 +- avilla/red/perform/action/friend.py | 2 +- avilla/red/perform/action/group.py | 2 +- avilla/red/perform/action/member.py | 2 +- avilla/red/perform/action/message.py | 2 +- avilla/red/perform/context.py | 2 +- avilla/red/perform/message/deserialize.py | 2 +- avilla/red/perform/message/serialize.py | 2 +- avilla/red/perform/query.py | 2 +- avilla/red/perform/resource_fetch.py | 2 +- avilla/satori/capability.py | 2 +- avilla/satori/collector/connection.py | 2 +- avilla/satori/perform/action/message.py | 2 +- avilla/satori/perform/action/request.py | 2 +- avilla/satori/perform/context.py | 2 +- avilla/satori/perform/message/deserialize.py | 2 +- avilla/satori/perform/message/serialize.py | 2 +- avilla/satori/perform/resource_fetch.py | 2 +- avilla/satori/service.py | 2 +- avilla/standard/core/activity/capability.py | 2 +- avilla/standard/core/message/capability.py | 2 +- avilla/standard/core/privilege/capability.py | 2 +- avilla/standard/core/profile/capability.py | 2 +- avilla/standard/core/relation/capability.py | 2 +- avilla/standard/core/request/capability.py | 2 +- avilla/standard/qq/announcement/capability.py | 2 +- docs/flywheel-migration.md | 2 +- 119 files changed, 365 insertions(+), 172 deletions(-) create mode 100644 avilla/core/ryanvk/overloads.py create mode 100644 avilla/core/ryanvk_old/__init__.py rename avilla/core/{ryanvk => ryanvk_old}/behavior/__init__.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/behavior/query.py (93%) rename avilla/core/{ryanvk => ryanvk_old}/collector/__init__.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/collector/account.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/collector/application.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/collector/base.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/collector/context.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/collector/protocol.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/descriptor/__init__.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/descriptor/query.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/endpoint/__init__.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/endpoint/launart.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/overload/__init__.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/overload/metadata.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/overload/target.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/staff.py (100%) rename avilla/core/{ryanvk => ryanvk_old}/util.py (100%) diff --git a/avilla/console/capability.py b/avilla/console/capability.py index 76dd7a50..65ee115c 100644 --- a/avilla/console/capability.py +++ b/avilla/console/capability.py @@ -7,7 +7,7 @@ from nonechat.message import Element as ConsoleElement from avilla.core.event import AvillaEvent -from avilla.core.ryanvk.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.collector.application import ApplicationCollector from graia.ryanvk import Fn, TypeOverload CE = TypeVar("CE", bound=ConsoleElement) diff --git a/avilla/console/perform/action/activity.py b/avilla/console/perform/action/activity.py index 2a9a65a7..39fa4e98 100644 --- a/avilla/console/perform/action/activity.py +++ b/avilla/console/perform/action/activity.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.activity import ActivityTrigger diff --git a/avilla/console/perform/action/message.py b/avilla/console/perform/action/message.py index 06317a1a..a3dda3b7 100644 --- a/avilla/console/perform/action/message.py +++ b/avilla/console/perform/action/message.py @@ -12,7 +12,7 @@ from avilla.console.capability import ConsoleCapability from avilla.core.context import Context from avilla.core.message import Message -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.message import MessageSend, MessageSent diff --git a/avilla/console/perform/action/profile.py b/avilla/console/perform/action/profile.py index d1e5352d..d1f4e4e7 100644 --- a/avilla/console/perform/action/profile.py +++ b/avilla/console/perform/action/profile.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.profile import Nick, Summary diff --git a/avilla/console/perform/context.py b/avilla/console/perform/context.py index 8b6d0a7d..58cb7bbd 100644 --- a/avilla/console/perform/context.py +++ b/avilla/console/perform/context.py @@ -4,7 +4,7 @@ from avilla.core.builtins.capability import CoreCapability from avilla.core.context import Context -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/console/perform/event/message.py b/avilla/console/perform/event/message.py index e8d342d0..b2658761 100644 --- a/avilla/console/perform/event/message.py +++ b/avilla/console/perform/event/message.py @@ -9,7 +9,7 @@ from avilla.console.capability import ConsoleCapability from avilla.core.context import Context from avilla.core.message import Message -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.message import MessageReceived diff --git a/avilla/console/perform/message/deserialize.py b/avilla/console/perform/message/deserialize.py index 716fa276..829d01fe 100644 --- a/avilla/console/perform/message/deserialize.py +++ b/avilla/console/perform/message/deserialize.py @@ -10,7 +10,7 @@ from avilla.console.capability import ConsoleCapability from avilla.console.element import Markdown, Markup from avilla.core.elements import Face, Text -from avilla.core.ryanvk.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.collector.application import ApplicationCollector class ConsoleMessageDeserializePerform((m := ApplicationCollector())._): diff --git a/avilla/console/perform/message/serialize.py b/avilla/console/perform/message/serialize.py index 3325b95b..a61dbdd9 100644 --- a/avilla/console/perform/message/serialize.py +++ b/avilla/console/perform/message/serialize.py @@ -11,7 +11,7 @@ from avilla.console.capability import ConsoleCapability from avilla.console.element import Markdown, Markup from avilla.core.elements import Face, Text -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector if TYPE_CHECKING: from ...account import ConsoleAccount # noqa diff --git a/avilla/core/account.py b/avilla/core/account.py index 5d2791c6..2104f990 100644 --- a/avilla/core/account.py +++ b/avilla/core/account.py @@ -5,7 +5,7 @@ from statv import Stats, Statv -from avilla.core.ryanvk.staff import Staff +from avilla.core.ryanvk_old.staff import Staff from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/core/application.py b/avilla/core/application.py index 20751cfe..54e28cc3 100644 --- a/avilla/core/application.py +++ b/avilla/core/application.py @@ -16,7 +16,7 @@ from avilla.core.dispatchers import AvillaBuiltinDispatcher from avilla.core.event import MetadataModified from avilla.core.protocol import BaseProtocol -from avilla.core.ryanvk.staff import Staff +from avilla.core.ryanvk_old.staff import Staff from avilla.core.selector import Selector from avilla.core.service import AvillaService from avilla.core.utilles import identity diff --git a/avilla/core/builtins/capability.py b/avilla/core/builtins/capability.py index 8bb50a39..08360b87 100644 --- a/avilla/core/builtins/capability.py +++ b/avilla/core/builtins/capability.py @@ -1,17 +1,16 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, TypeVar +from typing import TYPE_CHECKING, Any, Protocol, TypeVar, overload from typing_extensions import Unpack +from avilla.core.flywheel.fn.base import Fn +from avilla.core.flywheel.fn.compose import FnCompose, OverloadRecorder +from avilla.core.flywheel.fn.record import FnRecord +from avilla.core.flywheel.overloads import SimpleOverload, TypeOverload from avilla.core.metadata import Metadata, MetadataRoute from avilla.core.resource import Resource, T -from avilla.core.ryanvk.descriptor.query import QuerySchema -from avilla.core.ryanvk.overload.metadata import MetadataOverload -from avilla.core.ryanvk.overload.target import TargetOverload -from graia.ryanvk.capability import Capability -from graia.ryanvk.fn import Fn -from graia.ryanvk.overload import NoneOverload, TypeOverload +from avilla.core.ryanvk.overloads import TargetOverload if TYPE_CHECKING: from avilla.core.context import Context @@ -19,36 +18,124 @@ M = TypeVar("M", bound=Metadata) +MR = TypeVar("MR", bound=Metadata, covariant=True) +R = TypeVar("R", covariant=True) +Res = TypeVar("Res", bound=Resource, contravariant=True) +Res1 = TypeVar("Res1", bound=Resource, covariant=True) -class CoreCapability(Capability): - query = QuerySchema() +class CoreCapability: + # query = QuerySchema() - @Fn.complex( - { - TargetOverload(): ["target"], - NoneOverload(TargetOverload(), default_factory=lambda _: None): ["via"], - } - ) - def get_context(self, target: Selector, *, via: Selector | None = None) -> Context: - ... + target = TargetOverload("target") - @Fn.complex({TargetOverload(): ["target"], MetadataOverload(): ["route"]}) - async def pull(self, target: Selector, route: type[M] | MetadataRoute[Unpack[tuple[Any, ...]], M]) -> Any: - ... + @Fn.declare + class get_context(FnCompose): + via = TargetOverload("via") + novia = SimpleOverload("novia") - @Fn.complex({TypeOverload(): ["resource"]}) - async def fetch(self, resource: Resource[T]) -> T: - ... + def call(self, record: FnRecord, target: Selector, *, via: Selector | None = None) -> Context: + entities = self.load( + CoreCapability.target.dig(record, target), + self.novia.dig(record, via) if via is None else self.via.dig(record, via), + ) - @Fn.complex({TargetOverload(): ["target"]}) - def channel(self, target: Selector) -> str: - ... + if via is None: + return entities.first(target=target) # type: ignore - @Fn.complex({TargetOverload(): ["target"]}) - def guild(self, target: Selector) -> str: - ... + return entities.first(target=target, via=via) - @Fn.complex({TargetOverload(): ["target"]}) - def user(self, target: Selector) -> str: - ... + class shapecall_novia(Protocol): + def __call__(self, target: Selector) -> Context: ... + + class shapecall_via(Protocol): + def __call__(self, target: Selector, via: Selector) -> Context: ... + + @overload + def collect(self, recorder: OverloadRecorder[shapecall_via], target: str, via: str) -> None: ... + + @overload + def collect(self, recorder: OverloadRecorder[shapecall_novia], target: str, via: None = None) -> None: ... + + def collect(self, recorder: OverloadRecorder, target: str, via: str | None = None): + # TODO: 能否使用 predicator? + + recorder.use(CoreCapability.target, (target, {})) + if via is None: + recorder.use(self.novia, None) + else: + recorder.use(self.via, (via, {})) + + @Fn.declare + class pull(FnCompose): + route: SimpleOverload + + async def call( + self, record: FnRecord, target: Selector, route: type[M] | MetadataRoute[Unpack[tuple[Any, ...]], M] + ) -> M: + entities = self.load(CoreCapability.target.dig(record, target), self.route.dig(record, route)) + return await entities.first(target=target) + + class shapecall(Protocol[MR]): + async def __call__(self, target: Selector) -> MR: ... + + def collect( + self, + recorder: OverloadRecorder[shapecall[M]], + target: str, + route: type[M] | MetadataRoute[Unpack[tuple[Any, ...]], M], + ): + recorder.use(CoreCapability.target, (target, {})) + recorder.use(self.route, route) + + @Fn.declare + class fetch(FnCompose): + resource = TypeOverload("resource") + + async def call(self, record: FnRecord, resource: Resource[T]) -> T: + entities = self.load(self.resource.dig(record, resource)) + return await entities.first(resource) + + class shapecall(Protocol[Res, Res1]): + async def __call__(self: CoreCapability.fetch.shapecall[Res, Resource[T]], resource: Res) -> T: ... + + def collect(self, recorder: OverloadRecorder[shapecall[Res, Res]], resource: type[Res]): + recorder.use(self.resource, resource) + + @Fn.declare + class channel(FnCompose): + def call(self, record: FnRecord, target: Selector) -> str: + entities = self.load(CoreCapability.target.dig(record, target)) + return entities.first(target=target) + + class shapecall(Protocol): + def __call__(self, target: Selector) -> str: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(CoreCapability.target, (target, {})) + + @Fn.declare + class guild(FnCompose): + def call(self, record: FnRecord, target: Selector) -> str: + entities = self.load(CoreCapability.target.dig(record, target)) + return entities.first(target=target) + + class shapecall(Protocol): + def __call__(self, target: Selector) -> str: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(CoreCapability.target, (target, {})) + + @Fn.declare + class user(FnCompose): + def call(self, record: FnRecord, target: Selector) -> str: + entities = self.load(CoreCapability.target.dig(record, target)) + return entities.first(target=target) + + class shapecall(Protocol): + def __call__(self, target: Selector) -> str: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(CoreCapability.target, (target, {})) + + # TODO: query diff --git a/avilla/core/builtins/resource_fetch.py b/avilla/core/builtins/resource_fetch.py index 36d95925..e820382e 100644 --- a/avilla/core/builtins/resource_fetch.py +++ b/avilla/core/builtins/resource_fetch.py @@ -1,38 +1,29 @@ from __future__ import annotations +from avilla.core.flywheel.scoped import scoped_collect from avilla.core.resource import LocalFileResource, RawResource, UrlResource -from avilla.core.ryanvk.collector.application import ApplicationCollector try: from aiohttp import ClientSession - - aio = True except ImportError: ClientSession = None - aio = False from .capability import CoreCapability -class CoreResourceFetchPerform((m := ApplicationCollector())._): - @m.entity(CoreCapability.fetch, resource=LocalFileResource) +class CoreResourceFetchPerform(m := scoped_collect.globals().target): + @m.impl(CoreCapability.fetch, resource=LocalFileResource) async def fetch_localfile(self, resource: LocalFileResource): return resource.file.read_bytes() - @m.entity(CoreCapability.fetch, resource=RawResource) + @m.impl(CoreCapability.fetch, resource=RawResource) async def fetch_raw(self, resource: RawResource): return resource.data - if aio: + if ClientSession is not None: - @m.entity(CoreCapability.fetch, resource=UrlResource) + @m.impl(CoreCapability.fetch, resource=UrlResource) async def fetch_url(self, resource: UrlResource): - async with ClientSession() as session: + async with ClientSession() as session: # type: ignore async with session.get(resource.url) as resp: return await resp.read() - - else: - - @m.entity(CoreCapability.fetch, resource=UrlResource) - async def fetch_url(self, resource: UrlResource): - raise NotImplementedError("aiohttp is not installed") diff --git a/avilla/core/context/__init__.py b/avilla/core/context/__init__.py index c7f83c15..8f566222 100644 --- a/avilla/core/context/__init__.py +++ b/avilla/core/context/__init__.py @@ -10,8 +10,8 @@ from avilla.core.metadata import Metadata, MetadataRoute from avilla.core.platform import Land from avilla.core.resource import Resource -from avilla.core.ryanvk import Fn -from avilla.core.ryanvk.staff import Staff +from avilla.core.ryanvk_old import Fn +from avilla.core.ryanvk_old.staff import Staff from avilla.core.selector import FollowsPredicater, Selectable, Selector from avilla.core.utilles import classproperty diff --git a/avilla/core/context/_roles.py b/avilla/core/context/_roles.py index 85bcdb36..11617326 100644 --- a/avilla/core/context/_roles.py +++ b/avilla/core/context/_roles.py @@ -4,7 +4,6 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, TypeVar -from graia.amnesia.message import Element, MessageChain, Text from typing_extensions import ParamSpec from avilla.core.builtins.capability import CoreCapability @@ -15,6 +14,7 @@ from avilla.standard.core.message import MessageSend from avilla.standard.core.relation import SceneCapability from avilla.standard.core.request import RequestCapability +from graia.amnesia.message import Element, MessageChain, Text from ._selector import ContextSelector @@ -32,18 +32,18 @@ def trigger_activity(self, activity: str): @property def channel(self) -> str: - return self.context.staff.call_fn(CoreCapability.channel, self) + return CoreCapability.channel(self) @property def guild(self) -> str | None: try: - return self.context.staff.call_fn(CoreCapability.guild, self) + return CoreCapability.guild(self) except NotImplementedError: return None @property def user(self) -> str: - return self.context.staff.call_fn(CoreCapability.user, self) + return CoreCapability.user(self) class ContextEndpointSelector(ContextSelector): @@ -55,18 +55,18 @@ def expects_request(self) -> ContextRequestSelector: @property def channel(self) -> str: - return self.context.staff.call_fn(CoreCapability.channel, self) + return CoreCapability.channel(self) @property def guild(self) -> str | None: try: - return self.context.staff.call_fn(CoreCapability.guild, self) + return CoreCapability.guild(self) except NotImplementedError: return None @property def user(self) -> str: - return self.context.staff.call_fn(CoreCapability.user, self) + return CoreCapability.user(self) class ContextSelfSelector(ContextSelector): @@ -108,11 +108,11 @@ def remove_member(self, target: Selector, reason: str | None = None): @property def channel(self) -> str: - return self.context.staff.call_fn(CoreCapability.channel, self) + return CoreCapability.channel(self) @property def guild(self) -> str: - return self.context.staff.call_fn(CoreCapability.guild, self) + return CoreCapability.guild(self) class ContextRequestSelector(ContextEndpointSelector): diff --git a/avilla/core/context/_selector.py b/avilla/core/context/_selector.py index 363e01b2..32414d14 100644 --- a/avilla/core/context/_selector.py +++ b/avilla/core/context/_selector.py @@ -7,7 +7,7 @@ from typing_extensions import Concatenate, ParamSpec, Self, Unpack from avilla.core.metadata import Metadata, MetadataRoute -from avilla.core.ryanvk import Fn +from avilla.core.ryanvk_old import Fn from avilla.core.selector import EMPTY_MAP, Selector from avilla.standard.core.privilege import Privilege from avilla.standard.core.profile import Avatar, Nick, Summary diff --git a/avilla/core/flywheel/fn/base.py b/avilla/core/flywheel/fn/base.py index 45428e66..feb77ee8 100644 --- a/avilla/core/flywheel/fn/base.py +++ b/avilla/core/flywheel/fn/base.py @@ -6,7 +6,7 @@ from ..entity import BaseEntity from ..globals import iter_layout -from ..typing import CR, AssignKeeper, Call, InP, OutP, P, R +from ..typing import CR, AssignKeeper, Call, OutP, P, R from .compose import FnCompose from .implement import FnImplementEntity, OverloadRecorder @@ -17,6 +17,7 @@ CCall = TypeVar("CCall", bound=Callable, covariant=True) CCollect = TypeVar("CCollect", bound=Callable, covariant=True) +C = TypeVar("C", bound=Callable, covariant=True) class ComposeShape(Protocol[CCollect, CCall]): @@ -29,7 +30,7 @@ def call(self) -> CCall: ... -FnDef = Type[ComposeShape[Callable[InP, None], Callable[Concatenate["FnRecord", OutP], R]]] +FnDef = Type[ComposeShape[CCollect, Callable[Concatenate["FnRecord", OutP], R]]] class Fn(Generic[CCollect, CCall], BaseEntity): @@ -39,7 +40,7 @@ def __init__(self, compose: type[FnCompose]): self.desc = compose(self) @classmethod - def declare(cls, desc: FnDef[InP, OutP, R]) -> Fn[Callable[InP, None], Callable[OutP, R]]: + def declare(cls, desc: FnDef[C, OutP, R]) -> Fn[C, Callable[OutP, R]]: return cls(desc) # type: ignore @property diff --git a/avilla/core/flywheel/scoped.py b/avilla/core/flywheel/scoped.py index 3be3d743..a0c52bca 100644 --- a/avilla/core/flywheel/scoped.py +++ b/avilla/core/flywheel/scoped.py @@ -3,6 +3,7 @@ import functools from typing import Any, Callable +from .fn.implement import OverloadRecorder from .context import CollectContext from .globals import COLLECTING_CONTEXT_VAR, GLOBAL_COLLECT_CONTEXT, GLOBAL_INSTANCE_CONTEXT, INSTANCE_CONTEXT_VAR from .typing import TYPE_CHECKING, AssignKeeperCls, P, R, TEntity @@ -85,7 +86,7 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: @staticmethod def impl( - fn: Fn[Callable[Concatenate[CR, OutP], Any], Any], *args: OutP.args, **kwargs: OutP.kwargs + fn: Fn[Callable[Concatenate[OverloadRecorder[CR], OutP], Any], Any], *args: OutP.args, **kwargs: OutP.kwargs ) -> AssignKeeperCls[Call[..., CR]]: def inner(impl: Callable[Concatenate[Any, P], R] | FnImplementEntity[Callable[P, R]]): if not isinstance(impl, FnImplementEntity): diff --git a/avilla/core/ryanvk/__init__.py b/avilla/core/ryanvk/__init__.py index 53c32e4a..e69de29b 100644 --- a/avilla/core/ryanvk/__init__.py +++ b/avilla/core/ryanvk/__init__.py @@ -1,13 +0,0 @@ -from avilla.core.ryanvk.collector.context import ( - ContextBasedPerformTemplate as ContextBasedPerformTemplate, -) -from avilla.core.ryanvk.collector.context import ContextCollector as ContextCollector -from avilla.core.ryanvk.descriptor.query import QueryRecord as QueryRecord -from avilla.core.ryanvk.descriptor.query import QuerySchema as QuerySchema -from avilla.core.ryanvk.overload.metadata import MetadataOverload as MetadataOverload -from avilla.core.ryanvk.overload.target import TargetOverload as TargetOverload -from graia.ryanvk.capability import Capability as Capability -from graia.ryanvk.fn import Fn as Fn -from graia.ryanvk.overload import FnOverload as FnOverload -from graia.ryanvk.overload import SimpleOverload as SimpleOverload -from graia.ryanvk.overload import TypeOverload as TypeOverload diff --git a/avilla/core/ryanvk/overloads.py b/avilla/core/ryanvk/overloads.py new file mode 100644 index 00000000..1375a3bc --- /dev/null +++ b/avilla/core/ryanvk/overloads.py @@ -0,0 +1,113 @@ +from __future__ import annotations +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any, Callable +from typing_extensions import TypeAlias + +from avilla.core.flywheel.fn.overload import FnOverload +from avilla.core.selector import FollowsPredicater, Selector, _parse_follows + + +@dataclass +class LookupBranchMetadata: + ... + +@dataclass +class LookupBranch: + metadata: LookupBranchMetadata + levels: LookupCollection + bind: dict[Callable, None] = field(default_factory=dict) + + +LookupBranches: TypeAlias = "dict[str | FollowsPredicater | None, LookupBranch]" +LookupCollection: TypeAlias = "dict[str, LookupBranches]" + +@dataclass +class TargetOverloadSignature: + order: str + predicators: dict[str, FollowsPredicater] = field(default_factory=dict) + + +class TargetOverload(FnOverload[TargetOverloadSignature, tuple[str, dict[str, FollowsPredicater]], Selector]): + def digest(self, collect_value: tuple[str, dict[str, FollowsPredicater]]) -> TargetOverloadSignature: + return TargetOverloadSignature(collect_value[0], collect_value[1]) + + def collect(self, scope: dict, signature: TargetOverloadSignature) -> dict[Callable, None]: + pattern_items = _parse_follows(signature.order) + if not pattern_items: + raise ValueError("invalid target pattern") + + processing_level = scope + + if TYPE_CHECKING: + branch = LookupBranch(LookupBranchMetadata(), {}) + + for item in pattern_items: + if item.name not in processing_level: + processing_level[item.name] = {} + + branches = processing_level[item.name] + if (item.literal or item.predicate) in branches: + branch = branches[item.literal or item.predicate] + else: + branch = LookupBranch(LookupBranchMetadata(), {}) + branches[item.literal or item.predicate] = branch + + processing_level = branch.levels + + return branch.bind + + def harvest(self, scope: dict, value: Selector) -> dict[Callable, None]: + processing_scope: LookupCollection = scope + branch = None + + for k, v in value.pattern.items(): + if (branches := processing_scope.get(k)) is None: + return {} + + if v in branches: + header = v + else: + for _key, branch in branches.items(): + if callable(_key) and _key(v): + header = _key + break # hit predicate + else: + if None in branches: + header = None # hit default + elif "*" in branches: + return branches["*"].bind # hit wildcard + else: + return {} + + branch = branches[header] + processing_scope = branch.levels + if header is not None and None in branches: + processing_scope = branches[None].levels | processing_scope + + if branch is not None and branch.bind: + return branch.bind + else: + return {} + + def access(self, scope: dict, signature: TargetOverloadSignature) -> dict[Callable, None] | None: + pattern_items = _parse_follows(signature.order) + if not pattern_items: + raise ValueError("invalid target pattern") + + processing_level = scope + + if TYPE_CHECKING: + branch = LookupBranch(LookupBranchMetadata(), {}) + + for item in pattern_items: + if item.name not in processing_level: + return + + branches: LookupBranches = processing_level[item.name] + if (item.literal or item.predicate) not in branches: + return + + branch = branches[item.literal or item.predicate] + processing_level = branch.levels + + return branch.bind \ No newline at end of file diff --git a/avilla/core/ryanvk_old/__init__.py b/avilla/core/ryanvk_old/__init__.py new file mode 100644 index 00000000..922616c7 --- /dev/null +++ b/avilla/core/ryanvk_old/__init__.py @@ -0,0 +1,13 @@ +from avilla.core.ryanvk_old.collector.context import ( + ContextBasedPerformTemplate as ContextBasedPerformTemplate, +) +from avilla.core.ryanvk_old.collector.context import ContextCollector as ContextCollector +from avilla.core.ryanvk_old.descriptor.query import QueryRecord as QueryRecord +from avilla.core.ryanvk_old.descriptor.query import QuerySchema as QuerySchema +from avilla.core.ryanvk_old.overload.metadata import MetadataOverload as MetadataOverload +from avilla.core.ryanvk_old.overload.target import TargetOverload as TargetOverload +from graia.ryanvk.capability import Capability as Capability +from graia.ryanvk.fn import Fn as Fn +from graia.ryanvk.overload import FnOverload as FnOverload +from graia.ryanvk.overload import SimpleOverload as SimpleOverload +from graia.ryanvk.overload import TypeOverload as TypeOverload diff --git a/avilla/core/ryanvk/behavior/__init__.py b/avilla/core/ryanvk_old/behavior/__init__.py similarity index 100% rename from avilla/core/ryanvk/behavior/__init__.py rename to avilla/core/ryanvk_old/behavior/__init__.py diff --git a/avilla/core/ryanvk/behavior/query.py b/avilla/core/ryanvk_old/behavior/query.py similarity index 93% rename from avilla/core/ryanvk/behavior/query.py rename to avilla/core/ryanvk_old/behavior/query.py index e80ee182..2170ee52 100644 --- a/avilla/core/ryanvk/behavior/query.py +++ b/avilla/core/ryanvk_old/behavior/query.py @@ -2,7 +2,7 @@ from typing import Any, Callable, TypedDict -from avilla.core.ryanvk.descriptor.query import QueryRecord, find_querier_steps +from avilla.core.ryanvk_old.descriptor.query import QueryRecord, find_querier_steps from avilla.core.selector import FollowsPredicater, _parse_follows from graia.ryanvk import FnOverload, OverloadBehavior from graia.ryanvk.collector import BaseCollector diff --git a/avilla/core/ryanvk/collector/__init__.py b/avilla/core/ryanvk_old/collector/__init__.py similarity index 100% rename from avilla/core/ryanvk/collector/__init__.py rename to avilla/core/ryanvk_old/collector/__init__.py diff --git a/avilla/core/ryanvk/collector/account.py b/avilla/core/ryanvk_old/collector/account.py similarity index 100% rename from avilla/core/ryanvk/collector/account.py rename to avilla/core/ryanvk_old/collector/account.py diff --git a/avilla/core/ryanvk/collector/application.py b/avilla/core/ryanvk_old/collector/application.py similarity index 100% rename from avilla/core/ryanvk/collector/application.py rename to avilla/core/ryanvk_old/collector/application.py diff --git a/avilla/core/ryanvk/collector/base.py b/avilla/core/ryanvk_old/collector/base.py similarity index 100% rename from avilla/core/ryanvk/collector/base.py rename to avilla/core/ryanvk_old/collector/base.py diff --git a/avilla/core/ryanvk/collector/context.py b/avilla/core/ryanvk_old/collector/context.py similarity index 100% rename from avilla/core/ryanvk/collector/context.py rename to avilla/core/ryanvk_old/collector/context.py diff --git a/avilla/core/ryanvk/collector/protocol.py b/avilla/core/ryanvk_old/collector/protocol.py similarity index 100% rename from avilla/core/ryanvk/collector/protocol.py rename to avilla/core/ryanvk_old/collector/protocol.py diff --git a/avilla/core/ryanvk/descriptor/__init__.py b/avilla/core/ryanvk_old/descriptor/__init__.py similarity index 100% rename from avilla/core/ryanvk/descriptor/__init__.py rename to avilla/core/ryanvk_old/descriptor/__init__.py diff --git a/avilla/core/ryanvk/descriptor/query.py b/avilla/core/ryanvk_old/descriptor/query.py similarity index 100% rename from avilla/core/ryanvk/descriptor/query.py rename to avilla/core/ryanvk_old/descriptor/query.py diff --git a/avilla/core/ryanvk/endpoint/__init__.py b/avilla/core/ryanvk_old/endpoint/__init__.py similarity index 100% rename from avilla/core/ryanvk/endpoint/__init__.py rename to avilla/core/ryanvk_old/endpoint/__init__.py diff --git a/avilla/core/ryanvk/endpoint/launart.py b/avilla/core/ryanvk_old/endpoint/launart.py similarity index 100% rename from avilla/core/ryanvk/endpoint/launart.py rename to avilla/core/ryanvk_old/endpoint/launart.py diff --git a/avilla/core/ryanvk/overload/__init__.py b/avilla/core/ryanvk_old/overload/__init__.py similarity index 100% rename from avilla/core/ryanvk/overload/__init__.py rename to avilla/core/ryanvk_old/overload/__init__.py diff --git a/avilla/core/ryanvk/overload/metadata.py b/avilla/core/ryanvk_old/overload/metadata.py similarity index 100% rename from avilla/core/ryanvk/overload/metadata.py rename to avilla/core/ryanvk_old/overload/metadata.py diff --git a/avilla/core/ryanvk/overload/target.py b/avilla/core/ryanvk_old/overload/target.py similarity index 100% rename from avilla/core/ryanvk/overload/target.py rename to avilla/core/ryanvk_old/overload/target.py diff --git a/avilla/core/ryanvk/staff.py b/avilla/core/ryanvk_old/staff.py similarity index 100% rename from avilla/core/ryanvk/staff.py rename to avilla/core/ryanvk_old/staff.py diff --git a/avilla/core/ryanvk/util.py b/avilla/core/ryanvk_old/util.py similarity index 100% rename from avilla/core/ryanvk/util.py rename to avilla/core/ryanvk_old/util.py diff --git a/avilla/elizabeth/capability.py b/avilla/elizabeth/capability.py index a165ad0b..c1881aed 100644 --- a/avilla/elizabeth/capability.py +++ b/avilla/elizabeth/capability.py @@ -5,7 +5,7 @@ from graia.amnesia.message import Element, MessageChain from avilla.core.event import AvillaEvent -from avilla.core.ryanvk.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.collector.application import ApplicationCollector from graia.ryanvk import Fn, PredicateOverload, TypeOverload if TYPE_CHECKING: diff --git a/avilla/elizabeth/collector/connection.py b/avilla/elizabeth/collector/connection.py index 550baa84..03f5c90b 100644 --- a/avilla/elizabeth/collector/connection.py +++ b/avilla/elizabeth/collector/connection.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, ClassVar, TypeVar -from avilla.core.ryanvk.collector.base import AvillaBaseCollector +from avilla.core.ryanvk_old.collector.base import AvillaBaseCollector from graia.ryanvk import Access, BasePerform if TYPE_CHECKING: diff --git a/avilla/elizabeth/connection/base.py b/avilla/elizabeth/connection/base.py index 212ac8bb..c8cc2a50 100644 --- a/avilla/elizabeth/connection/base.py +++ b/avilla/elizabeth/connection/base.py @@ -8,7 +8,7 @@ from typing_extensions import Self from avilla.core.exceptions import InvalidAuthentication -from avilla.core.ryanvk.staff import Staff +from avilla.core.ryanvk_old.staff import Staff from avilla.core.selector import Selector from avilla.elizabeth.capability import ElizabethCapability from avilla.standard.core.account import AccountAvailable diff --git a/avilla/elizabeth/file/capability.py b/avilla/elizabeth/file/capability.py index 9ddb858b..226b60fb 100644 --- a/avilla/elizabeth/file/capability.py +++ b/avilla/elizabeth/file/capability.py @@ -3,7 +3,7 @@ import os from typing import IO -from avilla.core.ryanvk import Fn, TargetOverload +from avilla.core.ryanvk_old import Fn, TargetOverload from avilla.core.selector import Selector from graia.ryanvk.capability import Capability diff --git a/avilla/elizabeth/perform/action/activity.py b/avilla/elizabeth/perform/action/activity.py index a49d4e9b..11d55b5a 100644 --- a/avilla/elizabeth/perform/action/activity.py +++ b/avilla/elizabeth/perform/action/activity.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.activity import ActivityTrigger diff --git a/avilla/elizabeth/perform/action/announcement.py b/avilla/elizabeth/perform/action/announcement.py index ae4e3292..c5aac22c 100644 --- a/avilla/elizabeth/perform/action/announcement.py +++ b/avilla/elizabeth/perform/action/announcement.py @@ -8,7 +8,7 @@ from graia.amnesia.builtins.memcache import Memcache, MemcacheService -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.qq.announcement import ( Announcement, diff --git a/avilla/elizabeth/perform/action/contact.py b/avilla/elizabeth/perform/action/contact.py index dcc9c49c..b8973754 100644 --- a/avilla/elizabeth/perform/action/contact.py +++ b/avilla/elizabeth/perform/action/contact.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.profile import Avatar, Nick, Summary diff --git a/avilla/elizabeth/perform/action/file.py b/avilla/elizabeth/perform/action/file.py index 91ae19be..e8a6e1b4 100644 --- a/avilla/elizabeth/perform/action/file.py +++ b/avilla/elizabeth/perform/action/file.py @@ -7,7 +7,7 @@ from graia.amnesia.builtins.memcache import Memcache, MemcacheService -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.elizabeth.file import ( FileData, diff --git a/avilla/elizabeth/perform/action/friend.py b/avilla/elizabeth/perform/action/friend.py index 454282e9..ada776e9 100644 --- a/avilla/elizabeth/perform/action/friend.py +++ b/avilla/elizabeth/perform/action/friend.py @@ -4,7 +4,7 @@ from graia.amnesia.builtins.memcache import Memcache, MemcacheService -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.profile import Avatar, Nick, Summary from avilla.standard.core.relation.capability import RelationshipTerminate diff --git a/avilla/elizabeth/perform/action/group.py b/avilla/elizabeth/perform/action/group.py index aadd62df..30a409b5 100644 --- a/avilla/elizabeth/perform/action/group.py +++ b/avilla/elizabeth/perform/action/group.py @@ -5,7 +5,7 @@ from graia.amnesia.builtins.memcache import Memcache, MemcacheService from avilla.core.exceptions import permission_error_message -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.elizabeth.const import PRIVILEGE_LEVEL from avilla.standard.core.privilege import MuteAllCapability, Privilege diff --git a/avilla/elizabeth/perform/action/member.py b/avilla/elizabeth/perform/action/member.py index b3a8a99b..f5e3cc04 100644 --- a/avilla/elizabeth/perform/action/member.py +++ b/avilla/elizabeth/perform/action/member.py @@ -6,7 +6,7 @@ from graia.amnesia.builtins.memcache import Memcache, MemcacheService from avilla.core.exceptions import permission_error_message -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.elizabeth.const import PRIVILEGE_LEVEL, PRIVILEGE_TRANS from avilla.standard.core.privilege import ( diff --git a/avilla/elizabeth/perform/action/message.py b/avilla/elizabeth/perform/action/message.py index 39733651..eb221623 100644 --- a/avilla/elizabeth/perform/action/message.py +++ b/avilla/elizabeth/perform/action/message.py @@ -7,7 +7,7 @@ from avilla.core import Context from avilla.core.message import Message -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.elizabeth.capability import ElizabethCapability from avilla.standard.core.message import ( diff --git a/avilla/elizabeth/perform/action/request.py b/avilla/elizabeth/perform/action/request.py index 28b2af3a..d3e856fc 100644 --- a/avilla/elizabeth/perform/action/request.py +++ b/avilla/elizabeth/perform/action/request.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.request import RequestCapability diff --git a/avilla/elizabeth/perform/context.py b/avilla/elizabeth/perform/context.py index adb8ab24..0fdd5c93 100644 --- a/avilla/elizabeth/perform/context.py +++ b/avilla/elizabeth/perform/context.py @@ -4,7 +4,7 @@ from avilla.core.builtins.capability import CoreCapability from avilla.core.context import Context -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/elizabeth/perform/message/deserialize.py b/avilla/elizabeth/perform/message/deserialize.py index 1bda4079..e5f8921f 100644 --- a/avilla/elizabeth/perform/message/deserialize.py +++ b/avilla/elizabeth/perform/message/deserialize.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from avilla.core.elements import Audio, Face, File, Notice, NoticeAll, Picture, Text -from avilla.core.ryanvk.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.collector.application import ApplicationCollector from avilla.core.selector import Selector from avilla.elizabeth.capability import ElizabethCapability from avilla.elizabeth.resource import ( diff --git a/avilla/elizabeth/perform/message/serialize.py b/avilla/elizabeth/perform/message/serialize.py index d4a9f847..817438f4 100644 --- a/avilla/elizabeth/perform/message/serialize.py +++ b/avilla/elizabeth/perform/message/serialize.py @@ -6,7 +6,7 @@ from avilla.core.elements import Audio, Face, Notice, NoticeAll, Picture, Text from avilla.core.resource import LocalFileResource, RawResource, UrlResource -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.elizabeth.capability import ElizabethCapability from avilla.elizabeth.resource import ElizabethImageResource, ElizabethVoiceResource from avilla.standard.qq.elements import ( diff --git a/avilla/elizabeth/perform/query/announcement.py b/avilla/elizabeth/perform/query/announcement.py index 4f6820e8..9d061315 100644 --- a/avilla/elizabeth/perform/query/announcement.py +++ b/avilla/elizabeth/perform/query/announcement.py @@ -6,7 +6,7 @@ from graia.amnesia.builtins.memcache import MemcacheService from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/elizabeth/perform/query/bot.py b/avilla/elizabeth/perform/query/bot.py index 45df0b6c..6e98b31d 100644 --- a/avilla/elizabeth/perform/query/bot.py +++ b/avilla/elizabeth/perform/query/bot.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Callable, cast from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/elizabeth/perform/query/file.py b/avilla/elizabeth/perform/query/file.py index 9e98578f..294f6eb5 100644 --- a/avilla/elizabeth/perform/query/file.py +++ b/avilla/elizabeth/perform/query/file.py @@ -6,7 +6,7 @@ from graia.amnesia.builtins.memcache import MemcacheService from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.elizabeth.file import FileData diff --git a/avilla/elizabeth/perform/query/friend.py b/avilla/elizabeth/perform/query/friend.py index 2fdc3325..090ba489 100644 --- a/avilla/elizabeth/perform/query/friend.py +++ b/avilla/elizabeth/perform/query/friend.py @@ -6,7 +6,7 @@ from graia.amnesia.builtins.memcache import MemcacheService from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/elizabeth/perform/query/group.py b/avilla/elizabeth/perform/query/group.py index 69e39ac5..43c10a22 100644 --- a/avilla/elizabeth/perform/query/group.py +++ b/avilla/elizabeth/perform/query/group.py @@ -6,7 +6,7 @@ from graia.amnesia.builtins.memcache import Memcache, MemcacheService from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/elizabeth/perform/resource_fetch.py b/avilla/elizabeth/perform/resource_fetch.py index ebb2086e..aa58239f 100644 --- a/avilla/elizabeth/perform/resource_fetch.py +++ b/avilla/elizabeth/perform/resource_fetch.py @@ -6,7 +6,7 @@ from avilla.core.builtins.capability import CoreCapability from avilla.core.exceptions import UnknownTarget -from avilla.core.ryanvk.collector.protocol import ProtocolCollector +from avilla.core.ryanvk_old.collector.protocol import ProtocolCollector from avilla.elizabeth.resource import ( ElizabethImageResource, ElizabethResource, diff --git a/avilla/nonebridge/capability.py b/avilla/nonebridge/capability.py index afc7cb4a..812ade60 100644 --- a/avilla/nonebridge/capability.py +++ b/avilla/nonebridge/capability.py @@ -5,7 +5,7 @@ from graia.amnesia.message import Element, MessageChain from avilla.core.event import AvillaEvent -from avilla.core.ryanvk.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.collector.application import ApplicationCollector from graia.ryanvk import Fn, PredicateOverload, TypeOverload if TYPE_CHECKING: diff --git a/avilla/nonebridge/perform/event/message.py b/avilla/nonebridge/perform/event/message.py index 7b2c4cad..50f56e47 100644 --- a/avilla/nonebridge/perform/event/message.py +++ b/avilla/nonebridge/perform/event/message.py @@ -1,6 +1,6 @@ from __future__ import annotations -from avilla.core.ryanvk.collector.context import ContextCollector +from avilla.core.ryanvk_old.collector.context import ContextCollector from avilla.standard.core.message import MessageReceived from ...descriptor.event import NoneEventTranslate diff --git a/avilla/nonebridge/service.py b/avilla/nonebridge/service.py index 6f7c1d08..7cf7bf55 100644 --- a/avilla/nonebridge/service.py +++ b/avilla/nonebridge/service.py @@ -13,7 +13,7 @@ from avilla.core.account import BaseAccount from avilla.core.event import AvillaEvent -from avilla.core.ryanvk.staff import Staff +from avilla.core.ryanvk_old.staff import Staff from avilla.core.utilles import identity from avilla.standard.core.account import AccountRegistered, AccountUnregistered from graia.ryanvk import merge, ref diff --git a/avilla/onebot/v11/capability.py b/avilla/onebot/v11/capability.py index 6656ce53..0382458e 100644 --- a/avilla/onebot/v11/capability.py +++ b/avilla/onebot/v11/capability.py @@ -5,7 +5,7 @@ from graia.amnesia.message import Element, MessageChain from avilla.core.event import AvillaEvent -from avilla.core.ryanvk.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.collector.application import ApplicationCollector from avilla.standard.core.application import AvillaLifecycleEvent from graia.ryanvk import Fn, PredicateOverload, TypeOverload diff --git a/avilla/onebot/v11/collector/connection.py b/avilla/onebot/v11/collector/connection.py index 5172baaa..a36ff635 100644 --- a/avilla/onebot/v11/collector/connection.py +++ b/avilla/onebot/v11/collector/connection.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, ClassVar, TypeVar -from avilla.core.ryanvk.collector.base import AvillaBaseCollector +from avilla.core.ryanvk_old.collector.base import AvillaBaseCollector from graia.ryanvk import Access, BasePerform if TYPE_CHECKING: diff --git a/avilla/onebot/v11/net/base.py b/avilla/onebot/v11/net/base.py index c4172619..88625264 100644 --- a/avilla/onebot/v11/net/base.py +++ b/avilla/onebot/v11/net/base.py @@ -8,7 +8,7 @@ from typing_extensions import Self from avilla.core.exceptions import ActionFailed -from avilla.core.ryanvk.staff import Staff +from avilla.core.ryanvk_old.staff import Staff from avilla.onebot.v11.capability import OneBot11Capability if TYPE_CHECKING: diff --git a/avilla/onebot/v11/perform/action/admin.py b/avilla/onebot/v11/perform/action/admin.py index 8f858c1d..2229d369 100644 --- a/avilla/onebot/v11/perform/action/admin.py +++ b/avilla/onebot/v11/perform/action/admin.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.privilege import PrivilegeCapability diff --git a/avilla/onebot/v11/perform/action/ban.py b/avilla/onebot/v11/perform/action/ban.py index 33cab3e0..4c4c8f37 100644 --- a/avilla/onebot/v11/perform/action/ban.py +++ b/avilla/onebot/v11/perform/action/ban.py @@ -3,7 +3,7 @@ from datetime import timedelta from typing import TYPE_CHECKING -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.privilege import BanCapability diff --git a/avilla/onebot/v11/perform/action/leave.py b/avilla/onebot/v11/perform/action/leave.py index 5700928f..c0872998 100644 --- a/avilla/onebot/v11/perform/action/leave.py +++ b/avilla/onebot/v11/perform/action/leave.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.relation import SceneCapability diff --git a/avilla/onebot/v11/perform/action/message.py b/avilla/onebot/v11/perform/action/message.py index 0f9d4930..61aeb34f 100644 --- a/avilla/onebot/v11/perform/action/message.py +++ b/avilla/onebot/v11/perform/action/message.py @@ -7,7 +7,7 @@ from avilla.core import Context from avilla.core.message import Message -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.onebot.v11.capability import OneBot11Capability from avilla.standard.core.message import MessageRevoke, MessageSend diff --git a/avilla/onebot/v11/perform/action/mute.py b/avilla/onebot/v11/perform/action/mute.py index 65b5ee6c..099f9110 100644 --- a/avilla/onebot/v11/perform/action/mute.py +++ b/avilla/onebot/v11/perform/action/mute.py @@ -3,7 +3,7 @@ from datetime import timedelta from typing import TYPE_CHECKING -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.privilege import MuteAllCapability, MuteCapability diff --git a/avilla/onebot/v11/perform/context.py b/avilla/onebot/v11/perform/context.py index fdf3f0ad..921b4321 100644 --- a/avilla/onebot/v11/perform/context.py +++ b/avilla/onebot/v11/perform/context.py @@ -4,7 +4,7 @@ from avilla.core.builtins.capability import CoreCapability from avilla.core.context import Context -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/onebot/v11/perform/message/deserialize.py b/avilla/onebot/v11/perform/message/deserialize.py index d4c7358a..c7bddd57 100644 --- a/avilla/onebot/v11/perform/message/deserialize.py +++ b/avilla/onebot/v11/perform/message/deserialize.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from avilla.core.elements import Face, Notice, NoticeAll, Picture, Reference, Text -from avilla.core.ryanvk.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.collector.application import ApplicationCollector from avilla.core.selector import Selector from avilla.onebot.v11.capability import OneBot11Capability from avilla.onebot.v11.resource import OneBot11ImageResource diff --git a/avilla/onebot/v11/perform/message/serialize.py b/avilla/onebot/v11/perform/message/serialize.py index 9c4a4013..c7d541b8 100644 --- a/avilla/onebot/v11/perform/message/serialize.py +++ b/avilla/onebot/v11/perform/message/serialize.py @@ -5,7 +5,7 @@ from avilla.core.elements import Face, Notice, NoticeAll, Picture, Text from avilla.core.resource import LocalFileResource, RawResource, UrlResource -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.onebot.v11.capability import OneBot11Capability from avilla.onebot.v11.resource import OneBot11ImageResource from avilla.standard.qq.elements import ( diff --git a/avilla/onebot/v11/perform/query/group.py b/avilla/onebot/v11/perform/query/group.py index dd43f1fb..68a34cdc 100644 --- a/avilla/onebot/v11/perform/query/group.py +++ b/avilla/onebot/v11/perform/query/group.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Callable, cast from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/onebot/v11/perform/resource_fetch.py b/avilla/onebot/v11/perform/resource_fetch.py index 665739e5..3b0233f8 100644 --- a/avilla/onebot/v11/perform/resource_fetch.py +++ b/avilla/onebot/v11/perform/resource_fetch.py @@ -5,7 +5,7 @@ from aiohttp import ClientSession from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk.collector.protocol import ProtocolCollector +from avilla.core.ryanvk_old.collector.protocol import ProtocolCollector from avilla.onebot.v11.resource import ( OneBot11FileResource, OneBot11ImageResource, diff --git a/avilla/qqapi/capability.py b/avilla/qqapi/capability.py index 9628f458..d09ae6db 100644 --- a/avilla/qqapi/capability.py +++ b/avilla/qqapi/capability.py @@ -5,8 +5,8 @@ from graia.amnesia.message import Element, MessageChain from avilla.core.event import AvillaEvent -from avilla.core.ryanvk.collector.application import ApplicationCollector -from avilla.core.ryanvk.overload.target import TargetOverload +from avilla.core.ryanvk_old.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.overload.target import TargetOverload from avilla.core.selector import Selector from avilla.standard.core.application.event import AvillaLifecycleEvent from graia.ryanvk import Fn, PredicateOverload, SimpleOverload, TypeOverload diff --git a/avilla/qqapi/collector/connection.py b/avilla/qqapi/collector/connection.py index b72681ad..cf57d79a 100644 --- a/avilla/qqapi/collector/connection.py +++ b/avilla/qqapi/collector/connection.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, ClassVar, TypeVar -from avilla.core.ryanvk.collector.base import AvillaBaseCollector +from avilla.core.ryanvk_old.collector.base import AvillaBaseCollector from graia.ryanvk import Access, BasePerform if TYPE_CHECKING: diff --git a/avilla/qqapi/connection/base.py b/avilla/qqapi/connection/base.py index 48f30c51..3420ade1 100644 --- a/avilla/qqapi/connection/base.py +++ b/avilla/qqapi/connection/base.py @@ -8,7 +8,7 @@ from loguru import logger from typing_extensions import Self -from avilla.core.ryanvk.staff import Staff +from avilla.core.ryanvk_old.staff import Staff from avilla.qqapi.audit import MessageAudited, audit_result from avilla.qqapi.capability import QQAPICapability diff --git a/avilla/qqapi/perform/action/channel.py b/avilla/qqapi/perform/action/channel.py index be946128..fb10a2f5 100644 --- a/avilla/qqapi/perform/action/channel.py +++ b/avilla/qqapi/perform/action/channel.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING from avilla.core.exceptions import permission_error_message -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.qqapi.const import PRIVILEGE_TRANS from avilla.standard.core.privilege import MuteAllCapability, Privilege diff --git a/avilla/qqapi/perform/action/guild.py b/avilla/qqapi/perform/action/guild.py index 5da5568c..fd47bbc3 100644 --- a/avilla/qqapi/perform/action/guild.py +++ b/avilla/qqapi/perform/action/guild.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.common import Count from avilla.standard.core.profile import Nick, Summary diff --git a/avilla/qqapi/perform/action/guild_member.py b/avilla/qqapi/perform/action/guild_member.py index 77826acb..2f6ffbc7 100644 --- a/avilla/qqapi/perform/action/guild_member.py +++ b/avilla/qqapi/perform/action/guild_member.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from avilla.core.exceptions import permission_error_message -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.qqapi.const import PRIVILEGE_TRANS from avilla.standard.core.privilege import MuteCapability, MuteInfo, Privilege diff --git a/avilla/qqapi/perform/action/message.py b/avilla/qqapi/perform/action/message.py index b52fa4af..42a5f754 100644 --- a/avilla/qqapi/perform/action/message.py +++ b/avilla/qqapi/perform/action/message.py @@ -9,7 +9,7 @@ from avilla.core import Context, CoreCapability, Message from avilla.core.exceptions import ActionFailed -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.qqapi.capability import QQAPICapability from avilla.qqapi.exception import AuditException diff --git a/avilla/qqapi/perform/action/role.py b/avilla/qqapi/perform/action/role.py index 8f19f16e..62825701 100644 --- a/avilla/qqapi/perform/action/role.py +++ b/avilla/qqapi/perform/action/role.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from avilla.core.exceptions import permission_error_message -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.qqapi.const import PRIVILEGE_TRANS from avilla.qqapi.role import ( diff --git a/avilla/qqapi/perform/action/user.py b/avilla/qqapi/perform/action/user.py index c60a6b9c..9fbe8735 100644 --- a/avilla/qqapi/perform/action/user.py +++ b/avilla/qqapi/perform/action/user.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.profile import Nick, Summary diff --git a/avilla/qqapi/perform/context.py b/avilla/qqapi/perform/context.py index 24923b9d..2b5b3d83 100644 --- a/avilla/qqapi/perform/context.py +++ b/avilla/qqapi/perform/context.py @@ -4,7 +4,7 @@ from avilla.core.builtins.capability import CoreCapability from avilla.core.context import Context -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/qqapi/perform/message/deserialize.py b/avilla/qqapi/perform/message/deserialize.py index eb31425c..14eb9302 100644 --- a/avilla/qqapi/perform/message/deserialize.py +++ b/avilla/qqapi/perform/message/deserialize.py @@ -12,7 +12,7 @@ Text, Video, ) -from avilla.core.ryanvk.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.collector.application import ApplicationCollector from avilla.core.selector import Selector from avilla.qqapi.capability import QQAPICapability from avilla.qqapi.element import Ark, ArkKv, Embed, Reference diff --git a/avilla/qqapi/perform/message/serialize.py b/avilla/qqapi/perform/message/serialize.py index effbebd5..af613410 100644 --- a/avilla/qqapi/perform/message/serialize.py +++ b/avilla/qqapi/perform/message/serialize.py @@ -5,7 +5,7 @@ from avilla.core.elements import Audio, Face, Notice, NoticeAll, Picture, Text, Video from avilla.core.resource import LocalFileResource, RawResource, UrlResource -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.qqapi.capability import QQAPICapability from avilla.qqapi.element import Ark, Embed, Keyboard, Markdown, Reference from avilla.qqapi.resource import ( diff --git a/avilla/qqapi/perform/query.py b/avilla/qqapi/perform/query.py index f5e439c8..faae58a4 100644 --- a/avilla/qqapi/perform/query.py +++ b/avilla/qqapi/perform/query.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Callable, cast from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/qqapi/perform/resource_fetch.py b/avilla/qqapi/perform/resource_fetch.py index 7476c95c..643801b0 100644 --- a/avilla/qqapi/perform/resource_fetch.py +++ b/avilla/qqapi/perform/resource_fetch.py @@ -5,7 +5,7 @@ from aiohttp import ClientSession from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk.collector.protocol import ProtocolCollector +from avilla.core.ryanvk_old.collector.protocol import ProtocolCollector from avilla.qqapi.resource import ( QQAPIAudioResource, QQAPIFileResource, diff --git a/avilla/qqapi/role/capability.py b/avilla/qqapi/role/capability.py index f47db061..3e9c8885 100644 --- a/avilla/qqapi/role/capability.py +++ b/avilla/qqapi/role/capability.py @@ -1,6 +1,6 @@ from __future__ import annotations -from avilla.core.ryanvk import Fn, TargetOverload +from avilla.core.ryanvk_old import Fn, TargetOverload from avilla.core.selector import Selector from graia.ryanvk.capability import Capability diff --git a/avilla/red/capability.py b/avilla/red/capability.py index 22890cf5..46905d79 100644 --- a/avilla/red/capability.py +++ b/avilla/red/capability.py @@ -5,8 +5,8 @@ from graia.amnesia.message import Element, MessageChain from avilla.core.event import AvillaEvent -from avilla.core.ryanvk.collector.application import ApplicationCollector -from avilla.core.ryanvk.overload.target import TargetOverload +from avilla.core.ryanvk_old.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.overload.target import TargetOverload from avilla.core.selector import Selector from avilla.standard.core.application.event import AvillaLifecycleEvent from avilla.standard.qq.elements import Forward diff --git a/avilla/red/collector/connection.py b/avilla/red/collector/connection.py index 8ae08f0e..450e7184 100644 --- a/avilla/red/collector/connection.py +++ b/avilla/red/collector/connection.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, ClassVar, TypeVar -from avilla.core.ryanvk.collector.base import AvillaBaseCollector +from avilla.core.ryanvk_old.collector.base import AvillaBaseCollector from graia.ryanvk import Access, BasePerform if TYPE_CHECKING: diff --git a/avilla/red/net/base.py b/avilla/red/net/base.py index 98b37a5a..d9f0ab56 100644 --- a/avilla/red/net/base.py +++ b/avilla/red/net/base.py @@ -7,7 +7,7 @@ from loguru import logger from typing_extensions import Self -from avilla.core.ryanvk.staff import Staff +from avilla.core.ryanvk_old.staff import Staff from avilla.red.account import RedAccount from avilla.red.capability import RedCapability from avilla.red.utils import MsgType, get_msg_types diff --git a/avilla/red/perform/action/friend.py b/avilla/red/perform/action/friend.py index d1012b29..9047e145 100644 --- a/avilla/red/perform/action/friend.py +++ b/avilla/red/perform/action/friend.py @@ -5,7 +5,7 @@ from graia.amnesia.builtins.memcache import Memcache, MemcacheService from avilla.core.exceptions import UnknownTarget -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.profile import Nick, Summary diff --git a/avilla/red/perform/action/group.py b/avilla/red/perform/action/group.py index 671b99d5..f3e57e9b 100644 --- a/avilla/red/perform/action/group.py +++ b/avilla/red/perform/action/group.py @@ -5,7 +5,7 @@ from graia.amnesia.builtins.memcache import Memcache, MemcacheService from avilla.core.exceptions import UnknownTarget -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.common import Count from avilla.standard.core.privilege import MuteAllCapability diff --git a/avilla/red/perform/action/member.py b/avilla/red/perform/action/member.py index 3aeb4eaa..c4c86305 100644 --- a/avilla/red/perform/action/member.py +++ b/avilla/red/perform/action/member.py @@ -6,7 +6,7 @@ from graia.amnesia.builtins.memcache import Memcache, MemcacheService from avilla.core.exceptions import UnknownTarget -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.privilege import MuteCapability, MuteInfo from avilla.standard.core.profile import Nick, Summary diff --git a/avilla/red/perform/action/message.py b/avilla/red/perform/action/message.py index 27f94f9e..656d2409 100644 --- a/avilla/red/perform/action/message.py +++ b/avilla/red/perform/action/message.py @@ -8,7 +8,7 @@ from loguru import logger from avilla.core import Context -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.red.capability import RedCapability from avilla.standard.core.message import ( diff --git a/avilla/red/perform/context.py b/avilla/red/perform/context.py index fb72168a..c0536e8e 100644 --- a/avilla/red/perform/context.py +++ b/avilla/red/perform/context.py @@ -4,7 +4,7 @@ from avilla.core.builtins.capability import CoreCapability from avilla.core.context import Context -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/red/perform/message/deserialize.py b/avilla/red/perform/message/deserialize.py index 5600412b..ddd30c40 100644 --- a/avilla/red/perform/message/deserialize.py +++ b/avilla/red/perform/message/deserialize.py @@ -16,7 +16,7 @@ Text, Video, ) -from avilla.core.ryanvk.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.collector.application import ApplicationCollector from avilla.core.selector import Selector from avilla.red.capability import RedCapability from avilla.red.resource import ( diff --git a/avilla/red/perform/message/serialize.py b/avilla/red/perform/message/serialize.py index 9212e4ba..712a37f6 100644 --- a/avilla/red/perform/message/serialize.py +++ b/avilla/red/perform/message/serialize.py @@ -7,7 +7,7 @@ from avilla.core.builtins.resource_fetch import CoreResourceFetchPerform from avilla.core.elements import Audio, Face, Notice, NoticeAll, Picture, Text from avilla.core.resource import LocalFileResource, RawResource, UrlResource -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.red.capability import RedCapability from avilla.standard.qq.elements import MarketFace diff --git a/avilla/red/perform/query.py b/avilla/red/perform/query.py index 144c6e32..a7dd8a2e 100644 --- a/avilla/red/perform/query.py +++ b/avilla/red/perform/query.py @@ -6,7 +6,7 @@ from graia.amnesia.builtins.memcache import Memcache, MemcacheService from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/red/perform/resource_fetch.py b/avilla/red/perform/resource_fetch.py index d48be9bb..67966483 100644 --- a/avilla/red/perform/resource_fetch.py +++ b/avilla/red/perform/resource_fetch.py @@ -6,7 +6,7 @@ from aiohttp import ClientSession from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk.collector.protocol import ProtocolCollector +from avilla.core.ryanvk_old.collector.protocol import ProtocolCollector from avilla.red.resource import ( RedFileResource, RedImageResource, diff --git a/avilla/satori/capability.py b/avilla/satori/capability.py index 84fab4da..32b83951 100644 --- a/avilla/satori/capability.py +++ b/avilla/satori/capability.py @@ -8,7 +8,7 @@ from satori.model import Event from avilla.core.event import AvillaEvent -from avilla.core.ryanvk.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.collector.application import ApplicationCollector from avilla.standard.core.application.event import AvillaLifecycleEvent from graia.ryanvk import Fn, PredicateOverload, TypeOverload diff --git a/avilla/satori/collector/connection.py b/avilla/satori/collector/connection.py index 305d9df1..c9b4eaa1 100644 --- a/avilla/satori/collector/connection.py +++ b/avilla/satori/collector/connection.py @@ -4,7 +4,7 @@ from satori.client.account import Account -from avilla.core.ryanvk.collector.base import AvillaBaseCollector +from avilla.core.ryanvk_old.collector.base import AvillaBaseCollector from graia.ryanvk import Access, BasePerform if TYPE_CHECKING: diff --git a/avilla/satori/perform/action/message.py b/avilla/satori/perform/action/message.py index a032e769..be48130a 100644 --- a/avilla/satori/perform/action/message.py +++ b/avilla/satori/perform/action/message.py @@ -10,7 +10,7 @@ from avilla.core.context import Context from avilla.core.elements import Reference from avilla.core.message import Message -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.satori.capability import SatoriCapability from avilla.standard.core.message import MessageRevoke, MessageSend, MessageSent diff --git a/avilla/satori/perform/action/request.py b/avilla/satori/perform/action/request.py index c240d06b..573ea33b 100644 --- a/avilla/satori/perform/action/request.py +++ b/avilla/satori/perform/action/request.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.request import RequestCapability diff --git a/avilla/satori/perform/context.py b/avilla/satori/perform/context.py index 15e2312d..702cc068 100644 --- a/avilla/satori/perform/context.py +++ b/avilla/satori/perform/context.py @@ -4,7 +4,7 @@ from avilla.core.builtins.capability import CoreCapability from avilla.core.context import Context -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector if TYPE_CHECKING: diff --git a/avilla/satori/perform/message/deserialize.py b/avilla/satori/perform/message/deserialize.py index 7ea92464..18204e3f 100644 --- a/avilla/satori/perform/message/deserialize.py +++ b/avilla/satori/perform/message/deserialize.py @@ -35,7 +35,7 @@ Text, Video, ) -from avilla.core.ryanvk.collector.application import ApplicationCollector +from avilla.core.ryanvk_old.collector.application import ApplicationCollector from avilla.core.selector import Selector from avilla.satori.capability import SatoriCapability from avilla.satori.element import Button diff --git a/avilla/satori/perform/message/serialize.py b/avilla/satori/perform/message/serialize.py index 0d437917..16842388 100644 --- a/avilla/satori/perform/message/serialize.py +++ b/avilla/satori/perform/message/serialize.py @@ -5,7 +5,7 @@ from satori.parser import escape from avilla.core.elements import Audio, File, Notice, NoticeAll, Picture, Text, Video -from avilla.core.ryanvk.collector.account import AccountCollector +from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.satori.capability import SatoriCapability from avilla.satori.element import Button from avilla.satori.resource import SatoriResource diff --git a/avilla/satori/perform/resource_fetch.py b/avilla/satori/perform/resource_fetch.py index 4e6266ab..eba78477 100644 --- a/avilla/satori/perform/resource_fetch.py +++ b/avilla/satori/perform/resource_fetch.py @@ -6,7 +6,7 @@ from aiohttp import ClientSession from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk.collector.protocol import ProtocolCollector +from avilla.core.ryanvk_old.collector.protocol import ProtocolCollector from avilla.satori.resource import ( SatoriAudioResource, SatoriFileResource, diff --git a/avilla/satori/service.py b/avilla/satori/service.py index e9743cbe..d966ff7b 100644 --- a/avilla/satori/service.py +++ b/avilla/satori/service.py @@ -18,7 +18,7 @@ ) from ..core import Selector -from ..core.ryanvk.staff import Staff +from ..core.ryanvk_old.staff import Staff from .account import SatoriAccount from .capability import SatoriCapability from .const import platform diff --git a/avilla/standard/core/activity/capability.py b/avilla/standard/core/activity/capability.py index 4fb6fa54..c1a077e9 100644 --- a/avilla/standard/core/activity/capability.py +++ b/avilla/standard/core/activity/capability.py @@ -1,6 +1,6 @@ from __future__ import annotations -from avilla.core.ryanvk import Capability, Fn, TargetOverload +from avilla.core.ryanvk_old import Capability, Fn, TargetOverload from avilla.core.selector import Selector diff --git a/avilla/standard/core/message/capability.py b/avilla/standard/core/message/capability.py index 7a386d49..bfb9375a 100644 --- a/avilla/standard/core/message/capability.py +++ b/avilla/standard/core/message/capability.py @@ -2,7 +2,7 @@ from graia.amnesia.message import MessageChain -from avilla.core.ryanvk import Capability, Fn, TargetOverload +from avilla.core.ryanvk_old import Capability, Fn, TargetOverload from avilla.core.selector import Selector # MessageFetch => rs.pull(Message, target=...) diff --git a/avilla/standard/core/privilege/capability.py b/avilla/standard/core/privilege/capability.py index 6f529490..e6bf8172 100644 --- a/avilla/standard/core/privilege/capability.py +++ b/avilla/standard/core/privilege/capability.py @@ -2,7 +2,7 @@ from datetime import timedelta -from avilla.core.ryanvk import Capability, Fn, TargetOverload +from avilla.core.ryanvk_old import Capability, Fn, TargetOverload from avilla.core.selector import Selector diff --git a/avilla/standard/core/profile/capability.py b/avilla/standard/core/profile/capability.py index a6a80320..ed699c14 100644 --- a/avilla/standard/core/profile/capability.py +++ b/avilla/standard/core/profile/capability.py @@ -2,7 +2,7 @@ from avilla.core.metadata import Route from avilla.core.resource import Resource -from avilla.core.ryanvk import Capability, Fn, MetadataOverload, TargetOverload +from avilla.core.ryanvk_old import Capability, Fn, MetadataOverload, TargetOverload from avilla.core.selector import Selector diff --git a/avilla/standard/core/relation/capability.py b/avilla/standard/core/relation/capability.py index 234be1b9..8baa337d 100644 --- a/avilla/standard/core/relation/capability.py +++ b/avilla/standard/core/relation/capability.py @@ -2,7 +2,7 @@ from typing import Literal -from avilla.core.ryanvk import Capability, Fn, TargetOverload +from avilla.core.ryanvk_old import Capability, Fn, TargetOverload from avilla.core.selector import Selector diff --git a/avilla/standard/core/request/capability.py b/avilla/standard/core/request/capability.py index b81c1f6e..3022338a 100644 --- a/avilla/standard/core/request/capability.py +++ b/avilla/standard/core/request/capability.py @@ -1,6 +1,6 @@ from __future__ import annotations -from avilla.core.ryanvk import Capability, Fn, TargetOverload +from avilla.core.ryanvk_old import Capability, Fn, TargetOverload from avilla.core.selector import Selector diff --git a/avilla/standard/qq/announcement/capability.py b/avilla/standard/qq/announcement/capability.py index 3f64ddb9..c6937eac 100644 --- a/avilla/standard/qq/announcement/capability.py +++ b/avilla/standard/qq/announcement/capability.py @@ -3,7 +3,7 @@ import io import os -from avilla.core.ryanvk import Capability, Fn, TargetOverload +from avilla.core.ryanvk_old import Capability, Fn, TargetOverload from avilla.core.selector import Selector diff --git a/docs/flywheel-migration.md b/docs/flywheel-migration.md index fc43c066..ed16692e 100644 --- a/docs/flywheel-migration.md +++ b/docs/flywheel-migration.md @@ -85,7 +85,7 @@ class QQAPIAccountPerform(m := scoped_context.env().target, QQAPIPerformAccountB ### Staff -在 Flywheel 中已经不存在 `Staff`,因为不再需要这样来封装对 `Fn` 的复杂封装。 +在 Flywheel 中已经不存在 `Staff`,因为 Flywheel 已经不再需要这样来封装对 `Fn` 的复杂封装。 相对的,我们建议协议实现的维护者提供形似 `TelegramAction` 的集合类来方便进行各种内部与外部的操作,如同之前的形似 `TelegramCapability` 的各式 Capability,只是这个名字更切题。 From 3916db670e93f666ae438538a375f0f5c90e60b8 Mon Sep 17 00:00:00 2001 From: Elaina Date: Mon, 18 Mar 2024 18:19:04 +0800 Subject: [PATCH 06/24] remove some old stuffs --- avilla/core/account.py | 30 +-- avilla/core/application.py | 21 +- avilla/core/context/__init__.py | 62 +---- avilla/core/ryanvk_old/__init__.py | 13 -- avilla/core/ryanvk_old/behavior/__init__.py | 0 avilla/core/ryanvk_old/behavior/query.py | 51 ---- avilla/core/ryanvk_old/collector/__init__.py | 0 avilla/core/ryanvk_old/collector/account.py | 58 ----- .../core/ryanvk_old/collector/application.py | 40 ---- avilla/core/ryanvk_old/collector/base.py | 46 ---- avilla/core/ryanvk_old/collector/context.py | 60 ----- avilla/core/ryanvk_old/collector/protocol.py | 44 ---- avilla/core/ryanvk_old/descriptor/__init__.py | 0 avilla/core/ryanvk_old/descriptor/query.py | 111 --------- avilla/core/ryanvk_old/endpoint/__init__.py | 0 avilla/core/ryanvk_old/endpoint/launart.py | 0 avilla/core/ryanvk_old/overload/__init__.py | 0 avilla/core/ryanvk_old/overload/metadata.py | 52 ----- avilla/core/ryanvk_old/overload/target.py | 155 ------------- avilla/core/ryanvk_old/staff.py | 122 ---------- avilla/core/ryanvk_old/util.py | 23 -- graia/ryanvk/__init__.py | 21 -- graia/ryanvk/_runtime.py | 30 --- graia/ryanvk/access.py | 25 -- graia/ryanvk/aio.py | 13 -- graia/ryanvk/behavior.py | 53 ----- graia/ryanvk/capability.py | 5 - graia/ryanvk/collector.py | 72 ------ graia/ryanvk/endpoint.py | 57 ----- graia/ryanvk/fn.py | 163 ------------- graia/ryanvk/gateway.py | 21 -- graia/ryanvk/overload.py | 218 ------------------ graia/ryanvk/override.py | 95 -------- graia/ryanvk/perform.py | 61 ----- graia/ryanvk/sign.py | 55 ----- graia/ryanvk/staff.py | 69 ------ graia/ryanvk/typing.py | 57 ----- 37 files changed, 18 insertions(+), 1885 deletions(-) delete mode 100644 avilla/core/ryanvk_old/__init__.py delete mode 100644 avilla/core/ryanvk_old/behavior/__init__.py delete mode 100644 avilla/core/ryanvk_old/behavior/query.py delete mode 100644 avilla/core/ryanvk_old/collector/__init__.py delete mode 100644 avilla/core/ryanvk_old/collector/account.py delete mode 100644 avilla/core/ryanvk_old/collector/application.py delete mode 100644 avilla/core/ryanvk_old/collector/base.py delete mode 100644 avilla/core/ryanvk_old/collector/context.py delete mode 100644 avilla/core/ryanvk_old/collector/protocol.py delete mode 100644 avilla/core/ryanvk_old/descriptor/__init__.py delete mode 100644 avilla/core/ryanvk_old/descriptor/query.py delete mode 100644 avilla/core/ryanvk_old/endpoint/__init__.py delete mode 100644 avilla/core/ryanvk_old/endpoint/launart.py delete mode 100644 avilla/core/ryanvk_old/overload/__init__.py delete mode 100644 avilla/core/ryanvk_old/overload/metadata.py delete mode 100644 avilla/core/ryanvk_old/overload/target.py delete mode 100644 avilla/core/ryanvk_old/staff.py delete mode 100644 avilla/core/ryanvk_old/util.py delete mode 100644 graia/ryanvk/__init__.py delete mode 100644 graia/ryanvk/_runtime.py delete mode 100644 graia/ryanvk/access.py delete mode 100644 graia/ryanvk/aio.py delete mode 100644 graia/ryanvk/behavior.py delete mode 100644 graia/ryanvk/capability.py delete mode 100644 graia/ryanvk/collector.py delete mode 100644 graia/ryanvk/endpoint.py delete mode 100644 graia/ryanvk/fn.py delete mode 100644 graia/ryanvk/gateway.py delete mode 100644 graia/ryanvk/overload.py delete mode 100644 graia/ryanvk/override.py delete mode 100644 graia/ryanvk/perform.py delete mode 100644 graia/ryanvk/sign.py delete mode 100644 graia/ryanvk/staff.py delete mode 100644 graia/ryanvk/typing.py diff --git a/avilla/core/account.py b/avilla/core/account.py index 2104f990..12056ab2 100644 --- a/avilla/core/account.py +++ b/avilla/core/account.py @@ -5,8 +5,8 @@ from statv import Stats, Statv -from avilla.core.ryanvk_old.staff import Staff from avilla.core.selector import Selector +from avilla.core.builtins.capability import CoreCapability if TYPE_CHECKING: from avilla.core.application import Avilla @@ -29,45 +29,23 @@ class AccountInfo: class BaseAccount: route: Selector avilla: Avilla + artifacts: @property def info(self) -> AccountInfo: return self.avilla.accounts[self.route] - @property - def staff(self): - return Staff(self.get_staff_artifacts(), self.get_staff_components()) - @property def available(self) -> bool: return True def get_context(self, target: Selector, *, via: Selector | None = None) -> Context: - return self.staff.get_context(target, via=via) + return CoreCapability.get_context(target, via=via) def get_self_context(self): from avilla.core.context import Context - return Context( - self, - self.route, - self.route, - self.route.into("::"), - self.route, - ) - - def get_staff_components(self): - return {"account": self, "protocol": self.info.protocol, "avilla": self.avilla} - - def get_staff_artifacts(self): - return [ - self.info.artifacts, - self.info.protocol.artifacts, - self.avilla.global_artifacts, - ] - - def __staff_generic__(self, element_type: dict, event_type: dict): - ... + return Context(self, self.route, self.route, self.route.into("::"), self.route) class AccountStatus(Statv): diff --git a/avilla/core/application.py b/avilla/core/application.py index 54e28cc3..78cf1694 100644 --- a/avilla/core/application.py +++ b/avilla/core/application.py @@ -195,28 +195,15 @@ def wrapper(func: Callable[[TE], None]): return wrapper self.custom_event_recorder[event_type] = recorder # type: ignore return recorder - - def __init_isolate__(self): - from avilla.core.builtins.resource_fetch import CoreResourceFetchPerform - - CoreResourceFetchPerform.apply_to(self.global_artifacts) - - def get_staff_components(self): - return {"avilla": self} - - def get_staff_artifacts(self): - return [self.global_artifacts] - - def __staff_generic__(self, element_type: dict, event_type: dict): - ... + + @staticmethod + def _require_builtins(): + import avilla.core.builtins.resource_fetch # noqa: F401 @classmethod def current(cls): return get_current_avilla() - async def fetch_resource(self, resource: Resource[T]) -> T: - return await Staff(self.get_staff_artifacts(), self.get_staff_components()).fetch_resource(resource) - def get_account(self, target: Selector) -> AccountInfo: return self.accounts[target] diff --git a/avilla/core/context/__init__.py b/avilla/core/context/__init__.py index 8f566222..2e4f9100 100644 --- a/avilla/core/context/__init__.py +++ b/avilla/core/context/__init__.py @@ -1,7 +1,7 @@ from __future__ import annotations -from collections.abc import Callable -from typing import Any, TypedDict, TypeVar, cast, overload +from contextlib import contextmanager +from typing import Any, TypedDict, TypeVar, cast from typing_extensions import ParamSpec, Unpack @@ -10,10 +10,9 @@ from avilla.core.metadata import Metadata, MetadataRoute from avilla.core.platform import Land from avilla.core.resource import Resource -from avilla.core.ryanvk_old import Fn -from avilla.core.ryanvk_old.staff import Staff -from avilla.core.selector import FollowsPredicater, Selectable, Selector +from avilla.core.selector import Selectable, Selector from avilla.core.utilles import classproperty +from avilla.core.builtins.capability import CoreCapability from ._roles import ( ContextClientSelector, @@ -54,15 +53,8 @@ def __init__( scene: Selector, selft: Selector, mediums: list[Selector] | None = None, - prelude_metadatas: dict[Selector, dict[type[Metadata] | MetadataRoute, Metadata]] | None = None, + metadatas: dict[Selector, dict[type[Metadata] | MetadataRoute, Metadata]] | None = None, ) -> None: - self.artifacts = [ - account.info.artifacts, - account.info.protocol.artifacts, - account.avilla.global_artifacts, - ] - # 这里是为了能在 Context 层级进行修改 - self.account = account self.client = ContextClientSelector.from_selector(self, client) @@ -71,8 +63,7 @@ def __init__( self.self = ContextSelfSelector.from_selector(self, selft) self.mediums = [ContextMedium(ContextSelector.from_selector(self, medium)) for medium in mediums or []] - self.cache = {"meta": prelude_metadatas or {}} - self.staff = Staff(self.get_staff_artifacts(), self.get_staff_components()) + self.cache = {"meta": metadatas or {}} @property def protocol(self): @@ -103,22 +94,9 @@ def _collect_metadatas(self, target: Selector | Selectable, *metadatas: Metadata def current(cls) -> Context: return cx_context.get() - def get_staff_components(self): - return { - "context": self, - "protocol": self.protocol, - "account": self.account, - "avilla": self.avilla, - } - - def get_staff_artifacts(self): - return self.artifacts - - def query(self, pattern: str, **predicators: FollowsPredicater): - return self.staff.query_entities(pattern, **predicators) - - async def fetch(self, resource: Resource[_T]) -> _T: - return await self.staff.fetch_resource(resource) + @contextmanager + def lookup_scope(self): + yield async def pull( self, @@ -138,24 +116,4 @@ async def pull( if not route.has_params(): return cast("_MetadataT", meta) - return await self.staff.pull_metadata(target, route) - - @overload - def __getitem__(self, closure: Selector) -> ContextSelector: - ... - - @overload - def __getitem__(self, closure: Fn[P, R]) -> Callable[P, R]: - ... - - def __getitem__(self, closure: Selector | Fn[P, Any]): - if isinstance(closure, Selector): - return ContextSelector(self, closure.pattern) - - def run(*args: P.args, **kwargs: P.kwargs): - return self.staff.call_fn(closure, *args, **kwargs) - - return run - - def __staff_generic__(self, element_type: dict, event_type: dict): - ... + return await CoreCapability.pull(target, route) diff --git a/avilla/core/ryanvk_old/__init__.py b/avilla/core/ryanvk_old/__init__.py deleted file mode 100644 index 922616c7..00000000 --- a/avilla/core/ryanvk_old/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from avilla.core.ryanvk_old.collector.context import ( - ContextBasedPerformTemplate as ContextBasedPerformTemplate, -) -from avilla.core.ryanvk_old.collector.context import ContextCollector as ContextCollector -from avilla.core.ryanvk_old.descriptor.query import QueryRecord as QueryRecord -from avilla.core.ryanvk_old.descriptor.query import QuerySchema as QuerySchema -from avilla.core.ryanvk_old.overload.metadata import MetadataOverload as MetadataOverload -from avilla.core.ryanvk_old.overload.target import TargetOverload as TargetOverload -from graia.ryanvk.capability import Capability as Capability -from graia.ryanvk.fn import Fn as Fn -from graia.ryanvk.overload import FnOverload as FnOverload -from graia.ryanvk.overload import SimpleOverload as SimpleOverload -from graia.ryanvk.overload import TypeOverload as TypeOverload diff --git a/avilla/core/ryanvk_old/behavior/__init__.py b/avilla/core/ryanvk_old/behavior/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/avilla/core/ryanvk_old/behavior/query.py b/avilla/core/ryanvk_old/behavior/query.py deleted file mode 100644 index 2170ee52..00000000 --- a/avilla/core/ryanvk_old/behavior/query.py +++ /dev/null @@ -1,51 +0,0 @@ -from __future__ import annotations - -from typing import Any, Callable, TypedDict - -from avilla.core.ryanvk_old.descriptor.query import QueryRecord, find_querier_steps -from avilla.core.selector import FollowsPredicater, _parse_follows -from graia.ryanvk import FnOverload, OverloadBehavior -from graia.ryanvk.collector import BaseCollector - - -class QueryCollectParams(TypedDict): - target: str - previous: str | None - - -class QueryCallArgs(TypedDict): - pattern: str - predicators: dict[str, FollowsPredicater] - - -class QueryOverload(FnOverload): - identity: str = "query" - - def collect_entity( - self, - collector: BaseCollector, - scope: dict[Any, Any], - entity: Any, - params: QueryCollectParams, - ) -> None: - record_tuple = (collector, entity) - - sign = QueryRecord(params["previous"], params["target"]) - scope.setdefault(sign, set()).add(record_tuple) - - def get_entities( - self, - scope: dict[QueryRecord, set[tuple[BaseCollector, Callable[..., Any]]]], - args: QueryCallArgs, - ): - items = _parse_follows(args["pattern"], **args["predicators"]) - steps = find_querier_steps(scope, items) - - if steps is None: - raise NotImplementedError - - ... - - -class QueryFnBehavior(OverloadBehavior): - ... diff --git a/avilla/core/ryanvk_old/collector/__init__.py b/avilla/core/ryanvk_old/collector/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/avilla/core/ryanvk_old/collector/account.py b/avilla/core/ryanvk_old/collector/account.py deleted file mode 100644 index bf396ad6..00000000 --- a/avilla/core/ryanvk_old/collector/account.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, ClassVar, Generic, TypeVar - -from graia.ryanvk import Access, BasePerform - -from .base import AvillaBaseCollector - -if TYPE_CHECKING: - from avilla.core.account import BaseAccount - from avilla.core.protocol import BaseProtocol - - -TProtocol = TypeVar("TProtocol", bound="BaseProtocol") -TAccount = TypeVar("TAccount", bound="BaseAccount") - -TProtocol1 = TypeVar("TProtocol1", bound="BaseProtocol") -TAccount1 = TypeVar("TAccount1", bound="BaseAccount") - -T = TypeVar("T") -T1 = TypeVar("T1") - - -class AccountBasedPerformTemplate(BasePerform, native=True): - __collector__: ClassVar[AccountCollector] - - protocol: Access[BaseProtocol] = Access() - account: Access[BaseAccount] = Access() - - @property - def avilla(self): - return self.protocol.avilla - - @property - def broadcast(self): - return self.avilla.broadcast - - -class AccountCollector(AvillaBaseCollector, Generic[TProtocol, TAccount]): - post_applying: bool = False - - def __init__(self): - super().__init__() - - @property - def _(self): - upper = super()._ - - class LocalPerformTemplate( - Generic[TProtocol1, TAccount1], - AccountBasedPerformTemplate, - upper, - native=True, - ): - protocol: TProtocol1 - account: TAccount1 - - return LocalPerformTemplate[TProtocol, TAccount] diff --git a/avilla/core/ryanvk_old/collector/application.py b/avilla/core/ryanvk_old/collector/application.py deleted file mode 100644 index 8dad8b28..00000000 --- a/avilla/core/ryanvk_old/collector/application.py +++ /dev/null @@ -1,40 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, ClassVar, TypeVar - -from graia.ryanvk import Access, BasePerform - -from .base import AvillaBaseCollector - -if TYPE_CHECKING: - from avilla.core.application import Avilla - - -T = TypeVar("T") -T1 = TypeVar("T1") - - -class ApplicationBasedPerformTemplate(BasePerform, native=True): - __collector__: ClassVar[ApplicationCollector] - - avilla: Access[Avilla] = Access() - - -class ApplicationCollector(AvillaBaseCollector): - post_applying: bool = False - - def __init__(self): - super().__init__() - - @property - def _(self): - upper = super()._ - - class LocalPerformTemplate( - ApplicationBasedPerformTemplate, - upper, - native=True, - ): - ... - - return LocalPerformTemplate diff --git a/avilla/core/ryanvk_old/collector/base.py b/avilla/core/ryanvk_old/collector/base.py deleted file mode 100644 index 5866cfbd..00000000 --- a/avilla/core/ryanvk_old/collector/base.py +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Awaitable, Callable, TypeVar, overload - -from graia.ryanvk import BaseCollector - -if TYPE_CHECKING: - from typing_extensions import Unpack - - from avilla.core.metadata import Metadata, MetadataRoute - from avilla.core.selector import Selector - -T = TypeVar("T") -T1 = TypeVar("T1") - -M = TypeVar("M", bound="Metadata") - - -Wrapper = Callable[[T], T] - - -class AvillaBaseCollector(BaseCollector): - def __init__(self): - super().__init__() - - @overload - def pull( - self, target: str, route: type[M] - ) -> Callable[[Callable[[Any, Selector, type[M]], Awaitable[M]]], Callable[[Any, Selector, type[M]], Awaitable[M]]]: - ... - - @overload - def pull( - self, target: str, route: MetadataRoute[Unpack[tuple[Any, ...]], M] - ) -> Callable[[Callable[[Any, Selector, type[M]], Awaitable[M]]], Callable[[Any, Selector, type[M]], Awaitable[M]]]: - ... - - def pull(self, target: str, route: ...) -> ...: - from avilla.core.builtins.capability import CoreCapability - - return self.entity(CoreCapability.pull, target=target, route=route) - - def fetch(self, resource_type: type[T]) -> Wrapper[Callable[[Any, T], Awaitable[Any]]]: - from avilla.core.builtins.capability import CoreCapability - - return self.entity(CoreCapability.fetch, resource=resource_type) # type: ignore diff --git a/avilla/core/ryanvk_old/collector/context.py b/avilla/core/ryanvk_old/collector/context.py deleted file mode 100644 index 3f309f23..00000000 --- a/avilla/core/ryanvk_old/collector/context.py +++ /dev/null @@ -1,60 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, ClassVar, Generic, TypeVar - -from graia.ryanvk import Access, BasePerform - -from .base import AvillaBaseCollector - -if TYPE_CHECKING: - from avilla.core.account import BaseAccount - from avilla.core.context import Context - from avilla.core.metadata import Metadata - from avilla.core.protocol import BaseProtocol - - -TProtocol = TypeVar("TProtocol", bound="BaseProtocol") -TAccount = TypeVar("TAccount", bound="BaseAccount") - -TProtocol1 = TypeVar("TProtocol1", bound="BaseProtocol") -TAccount1 = TypeVar("TAccount1", bound="BaseAccount") - -T = TypeVar("T") -T1 = TypeVar("T1") -M = TypeVar("M", bound="Metadata") - - -class ContextBasedPerformTemplate(BasePerform, native=True): - __collector__: ClassVar[ContextCollector] - - context: Access[Context] = Access() - - @property - def protocol(self): - return self.context.protocol - - @property - def account(self): - return self.context.account - - -class ContextCollector(AvillaBaseCollector, Generic[TProtocol, TAccount]): - post_applying: bool = False - - def __init__(self): - super().__init__() - - @property - def _(self): - upper = super()._ - - class LocalPerformTemplate( - Generic[TProtocol1, TAccount1], - ContextBasedPerformTemplate, - upper, - native=True, - ): - protocol: TProtocol1 - account: TAccount1 - - return LocalPerformTemplate[TProtocol, TAccount] diff --git a/avilla/core/ryanvk_old/collector/protocol.py b/avilla/core/ryanvk_old/collector/protocol.py deleted file mode 100644 index 6854b5c4..00000000 --- a/avilla/core/ryanvk_old/collector/protocol.py +++ /dev/null @@ -1,44 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, ClassVar, Generic, TypeVar - -from graia.ryanvk import Access, BasePerform - -from .base import AvillaBaseCollector - -if TYPE_CHECKING: - from avilla.core.protocol import BaseProtocol - - -TProtocol = TypeVar("TProtocol", bound="BaseProtocol") -TProtocol1 = TypeVar("TProtocol1", bound="BaseProtocol") - -T = TypeVar("T") -T1 = TypeVar("T1") - - -class ProtocolBasedPerformTemplate(BasePerform, native=True): - __collector__: ClassVar[ProtocolCollector] - - protocol: Access[BaseProtocol] = Access() - - -class ProtocolCollector(AvillaBaseCollector, Generic[TProtocol]): - post_applying: bool = False - - def __init__(self): - super().__init__() - - @property - def _(self): - upper = super()._ - - class LocalPerformTemplate( - Generic[TProtocol1], - ProtocolBasedPerformTemplate, - upper, - native=True, - ): - protocol: TProtocol1 - - return LocalPerformTemplate[TProtocol] diff --git a/avilla/core/ryanvk_old/descriptor/__init__.py b/avilla/core/ryanvk_old/descriptor/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/avilla/core/ryanvk_old/descriptor/query.py b/avilla/core/ryanvk_old/descriptor/query.py deleted file mode 100644 index 60fd7a8f..00000000 --- a/avilla/core/ryanvk_old/descriptor/query.py +++ /dev/null @@ -1,111 +0,0 @@ -from __future__ import annotations - -from collections import deque -from dataclasses import dataclass -from typing import Any, AsyncGenerator, Callable, Container, Protocol, overload - -from avilla.core.selector import Selector, _FollowItem -from graia.ryanvk import BaseCollector - - -@dataclass(unsafe_hash=True) -class QueryRecord: - """仅用作计算路径, 不参与实际运算, 也因此, 该元素仅存在于全局 Artifacts['query'] 中.""" - - previous: str | None - into: str - - -class QueryHandlerPerform(Protocol): - def __call__( - fself, self: Any, predicate: Callable[[str, str], bool] | str, previous: Selector | None = None - ) -> AsyncGenerator[Selector, None]: - ... - - -class QueryHandlerPerformNoPrev(Protocol): - def __call__( - fself, self: Any, predicate: Callable[[str, str], bool] | str, previous: None - ) -> AsyncGenerator[Selector, None]: - ... - - -class QueryHandlerPerformPrev(Protocol): - def __call__( - fself, self: Any, predicate: Callable[[str, str], bool] | str, previous: Selector - ) -> AsyncGenerator[Selector, None]: - ... - - -class QuerySchema: - @overload - def collect( - self, collector: BaseCollector, target: str, previous: None = None - ) -> Callable[[QueryHandlerPerformNoPrev], QueryHandlerPerformNoPrev]: - ... - - @overload - def collect( - self, collector: BaseCollector, target: str, previous: str - ) -> Callable[[QueryHandlerPerformPrev], QueryHandlerPerformPrev]: - ... - - def collect(self, collector: BaseCollector, target: str, previous: ... = None) -> ...: - def receive(entity: QueryHandlerPerform): - collector.artifacts[QueryRecord(previous, target)] = (collector, entity) - return entity - - return receive - - -class QueryHandler(Protocol): - def __call__( - self, predicate: Callable[[str, str], bool] | str, previous: Selector | None = None - ) -> AsyncGenerator[Selector, None]: - ... - - -# 使用 functools.reduce. -async def query_depth_generator( - handler: QueryHandler, - predicate: Callable[[str, str], bool] | str, - previous_generator: AsyncGenerator[Selector, None] | None = None, -): - if previous_generator is not None: - async for previous in previous_generator: - async for current in handler(predicate, previous): - yield current - else: - async for current in handler(predicate): - yield current - - -@dataclass -class _MatchStep: - upper: str - start: int - history: tuple[tuple[tuple[_FollowItem, ...], QueryRecord], ...] - - -def find_querier_steps( - artifacts: Container[Any], - frags: list[_FollowItem], -) -> list[tuple[tuple[_FollowItem, ...], QueryRecord]] | None: - result: list[tuple[tuple[_FollowItem, ...], QueryRecord]] | None = None - queue: deque[_MatchStep] = deque([_MatchStep("", 0, ())]) - whole = ".".join([i.name for i in frags]) - while queue: - head: _MatchStep = queue.popleft() - current_steps: list[_FollowItem] = [] - for curr_frag in frags[head.start :]: - current_steps.append(curr_frag) - steps = ".".join([i.name for i in current_steps]) - full_path = f"{head.upper}.{steps}" if head.upper else steps - head.start += 1 - if (query := ((*current_steps,), QueryRecord(head.upper or None, steps)))[1] in artifacts: - if full_path == whole: - if result is None or len(result) > len(head.history) + 1: - result = [*head.history, query] - else: - queue.append(_MatchStep(full_path, head.start, head.history + (query,))) - return result diff --git a/avilla/core/ryanvk_old/endpoint/__init__.py b/avilla/core/ryanvk_old/endpoint/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/avilla/core/ryanvk_old/endpoint/launart.py b/avilla/core/ryanvk_old/endpoint/launart.py deleted file mode 100644 index e69de29b..00000000 diff --git a/avilla/core/ryanvk_old/overload/__init__.py b/avilla/core/ryanvk_old/overload/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/avilla/core/ryanvk_old/overload/metadata.py b/avilla/core/ryanvk_old/overload/metadata.py deleted file mode 100644 index 20f8c475..00000000 --- a/avilla/core/ryanvk_old/overload/metadata.py +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -from typing import Any, Callable - -from avilla.core.metadata import Route -from graia.ryanvk.collector import BaseCollector -from graia.ryanvk.overload import FnOverload - - -class MetadataOverload(FnOverload): - def collect_entity( - self, - collector: BaseCollector, - scope: dict[Any, Any], - entity: Any, - params: dict[str, Route], - ) -> None: - record = (collector, entity) - - for param, route in params.items(): - ... - collection: dict[Route, set] = scope.setdefault(param, {}) - collection.setdefault(route, set()).add(record) - - def get_entities(self, scope: dict[Any, Any], args: dict[str, Route]) -> set[tuple[BaseCollector, Callable]]: - sets: list[set] = [] - - for arg_name, route in args.items(): - if arg_name not in scope: - raise NotImplementedError - - collection = scope[arg_name] - - if route not in collection: - raise NotImplementedError - - sets.append(collection[route]) - - return sets.pop().intersection(*sets) - - def merge_scopes(self, *scopes: dict[Any, Any]): - # layout: {param_name: {route: set()}} - result = {} - - for scope in scopes: - for param_name, collection in scope.items(): - result_collection = result.setdefault(param_name, {}) - for route, entities in collection.items(): - routes = result_collection.setdefault(route, set()) - routes.update(entities) - - return result diff --git a/avilla/core/ryanvk_old/overload/target.py b/avilla/core/ryanvk_old/overload/target.py deleted file mode 100644 index 950791a4..00000000 --- a/avilla/core/ryanvk_old/overload/target.py +++ /dev/null @@ -1,155 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Callable - -from typing_extensions import TypeAlias - -from avilla.core.selector import FollowsPredicater, Selector, _parse_follows -from graia.ryanvk.collector import BaseCollector -from graia.ryanvk.overload import FnOverload - - -@dataclass -class LookupBranchMetadata: - ... - - -@dataclass -class LookupBranch: - metadata: LookupBranchMetadata - levels: LookupCollection - bind: set[tuple[BaseCollector, Callable]] = field(default_factory=set) - - -LookupBranches: TypeAlias = "dict[str | FollowsPredicater | None, LookupBranch]" -LookupCollection: TypeAlias = "dict[str, LookupBranches]" - - -@dataclass -class TargetOverloadConfig: - pattern: str - predicators: dict[str, FollowsPredicater] - - def __init__(self, pattern: str, **predicators: FollowsPredicater): - self.pattern = pattern - self.predicators = predicators - - -def _merge_lookup_collection(current: LookupCollection, other: LookupCollection): - for key, branches in current.items(): - if (other_branches := other.pop(key, None)) is None: - continue - - for header, branch in branches.items(): - if (other_branch := other_branches.pop(header, None)) is None: - continue - - _merge_lookup_collection(branch.levels, other_branch.levels) - branch.bind |= other_branch.bind - - branches |= other_branches - - current |= other - - -class TargetOverload(FnOverload): - def collect_entity( - self, - collector: BaseCollector, - scope: dict[Any, Any], - entity: Any, - params: dict[str, str | TargetOverloadConfig], - ) -> None: - record = (collector, entity) - - for param, pattern in params.items(): - param_scope = scope.setdefault(param, {}) - - if isinstance(pattern, str): - pattern = TargetOverloadConfig(pattern) - - pattern_items = _parse_follows(pattern.pattern, **pattern.predicators) - if not pattern_items: - raise ValueError("invalid target pattern") - - processing_level = param_scope - - if TYPE_CHECKING: - branch = LookupBranch(LookupBranchMetadata(), {}) - - for item in pattern_items: - if item.name not in processing_level: - processing_level[item.name] = {} - - branches = processing_level[item.name] - if (item.literal or item.predicate) in branches: - branch = branches[item.literal or item.predicate] - else: - branch = LookupBranch(LookupBranchMetadata(), {}) - branches[item.literal or item.predicate] = branch - - processing_level = branch.levels - - branch.bind.add(record) - - def get_entities(self, scope: dict[Any, Any], args: dict[str, Selector]) -> set[tuple[BaseCollector, Callable]]: - bind_sets: list[set] = [] - - for arg_name, selector in args.items(): - if arg_name not in scope: - raise NotImplementedError - - def get_bind_set(): - processing_scope: LookupCollection = scope[arg_name] - branch = None - for key, value in selector.pattern.items(): - if (branches := processing_scope.get(key)) is None: - raise NotImplementedError - - if value in branches: - header = value - else: - for _key, branch in branches.items(): - if callable(_key) and _key(value): - header = _key - break # hit predicate - else: - if None in branches: - header = None # hit default - elif "*" in branches: - return branches["*"].bind # hit wildcard - else: - raise NotImplementedError - - branch = branches[header] - processing_scope = branch.levels - - if header is not None and None in branches: - processing_scope = branches[None].levels | processing_scope - if branch is not None and branch.bind: - # branch has bind - return branch.bind - - raise NotImplementedError - - bind_sets.append(get_bind_set()) - return bind_sets.pop().intersection(*bind_sets) - - def merge_scopes(self, *scopes: dict[Any, Any]): - # scope layout: { - # : LookupCollection - # } - param_collections: dict[str, list[LookupCollection]] = {} - result = {} - - for scope in scopes: - for param, collection in scope.items(): - param_collections.setdefault(param, []).append(collection) - - for param, collections in param_collections.items(): - result[param] = current = collections.pop(0) - for other in collections: - _merge_lookup_collection(current, other) - - return result diff --git a/avilla/core/ryanvk_old/staff.py b/avilla/core/ryanvk_old/staff.py deleted file mode 100644 index 372b6e2f..00000000 --- a/avilla/core/ryanvk_old/staff.py +++ /dev/null @@ -1,122 +0,0 @@ -from __future__ import annotations - -from functools import reduce -from typing import TYPE_CHECKING, Any, Callable, ChainMap, overload - -from typing_extensions import ParamSpec, TypeVar, Unpack - -from avilla.core.builtins.capability import CoreCapability -from avilla.core.metadata import MetadataRoute -from avilla.core.selector import ( - FollowsPredicater, - Selector, - _FollowItem, - _parse_follows, -) -from graia.ryanvk import BaseCollector -from graia.ryanvk import Staff as BaseStaff - -from .descriptor.query import find_querier_steps, query_depth_generator - -if TYPE_CHECKING: - from avilla.core.metadata import Metadata - from avilla.core.resource import Resource - - from .descriptor.query import QueryHandler, QueryHandlerPerform - - -T = TypeVar("T") -R = TypeVar("R", covariant=True) -M = TypeVar("M", bound="Metadata") -P = ParamSpec("P") -P1 = ParamSpec("P1") -N = TypeVar("N") -Co = TypeVar("Co", bound="BaseCollector") - - -class Staff(BaseStaff): - """手杖与核心工艺 (Staff & Focus Craft).""" - - def get_context(self, target: Selector, *, via: Selector | None = None): - return self.call_fn(CoreCapability.get_context, target, via=via) - - async def fetch_resource(self, resource: Resource[T]) -> T: - return await self.get_fn_call(CoreCapability.fetch)(resource) - - @overload - async def pull_metadata( - self, - target: Selector, - route: type[M], - ) -> M: - ... - - @overload - async def pull_metadata( - self, - target: Selector, - route: MetadataRoute[Unpack[tuple[Any, ...]], T], - ) -> T: - ... - - async def pull_metadata( - self, - target: Selector, - route: ..., - ): - return await self.call_fn(CoreCapability.pull, target, route) - - async def query_entities(self, pattern: str, **predicators: FollowsPredicater): - items = _parse_follows(pattern, **predicators) - artifact_map = ChainMap(*self.artifact_collections) - steps = find_querier_steps(artifact_map, items) - - if steps is None: - return - - def build_handler(artifact: tuple[BaseCollector, QueryHandlerPerform]) -> QueryHandler: - async def handler(predicate: Callable[[str, str], bool] | str, previous: Selector | None = None): - collector, entity = artifact - - def _get_instance(_staff: Staff, _cls: type[N]) -> N: - if _cls not in _staff.instances: - res = _staff.instances[_cls] = _cls(_staff) - else: - res = _staff.instances[_cls] - - return res - - async for i in entity(_get_instance(self, collector.cls), predicate, previous): - yield i - - return handler - - def build_predicate(_steps: tuple[_FollowItem, ...]) -> Callable[[str, str], bool]: - mapping = {i.name: i for i in _steps} - - def predicater(key: str, value: str) -> bool: - if key not in mapping: - raise KeyError(f"expected existed key: {key}") - item = mapping[key] - if item.literal is not None: - return value == item.literal - elif item.predicate is not None: - return item.predicate(value) - return True - - return predicater - - handlers = [] - for follow_item, query_record in steps: - handlers.append((follow_item, build_handler(artifact_map[query_record]))) - - r = reduce( - lambda previous, current: query_depth_generator(current[1], build_predicate(current[0]), previous), - handlers, - None, - ) - if TYPE_CHECKING: - assert r is not None - - async for i in r: - yield i diff --git a/avilla/core/ryanvk_old/util.py b/avilla/core/ryanvk_old/util.py deleted file mode 100644 index c29b16a3..00000000 --- a/avilla/core/ryanvk_old/util.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from .overload.target import LookupCollection - - -def _merge_lookup_collection(self: LookupCollection, other: LookupCollection): - for key, branches in self.items(): - if (other_branches := other.pop(key, None)) is None: - continue - - for header, branch in branches.items(): - if (other_branch := other_branches.pop(header, None)) is None: - continue - - _merge_lookup_collection(branch.levels, other_branch.levels) - branch.bind |= other_branch.bind - - branches |= other_branches - - self |= other diff --git a/graia/ryanvk/__init__.py b/graia/ryanvk/__init__.py deleted file mode 100644 index 4cc23a02..00000000 --- a/graia/ryanvk/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from ._runtime import GLOBAL_GALLERY as GLOBAL_GALLERY -from ._runtime import merge as merge -from ._runtime import ref as ref -from .access import Access as Access -from .access import OptionalAccess as OptionalAccess -from .behavior import OverloadBehavior as OverloadBehavior -from .capability import Capability as Capability -from .collector import BaseCollector as BaseCollector -from .endpoint import Endpoint as Endpoint -from .fn import Fn as Fn -from .gateway import GLOBAL_GATEWAY as GLOBAL_GATEWAY -from .gateway import Gateway as Gateway -from .overload import FnOverload as FnOverload -from .overload import PredicateOverload as PredicateOverload -from .overload import SimpleOverload as SimpleOverload -from .overload import TypeOverload as TypeOverload -from .override import OverridePerformEntity as OverridePerformEntity -from .perform import BasePerform as BasePerform -from .staff import Staff as Staff -from .typing import SupportsCollect as SupportsCollect -from .typing import SupportsMerge as SupportsMerge diff --git a/graia/ryanvk/_runtime.py b/graia/ryanvk/_runtime.py deleted file mode 100644 index 2f401dc0..00000000 --- a/graia/ryanvk/_runtime.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -from collections import ChainMap -from itertools import chain -from typing import Any - -from graia.ryanvk.typing import SupportsMerge - -GLOBAL_GALLERY = {} # layout: {namespace: {identify: {...}}}, cover-mode. - - -def ref(namespace: str, identify: str | None = None) -> dict[Any, Any]: - ns = GLOBAL_GALLERY.setdefault(namespace, {}) - scope = ns.setdefault(identify or "_", {}) - return scope - - -def merge(*artifacts: dict[Any, Any]): - chainmap = ChainMap(*artifacts) - total_signatures = list(dict.fromkeys(chain(*[i.keys() for i in artifacts]))) - result = {} - - for sign in total_signatures: - if isinstance(sign, SupportsMerge): - records = [i[sign] for i in artifacts if sign in i] - result[sign] = sign.merge(*records) - else: - result[sign] = chainmap[sign] - - return result diff --git a/graia/ryanvk/access.py b/graia/ryanvk/access.py deleted file mode 100644 index 608f95c7..00000000 --- a/graia/ryanvk/access.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, TypeVar, Union - -from .endpoint import Endpoint - -if TYPE_CHECKING: - from .perform import BasePerform - - -T = TypeVar("T") - - -class Access(Endpoint[T], dynamic=False): - def evaluate(self, instance: BasePerform) -> T: - return instance.staff.components[self.name] - - @staticmethod - def optional(): - return OptionalAccess() - - -class OptionalAccess(Endpoint[Union[T, None]], dynamic=False): - def evaluate(self, instance: BasePerform) -> T | None: - return instance.staff.components.get(self.name) diff --git a/graia/ryanvk/aio.py b/graia/ryanvk/aio.py deleted file mode 100644 index b8990300..00000000 --- a/graia/ryanvk/aio.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import annotations - -import asyncio -from typing import Coroutine - -TASKS: dict[int, asyncio.Task] = {} - - -def queue_task(coro: Coroutine): - task = asyncio.create_task(coro) - task_id = id(task) - TASKS[task_id] = task - task.add_done_callback(lambda x: TASKS.pop(task_id)) diff --git a/graia/ryanvk/behavior.py b/graia/ryanvk/behavior.py deleted file mode 100644 index 03fe55cb..00000000 --- a/graia/ryanvk/behavior.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Callable, TypeVar - -from typing_extensions import Concatenate, ParamSpec - -from .sign import FnImplement, FnRecord - -if TYPE_CHECKING: - from .collector import BaseCollector - from .fn import Fn - from .perform import BasePerform - from .staff import Staff - -T = TypeVar("T") -N = TypeVar("N", bound="BasePerform") -R = TypeVar("R", covariant=True) -P = ParamSpec("P") - - -class OverloadBehavior: - def harvest_record(self, staff: Staff, fn: Fn) -> FnRecord: - result = staff.artifact_map.get(FnImplement(fn)) - if result is None: - raise NotImplementedError - return result - - def harvest_overload( - self, staff: Staff, fn: Fn[P, R], *args: P.args, **kwargs: P.kwargs - ) -> tuple[BaseCollector, Callable[Concatenate[Any, P], R]]: - artifact_record = self.harvest_record(staff, fn) - - if fn.has_overload_capability: - bound_args = fn.shape_signature.bind(*args, **kwargs) - bound_args.apply_defaults() - collections = None - - for overload_item, required_args in fn.overload_param_map.items(): - scope = artifact_record["overload_scopes"][overload_item.identity] - entities = overload_item.get_entities(scope, {i: bound_args.arguments[i] for i in required_args}) - collections = entities if collections is None else collections.intersection(entities) - - if not collections: - raise NotImplementedError - - return collections.pop() # type: ignore - - else: - return artifact_record["record_tuple"] # type: ignore - - - -DEFAULT_BEHAVIOR = OverloadBehavior() diff --git a/graia/ryanvk/capability.py b/graia/ryanvk/capability.py deleted file mode 100644 index cc6b7d7b..00000000 --- a/graia/ryanvk/capability.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - - -class Capability: - ... diff --git a/graia/ryanvk/collector.py b/graia/ryanvk/collector.py deleted file mode 100644 index 300a8947..00000000 --- a/graia/ryanvk/collector.py +++ /dev/null @@ -1,72 +0,0 @@ -from __future__ import annotations - -from contextlib import AbstractContextManager -from typing import TYPE_CHECKING, Any, Callable, TypeVar - -from ._runtime import GLOBAL_GALLERY -from .perform import BasePerform - -if TYPE_CHECKING: - from .typing import P, R, SupportsCollect - -T = TypeVar("T") - - -class BaseCollector: - artifacts: dict[Any, Any] - collected_callbacks: list[Callable[[type[BasePerform]], Any]] - - namespace: str | None = None - identify: str | None = None - - def __init__(self, artifacts: dict[Any, Any] | None = None) -> None: - self.artifacts = artifacts or {} - self.collected_callbacks = [self.__post_collected__] - - def __post_collected__(self, cls: type[BasePerform]): - self.cls = cls - - if self.namespace is not None: - ns: dict = GLOBAL_GALLERY.setdefault(self.namespace, {}) - locate: dict = ns.setdefault(self.identify or "_", {}) - locate.update(self.artifacts) - - @property - def _(self): - class LocalPerformTemplate(BasePerform, native=True): - __collector__ = self - - return LocalPerformTemplate - - @classmethod - def get_forks_name(cls) -> str | None: - ... - - def collect(self, signature: Any, artifact: Any): - self.artifacts[signature] = artifact - - def on_collected(self, func: Callable[[type], Any]): - self.collected_callbacks.append(func) - - def forks(self, collector: BaseCollector): - maybe_name = collector.get_forks_name() - if maybe_name is None: - raise TypeError(f"{type(collector)} doesn't support fork") - collector.artifacts = self.artifacts.setdefault(maybe_name, {}) - collector.collected_callbacks = self.collected_callbacks - return collector - - def attachs(self, collector: BaseCollector): - collector.artifacts = self.artifacts - collector.collected_callbacks = self.collected_callbacks - return collector - - def using(self, context_manager: AbstractContextManager[T]) -> T: - self.on_collected(lambda _: context_manager.__exit__(None, None, None)) - return context_manager.__enter__() - - def post_merge(self, origin: dict): - self.on_collected(lambda _: origin.update(self.artifacts)) - - def entity(self, signature: SupportsCollect[P, R], *args: P.args, **kwargs: P.kwargs) -> R: - return signature.collect(self, *args, **kwargs) diff --git a/graia/ryanvk/endpoint.py b/graia/ryanvk/endpoint.py deleted file mode 100644 index d8646e40..00000000 --- a/graia/ryanvk/endpoint.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -from contextlib import asynccontextmanager -from typing import ( - TYPE_CHECKING, - ClassVar, - Generic, - TypeVar, - overload, -) - -from typing_extensions import Self - -if TYPE_CHECKING: - from graia.ryanvk.perform import BasePerform - - -T = TypeVar("T") - - -class Endpoint(Generic[T]): - __dynamic__: ClassVar[bool] = False - - name: str - - def __init__(self): - ... - - def __set_name__(self, owner: type[BasePerform], name: str): - if self.__dynamic__ and owner.__static__: - raise TypeError(f"{self.__class__.__name__} conflicts with static perform {owner.__name__}") - - self.name = name - - @overload - def __get__(self, instance: None, owner: type) -> Self: - ... - - @overload - def __get__(self, instance: BasePerform, owner: type) -> T: - ... - - def __get__(self, instance: BasePerform | None, owner: type): - if instance is None: - return self - - return self.evaluate(instance) - - def __init_subclass__(cls, *, dynamic: bool) -> None: - return super().__init_subclass__() - - def evaluate(self, instance: BasePerform) -> T: - ... - - @asynccontextmanager - async def lifespan(self, instance: BasePerform): - yield diff --git a/graia/ryanvk/fn.py b/graia/ryanvk/fn.py deleted file mode 100644 index 22640310..00000000 --- a/graia/ryanvk/fn.py +++ /dev/null @@ -1,163 +0,0 @@ -from __future__ import annotations - -import inspect -from typing import TYPE_CHECKING, Any, Callable, Generic, overload - -from typing_extensions import Concatenate, ParamSpec, Self, TypeVar - -from graia.ryanvk.sign import FnImplement - -from .behavior import DEFAULT_BEHAVIOR, OverloadBehavior -from .override import OverridePerformEntity -from .perform import BasePerform - -if TYPE_CHECKING: - from .capability import Capability - from .collector import BaseCollector - from .overload import FnOverload - from .staff import Staff - from .typing import SupportsCollect - -T = TypeVar("T") - -P = ParamSpec("P") -P1 = ParamSpec("P1") -P2 = ParamSpec("P2") - -R = TypeVar("R", covariant=True) -R1 = TypeVar("R1", covariant=True) -R2 = TypeVar("R2", covariant=True) - -N = TypeVar("N", bound="BasePerform") - -VnCallable = TypeVar("VnCallable", bound=Callable, covariant=True) - - -class Fn(Generic[P, R]): - owner: type[BasePerform | Capability] # TODO: review here - name: str - - shape: Callable - shape_signature: inspect.Signature - - behavior: OverloadBehavior - - overload_params: dict[str, FnOverload] - overload_param_map: dict[FnOverload, list[str]] - overload_map: dict[str, FnOverload] - - def __init__( - self: Fn[P, R], - shape: Callable[Concatenate[Any, P], R], - behavior: OverloadBehavior = DEFAULT_BEHAVIOR, - overload_param_map: dict[FnOverload, list[str]] | None = None, - ): - self.shape = shape - self.shape_signature = inspect.Signature(list(inspect.Signature.from_callable(shape).parameters.values())[1:]) - self.behavior = behavior - - self.overload_param_map = overload_param_map or {} - self.overload_params = {i: k for k, v in self.overload_param_map.items() for i in v} - self.overload_map = {i.identity: i for i in self.overload_param_map} - - def __set_name__(self, owner: type[BasePerform], name: str): - self.owner = owner - self.name = name - - @overload - def __get__(self, instance: BasePerform, owner: type) -> Callable[P, R]: - ... - - @overload - def __get__(self, instance: Any, owner: type) -> Self: - ... - - def __get__(self, instance: BasePerform | None, owner: type): - if not isinstance(instance, BasePerform): - return self - - def wrapper(*args: P.args, **kwargs: P.kwargs): - return instance.staff.call_fn(self, *args, **kwargs) - - return wrapper - - @classmethod - def complex(cls, overload_param_map: dict[FnOverload, list[str]], behavior: OverloadBehavior = DEFAULT_BEHAVIOR): - def wrapper(shape: Callable[Concatenate[Any, P1], R1]) -> Fn[P1, R1]: - return cls(shape, overload_param_map=overload_param_map, behavior=behavior) # type: ignore - - return wrapper - - @property - def has_overload_capability(self) -> bool: - return bool(self.overload_param_map) - - def collect( - self, - collector: BaseCollector, - **overload_settings: Any, - ): - def wrapper(entity: Callable[Concatenate[Any, P], R]) -> Callable[Concatenate[Any, P], R]: - artifact = collector.artifacts.setdefault( - FnImplement(self), - { - "overload_enabled": self.has_overload_capability, - "overload_scopes": {}, - "record_tuple": None, - }, - ) - if self.has_overload_capability: - for fn_overload, params in self.overload_param_map.items(): - scope = artifact["overload_scopes"].setdefault(fn_overload.identity, {}) - fn_overload.collect_entity( - collector, - scope, - entity, - fn_overload.get_params_layout(params, overload_settings), - ) - else: - artifact["record_tuple"] = (collector, entity) - - return entity - - return wrapper - - def dyn_perform(self, staff: Staff, cls: type[N]) -> N: - raise ValueError("common staff can only works with static perform") - - def _get_instance(self, staff: Staff, cls: type[N]) -> N: - if cls not in staff.instances: - if cls.__static__: - instance = staff.instances[cls] = cls(staff) - else: - instance = self.dyn_perform(staff, cls) - else: - instance = staff.instances[cls] - - return instance - - def execute( - self, - staff: Staff, - collector: BaseCollector, - entity: Callable[Concatenate[Any, P], R], - *args: P.args, - **kwargs: P.kwargs, - ) -> R: - # get instance - instance = self._get_instance(staff, collector.cls) - - # execute - return entity(instance, *args, **kwargs) - - def override( - self: SupportsCollect[P1, Callable[[Callable[Concatenate[Any, P2], R2]], Any]], # pyright: ignore - collector: BaseCollector, - *args: P1.args, - **kwargs: P1.kwargs, - ): - def wrapper(entity: Callable[Concatenate[Any, P2], R2]) -> OverridePerformEntity[P2, R2]: - self.collect(collector, *args, **kwargs)(entity) - return OverridePerformEntity(collector, self, entity) # type: ignore - - return wrapper diff --git a/graia/ryanvk/gateway.py b/graia/ryanvk/gateway.py deleted file mode 100644 index 27ab975a..00000000 --- a/graia/ryanvk/gateway.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from .perform import BasePerform - from .staff import Staff - - -class Gateway: - registry: dict[type[BasePerform], Staff] - - def __init__(self) -> None: - self.registry = {} - - def register(self, staff: Staff, *perform_types: type[BasePerform]): - for perform_type in perform_types: - self.registry[perform_type] = staff - - -GLOBAL_GATEWAY = Gateway() diff --git a/graia/ryanvk/overload.py b/graia/ryanvk/overload.py deleted file mode 100644 index a90bfa29..00000000 --- a/graia/ryanvk/overload.py +++ /dev/null @@ -1,218 +0,0 @@ -from __future__ import annotations - -from typing import Any, Callable - -from graia.ryanvk.collector import BaseCollector - - -class FnOverload: - @property - def identity(self) -> str: - return self.__class__.__name__ - - def collect_entity( - self, - collector: BaseCollector, - scope: dict[Any, Any], - entity: Any, - params: dict[str, Any], - ) -> None: - scope["_"] = {(collector, entity)} - - def get_entities(self, scope: dict[Any, Any], args: dict[str, Any]) -> set[tuple[BaseCollector, Callable]]: - return scope["_"] - - def merge_scopes(self, *scopes: dict[Any, Any]) -> dict: - return scopes[-1] - - def get_params_layout(self, params: list[str], args: dict[str, Any]) -> dict[str, Any]: - return {param: args[param] for param in params} - - -class SimpleOverload(FnOverload): - @property - def identity(self) -> str: - return str(id(self)) - - def collect_entity( - self, - collector: BaseCollector, - scope: dict[Any, Any], - entity: Any, - params: dict[str, Any], - ) -> None: - record = (collector, entity) - - for param_name, collect_info in params.items(): - collection = scope.setdefault(param_name, {}) - collection.setdefault(collect_info, set()).add(record) - - def get_entities(self, scope: dict[Any, Any], args: dict[str, Any]) -> set[tuple[BaseCollector, Callable]]: - result_sets: list[set] = [] - - for arg_name, arg_value in args.items(): - collection = scope[arg_name] - if arg_value not in collection: - raise NotImplementedError - result_sets.append(collection[arg_value]) - - return result_sets.pop().intersection(*result_sets) - - def merge_scopes(self, *scopes: dict[Any, Any]): - # layout: {arg: {value: set}} - - result = {} - - for scope in scopes: - for param_name, param_collection in scope.items(): - collection = result.setdefault(param_name, {}) - for value, entities in param_collection.items(): - collection.setdefault(value, set()).update(entities) - - return result - - -class TypeOverload(FnOverload): - @property - def identity(self) -> str: - return "type_overload:" + str(id(self)) - - def collect_entity( - self, - collector: BaseCollector, - scope: dict[Any, Any], - entity: Any, - params: dict[str, Any], - ) -> None: - record = (collector, entity) - - for param_name, collect_info in params.items(): - collection = scope.setdefault(param_name, {}) - collection.setdefault(collect_info, set()).add(record) - - def get_entities(self, scope: dict[Any, Any], args: dict[str, Any]) -> set[tuple[BaseCollector, Callable]]: - result_sets: list[set] = [] - - for arg_name, arg_value in args.items(): - collection = scope[arg_name] - key = type(arg_value) - if key not in collection: - raise NotImplementedError - result_sets.append(collection[key]) - - return result_sets.pop().intersection(*result_sets) - - def merge_scopes(self, *scopes: dict[Any, Any]): - # layout: {arg: {value: set}} - - result = {} - - for scope in scopes: - for param_name, param_collection in scope.items(): - collection = result.setdefault(param_name, {}) - for value, entities in param_collection.items(): - collection.setdefault(value, set()).update(entities) - - return result - - -class NoneOverload(FnOverload): - default_factory: Callable[[str], Any] | None = None - bypassing: FnOverload - - def __init__(self, bypassing: FnOverload, *, default_factory: Callable[[str], Any] | None = None) -> None: - self.bypassing = bypassing - self.default_factory = default_factory - - @property - def identity(self) -> str: - return "none_overload:" + str(id(self)) - - def get_params_layout(self, params: list[str], args: dict[str, Any]) -> dict[str, Any]: - if self.default_factory is not None: - return {param: args.get(param) or self.default_factory(param) for param in params} - return super().get_params_layout(params, args) - - def collect_entity( - self, - collector: BaseCollector, - scope: dict[Any, Any], - entity: Any, - params: dict[str, Any], - ) -> None: - record = (collector, entity) - if not scope: - scope.update({"none": {}, "bypassing": {}}) - for param, collect_info in params.items(): - if collect_info is None: - scope["none"].setdefault(param, set()).add(record) - else: - self.bypassing.collect_entity( - collector, - scope["bypassing"].setdefault(param, {}), - entity, - self.bypassing.get_params_layout([param], params), - ) - - def get_entities( - self, scope: dict[Any, Any], args: dict[str, Any] - ) -> set[tuple[BaseCollector, Callable[..., Any]]]: - sets: list[set] = [] - none_collection = scope["none"] - bypassing_collection = scope["bypassing"] - - for arg_name, arg_value in args.items(): - if arg_value is None: - sets.append(none_collection[arg_name]) - else: - sets.append(self.bypassing.get_entities(bypassing_collection[arg_name], {arg_name: arg_value})) - - return sets.pop().intersection(*sets) - -class PredicateOverload(FnOverload): - predicate: Callable[[str, Any], Any] - - def __init__(self, predicate: Callable[[str, Any], Any]) -> None: - self.predicate = predicate - - @property - def identity(self) -> str: - return "predicate_overload:" + str(id(self)) - - def collect_entity( - self, - collector: BaseCollector, - scope: dict[Any, Any], - entity: Any, - params: dict[str, Any], - ) -> None: - record = (collector, entity) - - for param_name, collect_info in params.items(): - collection = scope.setdefault(param_name, {}) - collection.setdefault(collect_info, set()).add(record) - - def get_entities(self, scope: dict[Any, Any], args: dict[str, Any]) -> set[tuple[BaseCollector, Callable]]: - result_sets: list[set] = [] - - for arg_name, arg_value in args.items(): - collection = scope[arg_name] - key = self.predicate(arg_name, arg_value) - if key not in collection: - raise NotImplementedError - result_sets.append(collection[key]) - - return result_sets.pop().intersection(*result_sets) - - def merge_scopes(self, *scopes: dict[Any, Any]): - # layout: {arg: {value: set}} - - result = {} - - for scope in scopes: - for param_name, param_collection in scope.items(): - collection = result.setdefault(param_name, {}) - for value, entities in param_collection.items(): - collection.setdefault(value, set()).update(entities) - - return result diff --git a/graia/ryanvk/override.py b/graia/ryanvk/override.py deleted file mode 100644 index f6a699aa..00000000 --- a/graia/ryanvk/override.py +++ /dev/null @@ -1,95 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar, overload - -from typing_extensions import Concatenate, ParamSpec, Self - -if TYPE_CHECKING: - from .collector import BaseCollector - from .fn import Fn - from .perform import BasePerform - from .typing import SupportsCollect - -P = ParamSpec("P") -R = TypeVar("R", covariant=True) -P1 = ParamSpec("P1") -Q = TypeVar("Q", contravariant=True) - -WrapperCallable = Callable[[Q], Q] -FnRecord = tuple["BaseCollector", Callable] - - -class OverridePerformEntity(Generic[P, R]): - collector: BaseCollector - fn: Fn[P, R] - entity: Callable[Concatenate[Any, P], R] - - origin_perform: type[BasePerform] - name: str - - def __init__( - self, - collector: BaseCollector, - fn: Fn[P, R], - entity: Callable[Concatenate[Any, P], R], - ) -> None: - self.collector = collector - self.fn = fn - self.entity = entity - - @classmethod - def collect( - cls, - collector: BaseCollector, - fn: SupportsCollect[P1, WrapperCallable[Callable[Concatenate[Any, P], R]]], - *args: P1.args, - **kwargs: P1.kwargs, - ): - def wrapper(entity: Callable[Concatenate[Any, P], R]) -> OverridePerformEntity[P, R]: - fn.collect(collector, *args, **kwargs)(entity) - return cls(collector, fn, entity) # type: ignore - - return wrapper - - def __set_name__(self, owner: type[BasePerform], name: str): - self.origin_perform = owner - self.name = name - - @overload - def __get__(self, instance: None, owner: type[BasePerform]) -> Self: - ... - - @overload - def __get__(self, instance: OverridePerformAgent, owner: type) -> Self: - ... - - @overload - def __get__(self, instance: BasePerform, owner: type[BasePerform]) -> OverridePerformAgent[P, R]: - ... - - def __get__(self, instance: Any, owner: type): - if instance is None: - return self - - return OverridePerformAgent(self, instance) - - -class OverridePerformAgent(Generic[P, R]): - perform_entity: OverridePerformEntity[P, R] - instance: BasePerform - - def __init__(self, perform_entity: OverridePerformEntity[P, R], instance: BasePerform) -> None: - self.perform_entity = perform_entity - self.instance = instance - - def __call__(self, *args: P.args, **kwds: P.kwargs) -> R: - return self.perform_entity.entity(self.instance, *args, **kwds) - - def super(self, *args: P.args, **kwds: P.kwargs) -> R: - origin = self.instance.staff - collections = origin.artifact_collections[1:] - staff_type = type(origin) - staff = staff_type(collections, origin.components) - staff.instances.update(origin.instances) - staff.exit_stack = origin.exit_stack - return staff.call_fn(self.perform_entity.fn, *args, **kwds) diff --git a/graia/ryanvk/perform.py b/graia/ryanvk/perform.py deleted file mode 100644 index d2012e5e..00000000 --- a/graia/ryanvk/perform.py +++ /dev/null @@ -1,61 +0,0 @@ -from __future__ import annotations - -from contextlib import AsyncExitStack, asynccontextmanager -from typing import TYPE_CHECKING, Any, ClassVar - -from .endpoint import Endpoint - -if TYPE_CHECKING: - from graia.ryanvk.staff import Staff - - from .collector import BaseCollector - - -class BasePerform: - __collector__: ClassVar[BaseCollector] - # spec said one perform declare / class only binds to one collector instance. - # multi to single or reversed behavior or settings are both denied in spec, - # and, be suggested, actually coding. - - __static__: ClassVar[bool] = True - # when a perform is static, its lifespan won't execute, - # which means dynamic endpoint cannot be used in the perform. - - staff: Staff - - def __init__(self, staff: Staff) -> None: - self.staff = staff - - def __post_init__(self): - ... - - @classmethod - def apply_to(cls, map: dict[Any, Any]): - map.update(cls.__collector__.artifacts) - - @classmethod - def endpoints(cls): - return [(k, v) for k, v in cls.__dict__.items() if isinstance(v, Endpoint)] - - @asynccontextmanager - async def lifespan(self): - async with AsyncExitStack() as stack: - for _, v in self.endpoints(): - await stack.enter_async_context(v.lifespan(self)) - yield self - - @classmethod - def __post_collected__(cls, collect: BaseCollector): - ... - - def __init_subclass__(cls, *, native: bool = False, static: bool = True) -> None: - if native: - return - - collector = cls.__collector__ - cls.__static__ = static - - for i in collector.collected_callbacks: - i(cls) - - cls.__post_collected__(collector) diff --git a/graia/ryanvk/sign.py b/graia/ryanvk/sign.py deleted file mode 100644 index d9a66c36..00000000 --- a/graia/ryanvk/sign.py +++ /dev/null @@ -1,55 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Callable, TypedDict - -from typing_extensions import ParamSpec, TypeVar - -if TYPE_CHECKING: - from .fn import Fn - from .overload import FnOverload - - -P = ParamSpec("P") -R = TypeVar("R", covariant=True) -R1 = TypeVar("R1", covariant=True) - -P1 = ParamSpec("P1") -P2 = ParamSpec("P2") -R2 = TypeVar("R2", covariant=True) - - -class _MergeScopesInfo(TypedDict): - overload_item: FnOverload - scopes: list[dict[Any, Any]] - - -class FnRecord(TypedDict): - overload_enabled: bool - overload_scopes: dict[str, dict[Any, Any]] - record_tuple: Callable | None - - -@dataclass(eq=True, frozen=True) -class FnImplement: - fn: Fn - - def merge(self, *records: FnRecord): - scopes_info: dict[str, _MergeScopesInfo] = {} - scopes = {} - - for record in records: - for k, v in record["overload_scopes"].items(): - overload_item = self.fn.overload_map[k] - info = scopes_info.setdefault(k, {"overload_item": overload_item, "scopes": []}) - info["scopes"].append(v) - - for identity, info in scopes_info.items(): - merged_scope = info["overload_item"].merge_scopes(*info["scopes"]) - scopes[identity] = merged_scope - - return { - "overload_enabled": self.fn.has_overload_capability, - "overload_scopes": scopes, - "record_tuple": records[-1]["record_tuple"], - } diff --git a/graia/ryanvk/staff.py b/graia/ryanvk/staff.py deleted file mode 100644 index 11ff4d7c..00000000 --- a/graia/ryanvk/staff.py +++ /dev/null @@ -1,69 +0,0 @@ -from __future__ import annotations - -from collections import ChainMap -from contextlib import AsyncExitStack, asynccontextmanager -from copy import copy -from typing import TYPE_CHECKING, Any, Callable, Protocol, TypeVar, overload - -from typing_extensions import ParamSpec - -if TYPE_CHECKING: - from .fn import Fn - from .perform import BasePerform - -P = ParamSpec("P") -R = TypeVar("R", covariant=True) -VnCallable = TypeVar("VnCallable", bound=Callable) - -class Staff: - artifact_collections: list[dict[Any, Any]] - artifact_map: ChainMap[Any, Any] - components: dict[str, Any] - exit_stack: AsyncExitStack - instances: dict[type, Any] - - def __init__(self, artifacts_collections: list[dict[Any, Any]], components: dict[str, Any]) -> None: - self.artifact_collections = artifacts_collections - self.artifact_map = ChainMap(*artifacts_collections) - self.components = components - self.exit_stack = AsyncExitStack() - self.instances = {} - - def call_fn(self, fn: Fn[P, R], *args: P.args, **kwargs: P.kwargs) -> R: - collector, entity = fn.behavior.harvest_overload(self, fn, *args, **kwargs) - return fn.execute(self, collector, entity, *args, **kwargs) - - class PostInitShape(Protocol[P]): - def __post_init__(self, *args: P.args, **kwargs: P.kwargs) -> Any: - ... - - @overload - def inject(self, perform_type: type[PostInitShape[P]], *args: P.args, **kwargs: P.kwargs) -> None: - ... - - @overload - def inject(self, perform_type: type[BasePerform]) -> None: - ... - - def inject(self, perform_type: ..., *args, **kwargs): - perform = perform_type(self) - perform.__post_init__(*args, **kwargs) - self.artifact_collections.insert(0, perform.__collector__.artifacts) - - async def maintain(self, perform: BasePerform): - await self.exit_stack.enter_async_context(perform.lifespan()) - - @asynccontextmanager - async def lifespan(self): - async with self.exit_stack: - yield self - - def ext(self, components: dict[str, Any]): - instance = copy(self) - instance.components.update(components) - return instance - - def get_fn_call(self, fn: Fn[P, R]) -> Callable[P, R]: - def wrapper(*args: P.args, **kwargs: P.kwargs): - return self.call_fn(fn, *args, **kwargs) - return wrapper diff --git a/graia/ryanvk/typing.py b/graia/ryanvk/typing.py deleted file mode 100644 index 32818607..00000000 --- a/graia/ryanvk/typing.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Generic, Protocol, Union, runtime_checkable - -from typing_extensions import ParamSpec, TypeVar - -if TYPE_CHECKING: - from .collector import BaseCollector # noqa - -P = ParamSpec("P") -P1 = ParamSpec("P1") -R = TypeVar("R", infer_variance=True) -T = TypeVar("T") -C = TypeVar("C", bound="BaseCollector") -CQ = TypeVar("CQ", bound="BaseCollector") - - -class SupportsCollect(Protocol[P, R]): - def collect(self, collector: Any, *args: P.args, **kwargs: P.kwargs) -> R: # type: ignore - ... - - -R1 = TypeVar("R1", covariant=True) -R2 = TypeVar("R2", covariant=True) - - -@dataclass(frozen=True, eq=True) -class Twin(Generic[R1, R2]): - _0: R1 - _1: R2 - - -class ClsIncluded(Protocol[T]): - cls: type[T] - - -Cr = TypeVar("Cr", bound="BaseCollector", covariant=True) - - -@runtime_checkable -class SupportsMerge(Protocol): - def merge(self, *records: dict): - ... - - -class LayoutProtocolProperty(Protocol): - @property - def get_artifact_layout(self) -> dict: - ... - - -class LayoutProtocolAttr(Protocol): - get_artifact_layout: dict - - -LayoutProtocol = Union[LayoutProtocolProperty, LayoutProtocolAttr] From 0d1c25bb2c7b9fc60e0ee35d0470b96759f4b34f Mon Sep 17 00:00:00 2001 From: Elaina Date: Mon, 18 Mar 2024 18:19:04 +0800 Subject: [PATCH 07/24] remove old store --- avilla/core/utilles/store.py | 279 ----------------------------------- 1 file changed, 279 deletions(-) delete mode 100644 avilla/core/utilles/store.py diff --git a/avilla/core/utilles/store.py b/avilla/core/utilles/store.py deleted file mode 100644 index 0d6e8ab1..00000000 --- a/avilla/core/utilles/store.py +++ /dev/null @@ -1,279 +0,0 @@ -""" -参考路径如下: - -## Cache path: - macOS: ~/Library/Caches/ - - Unix: ~/.cache/ (XDG default) - - Windows: C:\\Users\\\\AppData\\Local\\\\Cache - -## Data path: - macOS: ~/Library/Application Support/ - - Unix: ~/.local/share/ or in $XDG_DATA_HOME, if defined - - Win XP (not roaming): C:\\Documents and Settings\\\\Application Data\\ - - Win 7 (not roaming): C:\\Users\\\\AppData\\Local\\ - -## Config path: - macOS: same as user_data_dir - - Unix: ~/.config/ - - Win XP (roaming): C:\\Documents and Settings\\\\Local Settings\\Application Data\\ - - Win 7 (roaming): C:\\Users\\\\AppData\\Roaming\\ - -""" - -import os -import sys -from pathlib import Path -from typing import Callable, Literal, Optional - -from typing_extensions import ParamSpec - -WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") - - -def user_cache_dir(appname: str) -> Path: - r""" - Return full path to the user-specific cache dir for this application. - "appname" is the name of application. - Typical user cache directories are: - macOS: ~/Library/Caches/ - Unix: ~/.cache/ (XDG default) - Windows: C:\\Users\\\\AppData\\Local\\\\Cache - On Windows the only suggestion in the MSDN docs is that local settings go - in the `CSIDL_LOCAL_APPDATA` directory. This is identical to the - non-roaming app data dir (the default returned by `user_data_dir`). Apps - typically put cache data somewhere *under* the given dir here. Some - examples: - ...\\Mozilla\\Firefox\\Profiles\\\\Cache - ...\\Acme\\SuperApp\\Cache\\1.0 - OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. - """ - if WINDOWS: - return _get_win_folder("CSIDL_LOCAL_APPDATA") / appname / "Cache" - elif sys.platform == "darwin": - return Path("~/Library/Caches").expanduser() / appname - else: - return Path(os.getenv("XDG_CACHE_HOME", "~/.cache")).expanduser() / appname - - -def user_data_dir(appname: str, roaming: bool = False) -> Path: - r""" - Return full path to the user-specific data dir for this application. - "appname" is the name of application. - If None, just the system directory is returned. - "roaming" (boolean, default False) can be set True to use the Windows - roaming appdata directory. That means that for users on a Windows - network setup for roaming profiles, this user data will be - sync'd on login. See - - for a discussion of issues. - Typical user data directories are: - macOS: ~/Library/Application Support/ - Unix: ~/.local/share/ # or in - $XDG_DATA_HOME, if defined - Win XP (not roaming): C:\\Documents and Settings\\\\ ... - ...Application Data\\ - Win XP (roaming): C:\\Documents and Settings\\\\Local ... - ...Settings\\Application Data\\ - Win 7 (not roaming): C:\\Users\\\\AppData\\Local\\ - Win 7 (roaming): C:\\Users\\\\AppData\\Roaming\\ - For Unix, we follow the XDG spec and support $XDG_DATA_HOME. - That means, by default "~/.local/share/". - """ - if WINDOWS: - const = "CSIDL_APPDATA" if roaming else "CSIDL_LOCAL_APPDATA" - return Path(_get_win_folder(const)) / appname # type: ignore - elif sys.platform == "darwin": - return Path("~/Library/Application Support/").expanduser() / appname - else: - return Path(os.getenv("XDG_DATA_HOME", "~/.local/share")).expanduser() / appname - - -def user_config_dir(appname: str, roaming: bool = True) -> Path: - """Return full path to the user-specific config dir for this application. - "appname" is the name of application. - If None, just the system directory is returned. - "roaming" (boolean, default True) can be set False to not use the - Windows roaming appdata directory. That means that for users on a - Windows network setup for roaming profiles, this user data will be - sync'd on login. See - - for a discussion of issues. - Typical user data directories are: - macOS: same as user_data_dir - Unix: ~/.config/ - Win *: same as user_data_dir - For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. - That means, by default "~/.config/". - """ - if WINDOWS: - return user_data_dir(appname, roaming=roaming) - elif sys.platform == "darwin": - return user_data_dir(appname) - else: - return Path(os.getenv("XDG_CONFIG_HOME", "~/.config")).expanduser() / appname - - -# -- Windows support functions -- -def _get_win_folder_from_registry( - csidl_name: Literal["CSIDL_APPDATA", "CSIDL_COMMON_APPDATA", "CSIDL_LOCAL_APPDATA"] -) -> Path: - """ - This is a fallback technique at best. I'm not sure if using the - registry for this guarantees us the correct answer for all CSIDL_* - names. - """ - import winreg - - shell_folder_name = { - "CSIDL_APPDATA": "AppData", - "CSIDL_COMMON_APPDATA": "Common AppData", - "CSIDL_LOCAL_APPDATA": "Local AppData", - }[csidl_name] - - key = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, - r"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", - ) - directory, _type = winreg.QueryValueEx(key, shell_folder_name) - return Path(directory) - - -def _get_win_folder_with_ctypes( - csidl_name: Literal["CSIDL_APPDATA", "CSIDL_COMMON_APPDATA", "CSIDL_LOCAL_APPDATA"] -) -> Path: - import ctypes - - csidl_const = { - "CSIDL_APPDATA": 26, - "CSIDL_COMMON_APPDATA": 35, - "CSIDL_LOCAL_APPDATA": 28, - }[csidl_name] - - buf = ctypes.create_unicode_buffer(1024) - ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) - - # Downgrade to short path name if have highbit chars. See - # . - has_high_char = any(ord(c) > 255 for c in buf) - if has_high_char: - buf2 = ctypes.create_unicode_buffer(1024) - if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): - buf = buf2 - - return Path(buf.value) - - -if WINDOWS: - try: - _get_win_folder = _get_win_folder_with_ctypes - except ImportError: - _get_win_folder = _get_win_folder_from_registry - - -P = ParamSpec("P") - -APP_NAME = "avilla" -BASE_CACHE_DIR = user_cache_dir(APP_NAME).resolve() -BASE_CONFIG_DIR = user_config_dir(APP_NAME).resolve() -BASE_DATA_DIR = user_data_dir(APP_NAME).resolve() - - -def _ensure_dir(path: Path) -> None: - if not path.exists(): - path.mkdir(parents=True, exist_ok=True) - elif not path.is_dir(): - raise RuntimeError(f"{path} is not a directory") - - -def _auto_create_dir(func: Callable[P, Path]) -> Callable[P, Path]: - def wrapper(*args: P.args, **kwargs: P.kwargs) -> Path: - path = func(*args, **kwargs) - _ensure_dir(path) - return path - - return wrapper - - -@_auto_create_dir -def get_cache_dir(plugin_name: Optional[str]) -> Path: - """ - macOS: ~/Library/Caches/ - - Unix: ~/.cache/ (XDG default) - - Windows: C:\\Users\\\\AppData\\Local\\\\Cache - """ - return BASE_CACHE_DIR / plugin_name if plugin_name else BASE_CACHE_DIR - - -def get_cache_file(plugin_name: Optional[str], filename: str) -> Path: - """ - macOS: ~/Library/Caches/ - - Unix: ~/.cache/ (XDG default) - - Windows: C:\\Users\\\\AppData\\Local\\\\Cache - """ - return get_cache_dir(plugin_name) / filename - - -@_auto_create_dir -def get_config_dir(plugin_name: Optional[str]) -> Path: - """ - macOS: same as user_data_dir - - Unix: ~/.config/ - - Win XP (roaming): C:\\Documents and Settings\\\\Local Settings\\Application Data\\ - - Win 7 (roaming): C:\\Users\\\\AppData\\Roaming\\ - """ - return BASE_CONFIG_DIR / plugin_name if plugin_name else BASE_CONFIG_DIR - - -def get_config_file(plugin_name: Optional[str], filename: str) -> Path: - """ - macOS: same as user_data_dir - - Unix: ~/.config/ - - Win XP (roaming): C:\\Documents and Settings\\\\Local Settings\\Application Data\\ - - Win 7 (roaming): C:\\Users\\\\AppData\\Roaming\\ - """ - return get_config_dir(plugin_name) / filename - - -@_auto_create_dir -def get_data_dir(plugin_name: Optional[str]) -> Path: - """ - macOS: ~/Library/Application Support/ - - Unix: ~/.local/share/ or in $XDG_DATA_HOME, if defined - - Win XP (not roaming): C:\\Documents and Settings\\\\Application Data\\ - - Win 7 (not roaming): C:\\Users\\\\AppData\\Local\\ - """ - return BASE_DATA_DIR / plugin_name if plugin_name else BASE_DATA_DIR - - -def get_data_file(plugin_name: Optional[str], filename: str) -> Path: - """ - macOS: ~/Library/Application Support/ - - Unix: ~/.local/share/ or in $XDG_DATA_HOME, if defined - - Win XP (not roaming): C:\\Documents and Settings\\\\Application Data\\ - - Win 7 (not roaming): C:\\Users\\\\AppData\\Local\\ - """ - return get_data_dir(plugin_name) / filename From f44474031cafd2b959c005076d1d4fb256e9d147 Mon Sep 17 00:00:00 2001 From: Elaina Date: Mon, 18 Mar 2024 18:21:41 +0800 Subject: [PATCH 08/24] use pypi ver flywheel --- avilla/core/application.py | 5 +- avilla/core/builtins/capability.py | 8 +- avilla/core/builtins/resource_fetch.py | 3 +- avilla/core/context/__init__.py | 6 +- avilla/core/dispatchers.py | 6 +- avilla/core/event.py | 9 +- avilla/core/flywheel/__init__.py | 0 avilla/core/flywheel/context.py | 61 -------------- avilla/core/flywheel/entity.py | 56 ------------- avilla/core/flywheel/fn/__init__.py | 0 avilla/core/flywheel/fn/base.py | 76 ----------------- avilla/core/flywheel/fn/compose.py | 83 ------------------- avilla/core/flywheel/fn/implement.py | 76 ----------------- avilla/core/flywheel/fn/overload.py | 47 ----------- avilla/core/flywheel/fn/record.py | 20 ----- avilla/core/flywheel/globals.py | 57 ------------- avilla/core/flywheel/instance_of.py | 30 ------- avilla/core/flywheel/overloads.py | 91 -------------------- avilla/core/flywheel/scoped.py | 106 ------------------------ avilla/core/flywheel/typing.py | 52 ------------ avilla/core/{_runtime.py => globals.py} | 18 ++-- avilla/core/message.py | 4 +- avilla/core/protocol.py | 8 +- avilla/core/request.py | 10 +-- avilla/core/ryanvk/overloads.py | 8 +- avilla/nonebridge/adapter.py | 4 +- pdm.lock | 22 +++-- pyproject.toml | 1 + 28 files changed, 65 insertions(+), 802 deletions(-) delete mode 100644 avilla/core/flywheel/__init__.py delete mode 100644 avilla/core/flywheel/context.py delete mode 100644 avilla/core/flywheel/entity.py delete mode 100644 avilla/core/flywheel/fn/__init__.py delete mode 100644 avilla/core/flywheel/fn/base.py delete mode 100644 avilla/core/flywheel/fn/compose.py delete mode 100644 avilla/core/flywheel/fn/implement.py delete mode 100644 avilla/core/flywheel/fn/overload.py delete mode 100644 avilla/core/flywheel/fn/record.py delete mode 100644 avilla/core/flywheel/globals.py delete mode 100644 avilla/core/flywheel/instance_of.py delete mode 100644 avilla/core/flywheel/overloads.py delete mode 100644 avilla/core/flywheel/scoped.py delete mode 100644 avilla/core/flywheel/typing.py rename avilla/core/{_runtime.py => globals.py} (68%) diff --git a/avilla/core/application.py b/avilla/core/application.py index 78cf1694..a64f6680 100644 --- a/avilla/core/application.py +++ b/avilla/core/application.py @@ -11,7 +11,7 @@ from launart.service import Service from loguru import logger -from avilla.core._runtime import get_current_avilla +from avilla.core.globals import get_current_avilla from avilla.core.account import AccountInfo, BaseAccount from avilla.core.dispatchers import AvillaBuiltinDispatcher from avilla.core.event import MetadataModified @@ -65,8 +65,6 @@ def __init__( self.launch_manager.add_component(self.service) self.broadcast.finale_dispatchers.append(AvillaBuiltinDispatcher(self)) - self.__init_isolate__() - if message_cache_size > 0: from avilla.core.context import Context from avilla.core.message import Message @@ -198,6 +196,7 @@ def wrapper(func: Callable[[TE], None]): @staticmethod def _require_builtins(): + with import avilla.core.builtins.resource_fetch # noqa: F401 @classmethod diff --git a/avilla/core/builtins/capability.py b/avilla/core/builtins/capability.py index 08360b87..86bb96c0 100644 --- a/avilla/core/builtins/capability.py +++ b/avilla/core/builtins/capability.py @@ -2,12 +2,12 @@ from typing import TYPE_CHECKING, Any, Protocol, TypeVar, overload +from flywheel.fn.base import Fn +from flywheel.fn.compose import FnCompose, OverloadRecorder +from flywheel.fn.record import FnRecord +from flywheel.overloads import SimpleOverload, TypeOverload from typing_extensions import Unpack -from avilla.core.flywheel.fn.base import Fn -from avilla.core.flywheel.fn.compose import FnCompose, OverloadRecorder -from avilla.core.flywheel.fn.record import FnRecord -from avilla.core.flywheel.overloads import SimpleOverload, TypeOverload from avilla.core.metadata import Metadata, MetadataRoute from avilla.core.resource import Resource, T from avilla.core.ryanvk.overloads import TargetOverload diff --git a/avilla/core/builtins/resource_fetch.py b/avilla/core/builtins/resource_fetch.py index e820382e..0a1fcacf 100644 --- a/avilla/core/builtins/resource_fetch.py +++ b/avilla/core/builtins/resource_fetch.py @@ -1,6 +1,7 @@ from __future__ import annotations -from avilla.core.flywheel.scoped import scoped_collect +from flywheel.scoped import scoped_collect + from avilla.core.resource import LocalFileResource, RawResource, UrlResource try: diff --git a/avilla/core/context/__init__.py b/avilla/core/context/__init__.py index 2e4f9100..0429c837 100644 --- a/avilla/core/context/__init__.py +++ b/avilla/core/context/__init__.py @@ -5,14 +5,14 @@ from typing_extensions import ParamSpec, Unpack -from avilla.core._runtime import cx_context +from avilla.core.globals import CONTEXT_CONTEXT_VAR from avilla.core.account import BaseAccount +from avilla.core.builtins.capability import CoreCapability from avilla.core.metadata import Metadata, MetadataRoute from avilla.core.platform import Land from avilla.core.resource import Resource from avilla.core.selector import Selectable, Selector from avilla.core.utilles import classproperty -from avilla.core.builtins.capability import CoreCapability from ._roles import ( ContextClientSelector, @@ -92,7 +92,7 @@ def _collect_metadatas(self, target: Selector | Selectable, *metadatas: Metadata @classproperty @classmethod def current(cls) -> Context: - return cx_context.get() + return CONTEXT_CONTEXT_VAR.get() @contextmanager def lookup_scope(self): diff --git a/avilla/core/dispatchers.py b/avilla/core/dispatchers.py index 78394f51..2105cd63 100644 --- a/avilla/core/dispatchers.py +++ b/avilla/core/dispatchers.py @@ -5,7 +5,7 @@ from graia.broadcast.entities.dispatcher import BaseDispatcher -from avilla.core._runtime import cx_protocol +from avilla.core.globals import PROTOCOL_CONTEXT_VAR from avilla.core.account import BaseAccount from avilla.core.context import Context from avilla.core.event import AvillaEvent @@ -33,9 +33,9 @@ async def catch(self, interface: DispatcherInterface[AvillaEvent]): if ( isclass(interface.annotation) and issubclass(interface.annotation, BaseProtocol) - and isinstance(cx_protocol.get(None), interface.annotation) + and isinstance(PROTOCOL_CONTEXT_VAR.get(None), interface.annotation) ): - return cx_protocol.get(None) + return PROTOCOL_CONTEXT_VAR.get(None) if ( isinstance(interface.event, AvillaEvent) and isclass(interface.annotation) diff --git a/avilla/core/event.py b/avilla/core/event.py index c1fab43c..6c59d1c0 100644 --- a/avilla/core/event.py +++ b/avilla/core/event.py @@ -13,7 +13,7 @@ from avilla.core.metadata import Metadata, MetadataRoute from avilla.core.selector import Selector -from ._runtime import cx_context +from avilla.core.globals import CONTEXT_CONTEXT_VAR if TYPE_CHECKING: from graia.broadcast.interfaces.dispatcher import DispatcherInterface @@ -31,8 +31,9 @@ class Dispatcher(BaseDispatcher): async def beforeExecution(interface: DispatcherInterface[AvillaEvent]): if interface.depth < 1: interface.local_storage["avilla_context"] = interface.event.context - interface.local_storage["_context_token"] = cx_context.set(interface.event.context) - await interface.event.context.staff.exit_stack.__aenter__() + interface.local_storage["_context_token"] = CONTEXT_CONTEXT_VAR.set(interface.event.context) + + # TODO: Lookup Context @staticmethod async def catch(interface: DispatcherInterface[AvillaEvent]): @@ -56,7 +57,7 @@ async def catch(interface: DispatcherInterface[AvillaEvent]): @staticmethod async def afterExecution(interface: DispatcherInterface[AvillaEvent], exc, tb): if interface.depth < 1: - cx_context.reset(interface.local_storage["_context_token"]) + CONTEXT_CONTEXT_VAR.reset(interface.local_storage["_context_token"]) await interface.event.context.staff.exit_stack.__aexit__(type(exc), exc, tb) diff --git a/avilla/core/flywheel/__init__.py b/avilla/core/flywheel/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/avilla/core/flywheel/context.py b/avilla/core/flywheel/context.py deleted file mode 100644 index 5c6f5301..00000000 --- a/avilla/core/flywheel/context.py +++ /dev/null @@ -1,61 +0,0 @@ -from __future__ import annotations - -from contextlib import contextmanager -from typing import TYPE_CHECKING, Any - -from .typing import TEntity - -if TYPE_CHECKING: - from .fn.record import FnImplement, FnRecord - - -class CollectContext: - fn_implements: dict[FnImplement, FnRecord] - - def __init__(self): - self.fn_implements = {} - - def collect(self, entity: TEntity) -> TEntity: - return entity.collect(self) - - @contextmanager - def collect_scope(self): - from .globals import COLLECTING_CONTEXT_VAR - - token = COLLECTING_CONTEXT_VAR.set(self) - try: - yield self - finally: - COLLECTING_CONTEXT_VAR.reset(token) - - @contextmanager - def lookup_scope(self): - from .globals import LOOKUP_LAYOUT_VAR - - token = LOOKUP_LAYOUT_VAR.set((self, *LOOKUP_LAYOUT_VAR.get())) - try: - yield self - finally: - LOOKUP_LAYOUT_VAR.reset(token) - - -class InstanceContext: - instances: dict[type, Any] - - def __init__(self): - self.instances = {} - - @contextmanager - def scope(self, *, inherit: bool = True): - from .globals import INSTANCE_CONTEXT_VAR - - original = self.instances - if inherit: - self.instances = {**INSTANCE_CONTEXT_VAR.get().instances, **self.instances} - - token = INSTANCE_CONTEXT_VAR.set(self) - try: - yield self - finally: - INSTANCE_CONTEXT_VAR.reset(token) - self.instances = original diff --git a/avilla/core/flywheel/entity.py b/avilla/core/flywheel/entity.py deleted file mode 100644 index c0316832..00000000 --- a/avilla/core/flywheel/entity.py +++ /dev/null @@ -1,56 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, TypedDict, final - -from .scoped import scoped_collect - -if TYPE_CHECKING: - from .context import CollectContext - - -class EntityAssignInfo(TypedDict, total=False): - cls: type - name: str - annotation: Any - - -class BaseEntity: - collect_context: CollectContext | None = None - - def collect(self, collector: CollectContext): - self.collect_context = collector - - if isinstance(self.collect_context, scoped_collect): - self.collect_context.on_collected(self._fallback_collected_callback) - - return self - - def assign_callback(self, info: EntityAssignInfo): - ... - - @final - def _fallback_collected_callback(self, collector): - self.assign_callback({}) - - def __set_name__(self, owner: type, name: str): - if self.collect_context is None: - return - - if isinstance(self.collect_context, scoped_collect): - if owner is not self.collect_context.cls: - return - - try: - self.collect_context.remove_collected_callback(self._fallback_collected_callback) - except ValueError: - pass - - @self.collect_context.on_collected - def _(collector: scoped_collect): - if collector.cls is None: - return - - if name in collector.cls.__annotations__: - self.assign_callback({"cls": collector.cls, "name": name, "annotation": collector.cls.__annotations__[name]}) - else: - self.assign_callback({"cls": collector.cls, "name": name}) diff --git a/avilla/core/flywheel/fn/__init__.py b/avilla/core/flywheel/fn/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/avilla/core/flywheel/fn/base.py b/avilla/core/flywheel/fn/base.py deleted file mode 100644 index feb77ee8..00000000 --- a/avilla/core/flywheel/fn/base.py +++ /dev/null @@ -1,76 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Callable, Generic, Protocol, Type, TypeVar - -from typing_extensions import Concatenate - -from ..entity import BaseEntity -from ..globals import iter_layout -from ..typing import CR, AssignKeeper, Call, OutP, P, R -from .compose import FnCompose -from .implement import FnImplementEntity, OverloadRecorder - -if TYPE_CHECKING: - from .record import FnRecord - -K = TypeVar("K") - -CCall = TypeVar("CCall", bound=Callable, covariant=True) -CCollect = TypeVar("CCollect", bound=Callable, covariant=True) -C = TypeVar("C", bound=Callable, covariant=True) - - -class ComposeShape(Protocol[CCollect, CCall]): - @property - def collect(self) -> CCollect: - ... - - @property - def call(self) -> CCall: - ... - - -FnDef = Type[ComposeShape[CCollect, Callable[Concatenate["FnRecord", OutP], R]]] - - -class Fn(Generic[CCollect, CCall], BaseEntity): - desc: FnCompose - - def __init__(self, compose: type[FnCompose]): - self.desc = compose(self) - - @classmethod - def declare(cls, desc: FnDef[C, OutP, R]) -> Fn[C, Callable[OutP, R]]: - return cls(desc) # type: ignore - - @property - def impl( - self: Fn[Callable[Concatenate[OverloadRecorder[CR], OutP], Any], Any], - ) -> Callable[OutP, AssignKeeper[Call[..., CR]]]: - def wrapper(*args: OutP.args, **kwargs: OutP.kwargs): - def inner(impl: Callable[P, R] | FnImplementEntity[Callable[P, R]]): - if not isinstance(impl, FnImplementEntity): - impl = FnImplementEntity(impl) - - impl.add_target(self, *args, **kwargs) - return impl - - return inner - - return wrapper # type: ignore - - def _call(self, *args, **kwargs): - signature = self.desc.signature() - - for context in iter_layout(signature): - if signature not in context.fn_implements: - continue - - record = context.fn_implements[signature] - return record.spec.desc.call(record, *args, **kwargs) - else: - raise NotImplementedError("cannot find any record with given fn declaration") - - @property - def __call__(self) -> CCall: - return self._call # type: ignore diff --git a/avilla/core/flywheel/fn/compose.py b/avilla/core/flywheel/fn/compose.py deleted file mode 100644 index a32d00cd..00000000 --- a/avilla/core/flywheel/fn/compose.py +++ /dev/null @@ -1,83 +0,0 @@ -from __future__ import annotations - -from functools import reduce -from typing import TYPE_CHECKING, Any, Callable, Final, Generic, TypeVar, overload - -from typing_extensions import Concatenate - -from ..overloads import SINGLETON_OVERLOAD -from ..typing import CT, P1, Collectable, ImplementSample, P, R -from .record import FnImplement, FnRecord - -if TYPE_CHECKING: - from .base import Fn - from .implement import OverloadRecorder - -FC = TypeVar("FC", bound="FnCompose") - - -class FnCompose: - singleton: Final = SINGLETON_OVERLOAD - fn: Fn - - def __init__(self, fn: Fn): - self.fn = fn - - def call(self, record: FnRecord, *args, **kwargs) -> Any: - return next(iter(SINGLETON_OVERLOAD.dig(record, None).keys()))(*args, **kwargs) - - def collect(self, recorder: OverloadRecorder) -> None: - recorder.use(self.singleton, None) - - def signature(self): - return FnImplement(self.fn) - - @overload - def load(self: ImplementSample[CT], *collections: dict[Callable, None]) -> HarvestWrapper[CT]: - ... - - @overload - def load( - self: Collectable[Concatenate[OverloadRecorder[Callable[P, R]], P1]], *collections: dict[Callable, None] - ) -> HarvestWrapper[Callable[P, R]]: - ... - - def load(self, *collections: dict[Callable, None]): # type: ignore - if not collections: - raise TypeError("at least one collection is required") - - col = list(collections) - init = col.pop(0) - - if not col: - return HarvestWrapper(init) - - r = reduce(lambda x, y: {i: None for i in x if i in y}, col, init) - return HarvestWrapper(r) - - -class HarvestWrapper(Generic[CT]): - harvest: dict[CT, None] - - def __init__(self, harvest: dict[CT, None]): - self.harvest = harvest - - @property - def first(self) -> CT: - if not self.harvest: - raise NotImplementedError("cannot lookup any implementation with given arguments") - - return next(iter(self.harvest)) - - @property - def __call__(self): - return self.first - - def __iter__(self): - if not self.harvest: - raise NotImplementedError("cannot lookup any implementation with given arguments") - - return iter(self.harvest) - - def __bool__(self): - return bool(self.harvest) diff --git a/avilla/core/flywheel/fn/implement.py b/avilla/core/flywheel/fn/implement.py deleted file mode 100644 index ea443302..00000000 --- a/avilla/core/flywheel/fn/implement.py +++ /dev/null @@ -1,76 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Callable, Generic - -from typing_extensions import Concatenate - -from ..entity import BaseEntity -from ..scoped import scoped_collect -from ..typing import CR, P -from .record import FnRecord - -if TYPE_CHECKING: - from .base import Fn - from .overload import FnOverload, TCollectValue - - -@dataclass -class OverloadRecorder(Generic[CR]): - target: FnRecord - implement: CR - operators: list[tuple[str, FnOverload, Any]] = field(default_factory=list) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.done() - - def done(self): - for name, rule, value in self.operators: - rule.lay(self.target, value, self.implement, name=name) - - self.target.entities[frozenset(self.operators)] = self.implement - - def use(self, target: FnOverload[Any, TCollectValue, Any], value: TCollectValue, *, name: str | None = None): - self.operators.append((name or target.name, target, value)) - return self - - -class FnImplementEntity(Generic[CR], BaseEntity): - targets: list[tuple[Fn, tuple[Any, ...], dict[str, Any]]] - impl: CR - - def __init__(self, impl: CR): - self.targets = [] - self.impl = impl - - def add_target(self, fn: Fn[Callable[Concatenate[Any, P], Any], Any], *args: P.args, **kwargs: P.kwargs): - self.targets.append((fn, args, kwargs)) - - def collect(self, collector: scoped_collect): - super().collect(collector) - - for fn, args, kwargs in self.targets: - record_signature = fn.desc.signature() - if record_signature in collector.fn_implements: - record = collector.fn_implements[record_signature] - else: - record = collector.fn_implements[record_signature] = FnRecord(fn) - - with OverloadRecorder(record, self.impl) as recorder: - fn.desc.collect(recorder, *args, **kwargs) - - return self - - def _call(self, *args, **kwargs): - return self.impl(*args, **kwargs) - - @property - def __call__(self) -> CR: - return self._call # type: ignore - - @property - def super(self) -> CR: - return self.targets[0][0].__call__ diff --git a/avilla/core/flywheel/fn/overload.py b/avilla/core/flywheel/fn/overload.py deleted file mode 100644 index 20fe411e..00000000 --- a/avilla/core/flywheel/fn/overload.py +++ /dev/null @@ -1,47 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Callable, Generic, TypeVar - -from typing_extensions import final - -if TYPE_CHECKING: - from .record import FnRecord - -TOverload = TypeVar("TOverload", bound="FnOverload", covariant=True) -TCallValue = TypeVar("TCallValue") -TCollectValue = TypeVar("TCollectValue") -TSignature = TypeVar("TSignature") - - -class FnOverload(Generic[TSignature, TCollectValue, TCallValue]): - def __init__(self, name: str) -> None: - self.name = name - - @final - def dig(self, record: FnRecord, call_value: TCallValue, *, name: str | None = None) -> dict[Callable, None]: - name = name or self.name - if name not in record.scopes: - raise NotImplementedError("cannot lookup any implementation with given arguments") - - return self.harvest(record.scopes[name], call_value) - - @final - def lay(self, record: FnRecord, collect_value: TCollectValue, implement: Callable, *, name: str | None = None): - name = name or self.name - if name not in record.scopes: - record.scopes[name] = {} - - collection = self.collect(record.scopes[name], self.digest(collect_value)) - collection[implement] = None - - def digest(self, collect_value: TCollectValue) -> TSignature: - raise NotImplementedError - - def collect(self, scope: dict, signature: TSignature) -> dict[Callable, None]: - raise NotImplementedError - - def harvest(self, scope: dict, call_value: TCallValue) -> dict[Callable, None]: - raise NotImplementedError - - def access(self, scope: dict, signature: TSignature) -> dict[Callable, None] | None: - raise NotImplementedError diff --git a/avilla/core/flywheel/fn/record.py b/avilla/core/flywheel/fn/record.py deleted file mode 100644 index edf744a9..00000000 --- a/avilla/core/flywheel/fn/record.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Callable - -if TYPE_CHECKING: - from .base import Fn - from .overload import FnOverload - - -@dataclass(eq=True, frozen=True) -class FnRecord: - spec: Fn - scopes: dict[str, dict[Any, Any]] = field(default_factory=dict) - entities: dict[frozenset[tuple[str, "FnOverload", Any]], Callable] = field(default_factory=dict) - - -@dataclass(eq=True, frozen=True) -class FnImplement: - fn: Fn diff --git a/avilla/core/flywheel/globals.py b/avilla/core/flywheel/globals.py deleted file mode 100644 index a1c40a81..00000000 --- a/avilla/core/flywheel/globals.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -import functools -from collections import defaultdict -from contextvars import ContextVar, copy_context -from typing import Any, Callable, Generator, Tuple - -from .context import CollectContext, InstanceContext -from .typing import P, R, TEntity - -GLOBAL_COLLECT_CONTEXT = CollectContext() -GLOBAL_INSTANCE_CONTEXT = InstanceContext() - -COLLECTING_CONTEXT_VAR = ContextVar("CollectingContext", default=GLOBAL_COLLECT_CONTEXT) -LOOKUP_LAYOUT_VAR = ContextVar[Tuple[CollectContext, ...]]("LookupContext", default=(GLOBAL_COLLECT_CONTEXT,)) -INSTANCE_CONTEXT_VAR = ContextVar("InstanceContext", default=GLOBAL_INSTANCE_CONTEXT) - -ITER_BUCKET_VAR: ContextVar[defaultdict[Any, list[int]]] = ContextVar("LAYOUT_ITER_COLLECTIONS") - - -def _standalone_context(func: Callable[P, R]) -> Callable[P, R]: - @functools.wraps(func) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: - return copy_context().run(func, *args, **kwargs) - - return wrapper - - -@_standalone_context -def iter_layout(session_id: Any | None = None) -> Generator[CollectContext, None, None]: - bucket = ITER_BUCKET_VAR.get(None) - if bucket is None: - bucket = defaultdict(lambda: [-1]) - ITER_BUCKET_VAR.set(bucket) - - stack = bucket[session_id] - - contexts = LOOKUP_LAYOUT_VAR.get() - index = stack[-1] - stack.append(index) - - try: - for content in contexts[index + 1 :]: - stack[-1] += 1 - yield content - finally: - stack.pop() - if not stack: - bucket.pop(session_id, None) - - -def global_collect(entity: TEntity) -> TEntity: - return GLOBAL_COLLECT_CONTEXT.collect(entity) - - -def local_collect(entity: TEntity) -> TEntity: - return COLLECTING_CONTEXT_VAR.get().collect(entity) diff --git a/avilla/core/flywheel/instance_of.py b/avilla/core/flywheel/instance_of.py deleted file mode 100644 index e84abf6e..00000000 --- a/avilla/core/flywheel/instance_of.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -from typing import Any, Generic, TypeVar, overload - -from typing_extensions import Self - -from .globals import INSTANCE_CONTEXT_VAR - -T = TypeVar("T") - - -class InstanceOf(Generic[T]): - target: type[T] - - def __init__(self, target: type[T]) -> None: - self.target = target - - @overload - def __get__(self, instance: None, owner: type) -> Self: - ... - - @overload - def __get__(self, instance: Any, owner: type) -> T: - ... - - def __get__(self, instance: Any, owner: type): - if instance is None: - return self - - return INSTANCE_CONTEXT_VAR.get().instances[self.target] diff --git a/avilla/core/flywheel/overloads.py b/avilla/core/flywheel/overloads.py deleted file mode 100644 index 29ce872c..00000000 --- a/avilla/core/flywheel/overloads.py +++ /dev/null @@ -1,91 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any, Callable, Type - -from .fn.overload import FnOverload - - -@dataclass(eq=True, frozen=True) -class SimpleOverloadSignature: - value: Any - - -class SimpleOverload(FnOverload[SimpleOverloadSignature, Any, Any]): - def digest(self, collect_value: Any) -> SimpleOverloadSignature: - return SimpleOverloadSignature(collect_value) - - def collect(self, scope: dict, signature: SimpleOverloadSignature) -> dict[Callable, None]: - if signature.value not in scope: - target = scope[signature.value] = {} - else: - target = scope[signature.value] - - return target - - def harvest(self, scope: dict, call_value: Any) -> dict[Callable, None]: - if call_value in scope: - return scope[call_value] - - return {} - - def access(self, scope: dict, signature: SimpleOverloadSignature) -> dict[Callable, None] | None: - if signature.value in scope: - return scope[signature.value] - - -@dataclass(eq=True, frozen=True) -class TypeOverloadSignature: - type: type[Any] - - -class TypeOverload(FnOverload[TypeOverloadSignature, Type[Any], Any]): - def digest(self, collect_value: type) -> TypeOverloadSignature: - return TypeOverloadSignature(collect_value) - - def collect(self, scope: dict, signature: TypeOverloadSignature) -> dict[Callable, None]: - if signature.type not in scope: - target = scope[signature.type] = {} - else: - target = scope[signature.type] - - return target - - def harvest(self, scope: dict, call_value: Any) -> dict[Callable, None]: - t = type(call_value) - if t in scope: - return scope[t] - - return {} - - def access(self, scope: dict, signature: TypeOverloadSignature) -> dict[Callable, None] | None: - if signature.type in scope: - return scope[signature.type] - - -class _SingletonOverloadSignature: - ... - - -SINGLETON_SIGN = _SingletonOverloadSignature() - - -class SingletonOverload(FnOverload[_SingletonOverloadSignature, None, None]): - SIGNATURE = SINGLETON_SIGN - - def digest(self, collect_value) -> _SingletonOverloadSignature: - return SINGLETON_SIGN - - def collect(self, scope: dict, signature) -> dict[Callable, None]: - s = scope[None] = {} - return s - - def harvest(self, scope: dict, call_value) -> dict[Callable, None]: - return scope[None] - - def access(self, scope: dict, signature) -> dict[Callable, None] | None: - if None in scope: - return scope[None] - - -SINGLETON_OVERLOAD = SingletonOverload("singleton") diff --git a/avilla/core/flywheel/scoped.py b/avilla/core/flywheel/scoped.py deleted file mode 100644 index a0c52bca..00000000 --- a/avilla/core/flywheel/scoped.py +++ /dev/null @@ -1,106 +0,0 @@ -from __future__ import annotations - -import functools -from typing import Any, Callable - -from .fn.implement import OverloadRecorder -from .context import CollectContext -from .globals import COLLECTING_CONTEXT_VAR, GLOBAL_COLLECT_CONTEXT, GLOBAL_INSTANCE_CONTEXT, INSTANCE_CONTEXT_VAR -from .typing import TYPE_CHECKING, AssignKeeperCls, P, R, TEntity - -if TYPE_CHECKING: - from typing_extensions import Concatenate - - from .fn.base import Call, Fn - from .fn.implement import FnImplementEntity - from .fn.record import FnImplement, FnRecord - from .typing import CR, OutP - - -class scoped_collect(CollectContext): - fn_implements: dict[FnImplement, FnRecord] - _tocollect_list: dict[FnImplementEntity, None] - finalize_cbs: list[Callable[[scoped_collect], Any]] - cls: type | None = None - - def __init__(self) -> None: - self.fn_implements = {} - self.finalize_cbs = [] - self._tocollect_list = {} - - @classmethod - def globals(cls): - instance = cls() - instance.fn_implements = GLOBAL_COLLECT_CONTEXT.fn_implements - return instance - - @classmethod - def env(cls): - instance = cls() - instance.fn_implements = COLLECTING_CONTEXT_VAR.get().fn_implements - return instance - - def finalize(self): - for cb in self.finalize_cbs: - cb(self) - - for impl in self._tocollect_list: - impl.collect(self) - - def on_collected(self, func: Callable[[scoped_collect], Any]): - self.finalize_cbs.append(func) - return func - - def remove_collected_callback(self, func: Callable[[scoped_collect], Any]): - self.finalize_cbs.remove(func) - return func - - @property - def target(self): - from .fn.implement import FnImplementEntity - - class LocalEndpoint: - collector = self - - def __init_subclass__(cls, *, static: bool = False) -> None: - self.cls = cls - self.finalize() - - if static: - GLOBAL_INSTANCE_CONTEXT.instances[cls] = cls() - - @staticmethod - def collect(entity: TEntity) -> TEntity: # type: ignore - return entity.collect(self) - - @staticmethod - def ensure_self(func: Callable[Concatenate[Any, P], R]) -> Callable[P, R]: - @functools.wraps(func) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: - assert self.cls is not None - - instance = INSTANCE_CONTEXT_VAR.get().instances[self.cls] - return func(instance, *args, **kwargs) - - return wrapper - - @staticmethod - def impl( - fn: Fn[Callable[Concatenate[OverloadRecorder[CR], OutP], Any], Any], *args: OutP.args, **kwargs: OutP.kwargs - ) -> AssignKeeperCls[Call[..., CR]]: - def inner(impl: Callable[Concatenate[Any, P], R] | FnImplementEntity[Callable[P, R]]): - if not isinstance(impl, FnImplementEntity): - if not hasattr(impl, "__wrapped__"): - impl_call = LocalEndpoint.ensure_self(impl) - else: - impl_call: Callable[P, R] = impl # type: ignore - - impl = FnImplementEntity(impl_call) - - impl.add_target(fn, *args, **kwargs) - self._tocollect_list[impl] = None - return impl - - return inner # type: ignore - - return LocalEndpoint diff --git a/avilla/core/flywheel/typing.py b/avilla/core/flywheel/typing.py deleted file mode 100644 index 4170b2b6..00000000 --- a/avilla/core/flywheel/typing.py +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Callable, Protocol, TypeVar - -from typing_extensions import Concatenate, ParamSpec - -if TYPE_CHECKING: - from .entity import BaseEntity - from .fn.implement import FnImplementEntity - - -R = TypeVar("R", covariant=True) -R1 = TypeVar("R1", covariant=True) -P = ParamSpec("P") -P1 = ParamSpec("P1") -InP = ParamSpec("InP") -OutP = ParamSpec("OutP") -CR = TypeVar("CR", covariant=True, bound=Callable) -CT = TypeVar("CT", bound=Callable) -TEntity = TypeVar("TEntity", bound="BaseEntity") - - -class Collectable(Protocol[InP]): - def collect(self, *args: InP.args, **kwargs: InP.kwargs) -> Any: - ... - - -class Call(Protocol[P, R]): - def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: - ... - - -class AssignKeeper(Protocol[R]): - def __call__( - self: AssignKeeper[Call[..., Callable[P1, R1]]], - implement: Callable[P1, R1] | FnImplementEntity[Callable[P1, R1]], - ) -> FnImplementEntity[Callable[P1, R1]]: - ... - - -class AssignKeeperCls(Protocol[R]): - def __call__( - self: AssignKeeperCls[Call[..., Callable[P1, R1]]], - implement: Callable[Concatenate[Any, P1], R1] | FnImplementEntity[Callable[P1, R1]], - ) -> FnImplementEntity[Callable[P1, R1]]: - ... - - -class ImplementSample(Protocol[CR]): - @property - def implement_sample(self) -> CR: - ... diff --git a/avilla/core/_runtime.py b/avilla/core/globals.py similarity index 68% rename from avilla/core/_runtime.py rename to avilla/core/globals.py index 207160ad..2252d7f9 100644 --- a/avilla/core/_runtime.py +++ b/avilla/core/globals.py @@ -11,29 +11,29 @@ from avilla.core.protocol import BaseProtocol -cx_avilla: Ctx[Avilla] = Ctx("avilla") -cx_protocol: Ctx[BaseProtocol] = Ctx("protocol") -cx_context: Ctx[Context] = Ctx("context") +AVILLA_CONTEXT_VAR: Ctx[Avilla] = Ctx("avilla") +PROTOCOL_CONTEXT_VAR: Ctx[BaseProtocol] = Ctx("protocol") +CONTEXT_CONTEXT_VAR: Ctx[Context] = Ctx("context") def get_current_avilla() -> Avilla: - avilla = cx_avilla.get(None) + avilla = AVILLA_CONTEXT_VAR.get(None) if avilla: return avilla - protocol = cx_protocol.get(None) + protocol = PROTOCOL_CONTEXT_VAR.get(None) if protocol: return protocol.avilla - context = cx_context.get(None) + context = CONTEXT_CONTEXT_VAR.get(None) if context: return context.protocol.avilla raise RuntimeError("no any current avilla") def get_current_protocol(): - protocol = cx_protocol.get(None) + protocol = PROTOCOL_CONTEXT_VAR.get(None) if protocol: return protocol - context = cx_context.get(None) + context = CONTEXT_CONTEXT_VAR.get(None) if context: return context.protocol raise RuntimeError("no any current protocol") @@ -42,7 +42,7 @@ def get_current_protocol(): def require_context(func): @functools.wraps(func) async def wrapper(*args, **kwargs): - if cx_context.get(None): + if CONTEXT_CONTEXT_VAR.get(None): return await func(*args, **kwargs) raise RuntimeError("no any current context") diff --git a/avilla/core/message.py b/avilla/core/message.py index ee9ab4c8..b83b2a1f 100644 --- a/avilla/core/message.py +++ b/avilla/core/message.py @@ -9,7 +9,7 @@ from avilla.core.selector import Selector from avilla.standard.core.message.capability import MessageRevoke -from ._runtime import cx_context +from avilla.core.globals import CONTEXT_CONTEXT_VAR from .metadata import Metadata @@ -30,4 +30,4 @@ def to_selector(self) -> Selector: return self.scene.message(self.id) async def revoke(self): - await cx_context.get()[MessageRevoke.revoke](self.to_selector()) + await CONTEXT_CONTEXT_VAR.get()[MessageRevoke.revoke](self.to_selector()) diff --git a/avilla/core/protocol.py b/avilla/core/protocol.py index 3d0dbdaf..d63a4378 100644 --- a/avilla/core/protocol.py +++ b/avilla/core/protocol.py @@ -5,7 +5,7 @@ from typing_extensions import Self -from avilla.core._runtime import cx_avilla, cx_context, cx_protocol +from avilla.core.globals import AVILLA_CONTEXT_VAR, CONTEXT_CONTEXT_VAR, PROTOCOL_CONTEXT_VAR from avilla.core.event import AvillaEvent if TYPE_CHECKING: @@ -28,8 +28,10 @@ def configure(self, config: ProtocolConfig) -> Self: ... def post_event(self, event: AvillaEvent, context: Context | None = None): - with cx_avilla.use(self.avilla), cx_protocol.use(self), ( - cx_context.use(context) if context is not None else nullcontext() + with ( + AVILLA_CONTEXT_VAR.use(self.avilla), + PROTOCOL_CONTEXT_VAR.use(self), + CONTEXT_CONTEXT_VAR.use(context) if context is not None else nullcontext(), ): self.avilla.event_record(event) return self.avilla.broadcast.postEvent(event) diff --git a/avilla/core/request.py b/avilla/core/request.py index e3083863..98caad7d 100644 --- a/avilla/core/request.py +++ b/avilla/core/request.py @@ -6,7 +6,7 @@ from avilla.standard.core.request.capability import RequestCapability -from ._runtime import cx_context +from avilla.core.globals import CONTEXT_CONTEXT_VAR from .metadata import Metadata from .platform import Land @@ -61,13 +61,13 @@ def to_selector(self) -> Selector: return self.scene.request(request_id) async def accept(self): - return await cx_context.get()[RequestCapability.accept](self.to_selector()) + return await CONTEXT_CONTEXT_VAR.get()[RequestCapability.accept](self.to_selector()) async def reject(self, reason: str | None = None, forever: bool = False): - return await cx_context.get()[RequestCapability.reject](self.to_selector(), reason, forever) + return await CONTEXT_CONTEXT_VAR.get()[RequestCapability.reject](self.to_selector(), reason, forever) async def cancel(self): - return await cx_context.get()[RequestCapability.cancel](self.to_selector()) + return await CONTEXT_CONTEXT_VAR.get()[RequestCapability.cancel](self.to_selector()) async def ignore(self): - return await cx_context.get()[RequestCapability.ignore](self.to_selector()) + return await CONTEXT_CONTEXT_VAR.get()[RequestCapability.ignore](self.to_selector()) diff --git a/avilla/core/ryanvk/overloads.py b/avilla/core/ryanvk/overloads.py index 1375a3bc..ace42174 100644 --- a/avilla/core/ryanvk/overloads.py +++ b/avilla/core/ryanvk/overloads.py @@ -1,9 +1,11 @@ from __future__ import annotations + from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Callable + +from flywheel.fn.overload import FnOverload from typing_extensions import TypeAlias -from avilla.core.flywheel.fn.overload import FnOverload from avilla.core.selector import FollowsPredicater, Selector, _parse_follows @@ -110,4 +112,4 @@ def access(self, scope: dict, signature: TargetOverloadSignature) -> dict[Callab branch = branches[item.literal or item.predicate] processing_level = branch.levels - return branch.bind \ No newline at end of file + return branch.bind diff --git a/avilla/nonebridge/adapter.py b/avilla/nonebridge/adapter.py index 4a46dd83..5d2d9aa5 100644 --- a/avilla/nonebridge/adapter.py +++ b/avilla/nonebridge/adapter.py @@ -4,7 +4,7 @@ from nonebot.adapters import Adapter as BaseAdapter -from avilla.core._runtime import cx_context +from avilla.core.globals import CONTEXT_CONTEXT_VAR from .bot import NoneBridgeBot @@ -30,7 +30,7 @@ def driver(self): async def _call_api(self, bot: NoneBridgeBot, api: str, **data: Any): staff = bot.service.staff - maybe_cx = cx_context.get(None) + maybe_cx = CONTEXT_CONTEXT_VAR.get(None) if maybe_cx is not None: staff = staff.ext(maybe_cx.get_staff_components()) else: diff --git a/pdm.lock b/pdm.lock index 3f4c5fb2..a489f04e 100644 --- a/pdm.lock +++ b/pdm.lock @@ -3,10 +3,9 @@ [metadata] groups = ["default", "dev"] -cross_platform = true -static_urls = false -lock_version = "4.3" -content_hash = "sha256:e8057c8f48890b706f230fe917d7ee6e72b4845768650a17686af5465cddcecf" +strategy = ["cross_platform"] +lock_version = "4.4.1" +content_hash = "sha256:a7e338a3aac7e5c53f26abb4e6e1752535ed5361c4390949ec42d46e2883c988" [[package]] name = "aiohttp" @@ -372,6 +371,19 @@ files = [ {file = "editables-0.5.tar.gz", hash = "sha256:309627d9b5c4adc0e668d8c6fa7bac1ba7c8c5d415c2d27f60f081f8e80d1de2"}, ] +[[package]] +name = "elaina-flywheel" +version = "0.1.1" +requires_python = ">=3.9" +summary = "" +dependencies = [ + "typing-extensions>=4.10.0", +] +files = [ + {file = "elaina_flywheel-0.1.1-py3-none-any.whl", hash = "sha256:133d3d3af05465f37bee6e7e655b19697dea9081b0abbf4450e502ae65cf2f5e"}, + {file = "elaina_flywheel-0.1.1.tar.gz", hash = "sha256:78422e8e917618e3f3429e77448704269d1cd09fd062def10c3a6329a76b9069"}, +] + [[package]] name = "exceptiongroup" version = "1.1.2" @@ -1576,7 +1588,7 @@ dependencies = [ "python-dotenv>=0.13", "pyyaml>=5.1", "uvicorn==0.28.0", - "uvloop!=0.15.0,!=0.15.1,>=0.14.0; sys_platform != \"win32\" and (sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\")", + "uvloop!=0.15.0,!=0.15.1,>=0.14.0; (sys_platform != \"cygwin\" and sys_platform != \"win32\") and platform_python_implementation != \"PyPy\"", "watchfiles>=0.13", "websockets>=10.4", ] diff --git a/pyproject.toml b/pyproject.toml index e1426e20..db4cabf4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ "pygtrie>=2.5.0", "selectolax>=0.3.16", "satori-python>=0.10.0", + "elaina-flywheel>=0.1.1", ] version = "0" requires-python = ">=3.9" From 70df60e3fc584f646d6a5cfcc48ece6beba11593 Mon Sep 17 00:00:00 2001 From: Elaina Date: Mon, 18 Mar 2024 18:23:30 +0800 Subject: [PATCH 09/24] update cores mina info --- .mina/core.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mina/core.toml b/.mina/core.toml index 64c9362f..a0283af2 100644 --- a/.mina/core.toml +++ b/.mina/core.toml @@ -19,7 +19,8 @@ dependencies = [ "graia-amnesia", "loguru", "launart", - "creart" + "creart", + "elaina-flywheel" ] description = "" license = {text = "MIT"} From ddd00a574b6d16078aae0c72380429340c22eabe Mon Sep 17 00:00:00 2001 From: Elaina Date: Mon, 18 Mar 2024 19:52:16 +0800 Subject: [PATCH 10/24] migrate core to flywheel initially --- avilla/core/__init__.py | 19 +- avilla/core/account.py | 3 +- avilla/core/application.py | 30 ++-- avilla/core/builtins/command/__init__.py | 6 +- avilla/core/context/_roles.py | 36 ++-- avilla/core/context/_selector.py | 21 --- avilla/core/event.py | 31 ++-- avilla/core/message.py | 4 +- avilla/core/metadata.py | 30 ++-- avilla/core/platform.py | 3 +- avilla/core/protocol.py | 19 +- avilla/core/request.py | 10 +- avilla/core/ryanvk/__init__.py | 1 + avilla/core/ryanvk/overloads.py | 13 +- avilla/core/selector.py | 3 +- avilla/core/service.py | 3 +- avilla/core/typing.py | 3 +- avilla/standard/core/activity/__init__.py | 2 +- avilla/standard/core/activity/capability.py | 22 ++- avilla/standard/core/message/__init__.py | 7 +- avilla/standard/core/message/capability.py | 66 +++++-- avilla/standard/core/privilege/__init__.py | 12 +- avilla/standard/core/privilege/capability.py | 152 ++++++++++++---- avilla/standard/core/profile/__init__.py | 11 +- avilla/standard/core/profile/capability.py | 176 +++++++++++++++---- avilla/standard/core/relation/__init__.py | 5 +- avilla/standard/core/relation/capability.py | 112 ++++++++---- avilla/standard/core/request/__init__.py | 5 +- avilla/standard/core/request/capability.py | 95 ++++++++-- avilla/standard/qq/event.py | 3 +- ruff.toml | 2 + 31 files changed, 622 insertions(+), 283 deletions(-) diff --git a/avilla/core/__init__.py b/avilla/core/__init__.py index d20dc230..d67413cf 100644 --- a/avilla/core/__init__.py +++ b/avilla/core/__init__.py @@ -75,32 +75,29 @@ AvillaLifecycleEvent as AvillaLifecycleEvent, ) from avilla.standard.core.common import Count as Count -from avilla.standard.core.message import MessageEdit as MessageEdit from avilla.standard.core.message import MessageEdited as MessageEdited from avilla.standard.core.message import MessageReceived as MessageReceived -from avilla.standard.core.message import MessageRevoke as MessageRevoke from avilla.standard.core.message import MessageRevoked as MessageRevoked -from avilla.standard.core.message import MessageSend as MessageSend + from avilla.standard.core.message import MessageSent as MessageSent -from avilla.standard.core.privilege import BanCapability as BanCapability + from avilla.standard.core.privilege import BanInfo as BanInfo -from avilla.standard.core.privilege import MuteAllCapability as MuteAllCapability -from avilla.standard.core.privilege import MuteCapability as MuteCapability + from avilla.standard.core.privilege import MuteInfo as MuteInfo from avilla.standard.core.privilege import Privilege as Privilege -from avilla.standard.core.privilege import PrivilegeCapability as PrivilegeCapability + from avilla.standard.core.profile import Nick as Nick -from avilla.standard.core.profile import NickCapability as NickCapability + from avilla.standard.core.profile import Summary as Summary -from avilla.standard.core.profile import SummaryCapability as SummaryCapability -from avilla.standard.core.relation import SceneCapability as SceneCapability + + from avilla.standard.core.request import Answers as Answers from avilla.standard.core.request import Comment as Comment from avilla.standard.core.request import Questions as Questions from avilla.standard.core.request import Reason as Reason from avilla.standard.core.request import RequestAccepted as RequestAccepted from avilla.standard.core.request import RequestCancelled as RequestCancelled -from avilla.standard.core.request import RequestCapability as RequestCapability + from avilla.standard.core.request import RequestEvent as RequestEvent from avilla.standard.core.request import RequestIgnored as RequestIgnored from avilla.standard.core.request import RequestReceived as RequestReceived diff --git a/avilla/core/account.py b/avilla/core/account.py index 12056ab2..d8e85411 100644 --- a/avilla/core/account.py +++ b/avilla/core/account.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any from statv import Stats, Statv +from flywheel import CollectContext from avilla.core.selector import Selector from avilla.core.builtins.capability import CoreCapability @@ -29,7 +30,7 @@ class AccountInfo: class BaseAccount: route: Selector avilla: Avilla - artifacts: + artifacts: CollectContext = field(default_factory=CollectContext) @property def info(self) -> AccountInfo: diff --git a/avilla/core/application.py b/avilla/core/application.py index a64f6680..dbc6d26a 100644 --- a/avilla/core/application.py +++ b/avilla/core/application.py @@ -2,7 +2,7 @@ import asyncio import signal -from typing import TYPE_CHECKING, Any, Callable, Iterable, TypeVar, overload +from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterable, TypeVar, overload from creart import it from graia.amnesia.builtins.memcache import MemcacheService @@ -10,13 +10,13 @@ from launart import Launart from launart.service import Service from loguru import logger +from flywheel import CollectContext from avilla.core.globals import get_current_avilla from avilla.core.account import AccountInfo, BaseAccount from avilla.core.dispatchers import AvillaBuiltinDispatcher from avilla.core.event import MetadataModified from avilla.core.protocol import BaseProtocol -from avilla.core.ryanvk_old.staff import Staff from avilla.core.selector import Selector from avilla.core.service import AvillaService from avilla.core.utilles import identity @@ -42,7 +42,6 @@ class Avilla: protocols: list[BaseProtocol] accounts: dict[Selector, AccountInfo] service: AvillaService - global_artifacts: dict[Any, Any] def __init__( self, @@ -174,12 +173,10 @@ def event_record(self, event: AvillaEvent | AvillaLifecycleEvent): ) @overload - def add_event_recorder(self, event_type: type[TE]) -> Callable[[Callable[[TE], None]], Callable[[TE], None]]: - ... + def add_event_recorder(self, event_type: type[TE]) -> Callable[[Callable[[TE], None]], Callable[[TE], None]]: ... @overload - def add_event_recorder(self, event_type: type[TE], recorder: Callable[[TE], None]) -> Callable[[TE], None]: - ... + def add_event_recorder(self, event_type: type[TE], recorder: Callable[[TE], None]) -> Callable[[TE], None]: ... def add_event_recorder( self, event_type: type[TE], recorder: Callable[[TE], None] | None = None @@ -193,10 +190,9 @@ def wrapper(func: Callable[[TE], None]): return wrapper self.custom_event_recorder[event_type] = recorder # type: ignore return recorder - - @staticmethod - def _require_builtins(): - with + + @classmethod + def _require_builtins(cls): import avilla.core.builtins.resource_fetch # noqa: F401 @classmethod @@ -207,20 +203,16 @@ def get_account(self, target: Selector) -> AccountInfo: return self.accounts[target] @overload - def get_accounts(self, *, land: str) -> list[AccountInfo]: - ... + def get_accounts(self, *, land: str) -> list[AccountInfo]: ... @overload - def get_accounts(self, *, pattern: str) -> list[AccountInfo]: - ... + def get_accounts(self, *, pattern: str) -> list[AccountInfo]: ... @overload - def get_accounts(self, *, protocol_type: type[BaseProtocol]) -> list[AccountInfo]: - ... + def get_accounts(self, *, protocol_type: type[BaseProtocol]) -> list[AccountInfo]: ... @overload - def get_accounts(self, *, account_type: type[BaseAccount]) -> list[AccountInfo]: - ... + def get_accounts(self, *, account_type: type[BaseAccount]) -> list[AccountInfo]: ... def get_accounts( self, diff --git a/avilla/core/builtins/command/__init__.py b/avilla/core/builtins/command/__init__.py index 4d0dbda5..61ced9eb 100644 --- a/avilla/core/builtins/command/__init__.py +++ b/avilla/core/builtins/command/__init__.py @@ -216,8 +216,7 @@ def on( remove_tome: bool = False, dispatchers: Optional[list[T_Dispatcher]] = None, decorators: Optional[list[Decorator]] = None, - ) -> Callable[[TCallable], TCallable]: - ... + ) -> Callable[[TCallable], TCallable]: ... @overload def on( @@ -230,8 +229,7 @@ def on( *, args: Optional[dict[str, Union[TAValue, Args, Arg]]] = None, meta: Optional[CommandMeta] = None, - ) -> Callable[[TCallable], TCallable]: - ... + ) -> Callable[[TCallable], TCallable]: ... def on( self, diff --git a/avilla/core/context/_roles.py b/avilla/core/context/_roles.py index 11617326..b85814b0 100644 --- a/avilla/core/context/_roles.py +++ b/avilla/core/context/_roles.py @@ -10,10 +10,10 @@ from avilla.core.message import Message from avilla.core.metadata import Metadata from avilla.core.selector import Selector -from avilla.standard.core.activity import ActivityTrigger -from avilla.standard.core.message import MessageSend -from avilla.standard.core.relation import SceneCapability -from avilla.standard.core.request import RequestCapability +from avilla.standard.core.activity import start_activity +from avilla.standard.core.message import send_message +from avilla.standard.core.relation import leave_scene, disband_scene, remove_member +from avilla.standard.core.request import accept_request, reject_request, cancel_request, ignore_request from graia.amnesia.message import Element, MessageChain, Text from ._selector import ContextSelector @@ -28,7 +28,7 @@ class ContextClientSelector(ContextSelector): def trigger_activity(self, activity: str): - return self.context[ActivityTrigger.trigger](self.activity(activity)) + return start_activity(self.activity(activity)) @property def channel(self) -> str: @@ -71,15 +71,15 @@ def user(self) -> str: class ContextSelfSelector(ContextSelector): def trigger_activity(self, activity: str): - return self.context[ActivityTrigger.trigger](self.activity(activity)) + return start_activity(self.activity(activity)) class ContextSceneSelector(ContextSelector): def leave_scene(self): - return self.context[SceneCapability.leave](self) + return leave_scene(self) def disband_scene(self): - return self.context[SceneCapability.disband](self) + return disband_scene(self) def send_message( self, @@ -101,10 +101,10 @@ def send_message( elif isinstance(reply, str): reply = self.message(reply) - return self.context[MessageSend.send](self, message, reply=reply) + return send_message(self, message, reply=reply) def remove_member(self, target: Selector, reason: str | None = None): - return self.context[SceneCapability.remove_member](target, reason) + return remove_member(target, reason) @property def channel(self) -> str: @@ -116,17 +116,17 @@ def guild(self) -> str: class ContextRequestSelector(ContextEndpointSelector): - def accept_request(self): - return self.context[RequestCapability.accept](self) + def accept(self): + return accept_request(self) - def reject_request(self, reason: str | None = None, forever: bool = False): - return self.context[RequestCapability.reject](self, reason, forever) + def reject(self, reason: str | None = None, forever: bool = False): + return reject_request(self, reason, forever) - def cancel_request(self): - return self.context[RequestCapability.cancel](self) + def cancel(self): + return cancel_request(self) - def ignore_request(self): - return self.context[RequestCapability.ignore](self) + def ignore(self): + return ignore_request(self) @dataclass diff --git a/avilla/core/context/_selector.py b/avilla/core/context/_selector.py index 32414d14..e099cab6 100644 --- a/avilla/core/context/_selector.py +++ b/avilla/core/context/_selector.py @@ -7,7 +7,6 @@ from typing_extensions import Concatenate, ParamSpec, Self, Unpack from avilla.core.metadata import Metadata, MetadataRoute -from avilla.core.ryanvk_old import Fn from avilla.core.selector import EMPTY_MAP, Selector from avilla.standard.core.privilege import Privilege from avilla.standard.core.profile import Avatar, Nick, Summary @@ -38,26 +37,6 @@ def __deepcopy__(self, memo): def from_selector(cls, cx: Context, selector: Selector) -> Self: return cls(cx, selector.pattern) - @overload - def __getitem__(self, item: str) -> str: - ... - - @overload - def __getitem__(self, item: Fn[Concatenate[Selector, P], R]) -> Callable[P, R]: - ... - - def __getitem__( - self, - item: str | Fn[Concatenate[Selector, P], R], - ) -> str | Callable[P, R]: - if isinstance(item, str): - return super().__getitem__(item) - - def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: - return self.context.staff.call_fn(item, self, *args, **kwargs) - - return wrapper - def pull( self, metadata: type[_MetadataT] | MetadataRoute[Unpack[tuple[Any, ...]], _MetadataT] ) -> Awaitable[_MetadataT]: diff --git a/avilla/core/event.py b/avilla/core/event.py index 6c59d1c0..f71537d2 100644 --- a/avilla/core/event.py +++ b/avilla/core/event.py @@ -1,5 +1,6 @@ from __future__ import annotations +from contextlib import AsyncExitStack from dataclasses import dataclass, field from datetime import datetime from inspect import isclass @@ -32,8 +33,9 @@ async def beforeExecution(interface: DispatcherInterface[AvillaEvent]): if interface.depth < 1: interface.local_storage["avilla_context"] = interface.event.context interface.local_storage["_context_token"] = CONTEXT_CONTEXT_VAR.set(interface.event.context) - - # TODO: Lookup Context + + stack: AsyncExitStack = interface.local_storage["_depend_lifespan_manager"] + stack.enter_context(interface.event.context.protocol.artifacts.lookup_scope()) @staticmethod async def catch(interface: DispatcherInterface[AvillaEvent]): @@ -58,32 +60,26 @@ async def catch(interface: DispatcherInterface[AvillaEvent]): async def afterExecution(interface: DispatcherInterface[AvillaEvent], exc, tb): if interface.depth < 1: CONTEXT_CONTEXT_VAR.reset(interface.local_storage["_context_token"]) - await interface.event.context.staff.exit_stack.__aexit__(type(exc), exc, tb) @dataclass -class RelationshipEvent(AvillaEvent): - ... +class RelationshipEvent(AvillaEvent): ... @dataclass -class RelationshipCreated(RelationshipEvent): - ... +class RelationshipCreated(RelationshipEvent): ... @dataclass -class DirectSessionCreated(RelationshipCreated): - ... +class DirectSessionCreated(RelationshipCreated): ... @dataclass -class SceneCreated(RelationshipCreated): - ... +class SceneCreated(RelationshipCreated): ... @dataclass -class MemberCreated(RelationshipEvent): - ... +class MemberCreated(RelationshipEvent): ... @dataclass @@ -93,18 +89,15 @@ class RelationshipDestroyed(RelationshipEvent): @dataclass -class DirectSessionDestroyed(RelationshipDestroyed): - ... +class DirectSessionDestroyed(RelationshipDestroyed): ... @dataclass -class SceneDestroyed(RelationshipDestroyed): - ... +class SceneDestroyed(RelationshipDestroyed): ... @dataclass -class MemberDestroyed(RelationshipDestroyed): - ... +class MemberDestroyed(RelationshipDestroyed): ... @dataclass diff --git a/avilla/core/message.py b/avilla/core/message.py index b83b2a1f..60e6b33a 100644 --- a/avilla/core/message.py +++ b/avilla/core/message.py @@ -7,7 +7,7 @@ from avilla.core.platform import Land from avilla.core.selector import Selector -from avilla.standard.core.message.capability import MessageRevoke +from avilla.standard.core.message.capability import revoke_message from avilla.core.globals import CONTEXT_CONTEXT_VAR from .metadata import Metadata @@ -30,4 +30,4 @@ def to_selector(self) -> Selector: return self.scene.message(self.id) async def revoke(self): - await CONTEXT_CONTEXT_VAR.get()[MessageRevoke.revoke](self.to_selector()) + await revoke_message(self.to_selector()) diff --git a/avilla/core/metadata.py b/avilla/core/metadata.py index 704da441..2f0d0bea 100644 --- a/avilla/core/metadata.py +++ b/avilla/core/metadata.py @@ -27,14 +27,12 @@ def __init__(self, getitem: T): class MetadataMeta(type): @overload - def __rshift__(cls: type[_MetadataT1], other: type[_MetadataT2]) -> MetadataRoute[_MetadataT1, _MetadataT2]: - ... + def __rshift__(cls: type[_MetadataT1], other: type[_MetadataT2]) -> MetadataRoute[_MetadataT1, _MetadataT2]: ... @overload def __rshift__( cls: type[_MetadataT1], other: MetadataRoute[Unpack[_TVT1]] - ) -> MetadataRoute[_MetadataT1, Unpack[_TVT1]]: - ... + ) -> MetadataRoute[_MetadataT1, Unpack[_TVT1]]: ... def __rshift__(cls: Any, other: type[Metadata] | MetadataRoute) -> MetadataRoute: # sourcery skip: instance-method-first-arg-name @@ -68,8 +66,7 @@ def __init_subclass__(cls) -> None: if TYPE_CHECKING: @classmethod - def inh(cls: type[_MetadataT1]) -> _MetadataT1: - ... + def inh(cls: type[_MetadataT1]) -> _MetadataT1: ... else: @@ -107,14 +104,12 @@ def __init__(self, cells: tuple[type[Metadata], ...]) -> None: @overload def __rshift__( self: MetadataRoute[Unpack[_TVT1]], other: type[_MetadataT1] - ) -> MetadataRoute[Unpack[_TVT1], _MetadataT1]: - ... + ) -> MetadataRoute[Unpack[_TVT1], _MetadataT1]: ... @overload def __rshift__( self: MetadataRoute[Unpack[_TVT1]], other: MetadataRoute[Unpack[_TVT2]] - ) -> MetadataRoute[Unpack[_TVT1], Unpack[_TVT2]]: - ... + ) -> MetadataRoute[Unpack[_TVT1], Unpack[_TVT2]]: ... def __rshift__(self, other: type[Metadata] | MetadataRoute) -> MetadataRoute: if not isinstance(other, (type, MetadataRoute)): @@ -139,8 +134,7 @@ def clear_params(self) -> None: if TYPE_CHECKING: @property - def inh(self: MetadataRoute[Unpack[tuple[Any, ...]], _MetadataT1]) -> _MetadataT1: - ... + def inh(self: MetadataRoute[Unpack[tuple[Any, ...]], _MetadataT1]) -> _MetadataT1: ... else: @@ -172,9 +166,9 @@ def __getattr__(self, item: str) -> Self: def __call__(self, *args: Any, **kwargs: Any) -> Self: prev = self.__steps[-1] - self.__steps[ - -1 - ] = f"{prev}({', '.join(repr(arg) for arg in args)}, {', '.join(f'{key}={repr(value)}' for key, value in kwargs.items())})" + self.__steps[-1] = ( + f"{prev}({', '.join(repr(arg) for arg in args)}, {', '.join(f'{key}={repr(value)}' for key, value in kwargs.items())})" + ) return self def __getitem__(self, item: Any) -> Self: @@ -203,9 +197,9 @@ def __getattr__(self, item: str) -> Self: def __call__(self, *args: Any, **kwargs: Any) -> Self: prev = self.__steps[-1] - self.__steps[ - -1 - ] = f"{prev}({', '.join(repr(arg) for arg in args)}, {', '.join(f'{key}={repr(value)}' for key, value in kwargs.items())})" + self.__steps[-1] = ( + f"{prev}({', '.join(repr(arg) for arg in args)}, {', '.join(f'{key}={repr(value)}' for key, value in kwargs.items())})" + ) return self def __getitem__(self, item: Any) -> Self: diff --git a/avilla/core/platform.py b/avilla/core/platform.py index 776d3086..4e4972c2 100644 --- a/avilla/core/platform.py +++ b/avilla/core/platform.py @@ -5,8 +5,7 @@ @dataclass -class PlatformDescription: - ... +class PlatformDescription: ... PD = TypeVar("PD", bound=PlatformDescription) diff --git a/avilla/core/protocol.py b/avilla/core/protocol.py index d63a4378..73cf3a3d 100644 --- a/avilla/core/protocol.py +++ b/avilla/core/protocol.py @@ -1,9 +1,11 @@ from __future__ import annotations from contextlib import nullcontext +from functools import cached_property from typing import TYPE_CHECKING, Any, ClassVar from typing_extensions import Self +from flywheel import CollectContext from avilla.core.globals import AVILLA_CONTEXT_VAR, CONTEXT_CONTEXT_VAR, PROTOCOL_CONTEXT_VAR from avilla.core.event import AvillaEvent @@ -13,19 +15,22 @@ from avilla.core.context import Context -class ProtocolConfig: - ... +class ProtocolConfig: ... class BaseProtocol: avilla: Avilla - artifacts: ClassVar[dict[Any, Any]] - def ensure(self, avilla: Avilla) -> Any: - ... + @cached_property + def artifacts(self): + with CollectContext().collect_scope() as collect_context: + ... - def configure(self, config: ProtocolConfig) -> Self: - ... + return collect_context + + def ensure(self, avilla: Avilla) -> Any: ... + + def configure(self, config: ProtocolConfig) -> Self: ... def post_event(self, event: AvillaEvent, context: Context | None = None): with ( diff --git a/avilla/core/request.py b/avilla/core/request.py index 98caad7d..f9ffe5a1 100644 --- a/avilla/core/request.py +++ b/avilla/core/request.py @@ -4,7 +4,7 @@ from datetime import datetime from typing import TYPE_CHECKING -from avilla.standard.core.request.capability import RequestCapability +from avilla.standard.core.request.capability import accept_request, reject_request, cancel_request, ignore_request from avilla.core.globals import CONTEXT_CONTEXT_VAR from .metadata import Metadata @@ -61,13 +61,13 @@ def to_selector(self) -> Selector: return self.scene.request(request_id) async def accept(self): - return await CONTEXT_CONTEXT_VAR.get()[RequestCapability.accept](self.to_selector()) + return await accept_request(self.to_selector()) async def reject(self, reason: str | None = None, forever: bool = False): - return await CONTEXT_CONTEXT_VAR.get()[RequestCapability.reject](self.to_selector(), reason, forever) + return await reject_request(self.to_selector(), reason, forever) async def cancel(self): - return await CONTEXT_CONTEXT_VAR.get()[RequestCapability.cancel](self.to_selector()) + return await cancel_request(self.to_selector()) async def ignore(self): - return await CONTEXT_CONTEXT_VAR.get()[RequestCapability.ignore](self.to_selector()) + return await ignore_request(self.to_selector()) diff --git a/avilla/core/ryanvk/__init__.py b/avilla/core/ryanvk/__init__.py index e69de29b..5709e71b 100644 --- a/avilla/core/ryanvk/__init__.py +++ b/avilla/core/ryanvk/__init__.py @@ -0,0 +1 @@ +from .overloads import TargetOverload as TargetOverload diff --git a/avilla/core/ryanvk/overloads.py b/avilla/core/ryanvk/overloads.py index ace42174..6bcc6aff 100644 --- a/avilla/core/ryanvk/overloads.py +++ b/avilla/core/ryanvk/overloads.py @@ -10,8 +10,8 @@ @dataclass -class LookupBranchMetadata: - ... +class LookupBranchMetadata: ... + @dataclass class LookupBranch: @@ -23,6 +23,7 @@ class LookupBranch: LookupBranches: TypeAlias = "dict[str | FollowsPredicater | None, LookupBranch]" LookupCollection: TypeAlias = "dict[str, LookupBranches]" + @dataclass class TargetOverloadSignature: order: str @@ -55,9 +56,9 @@ def collect(self, scope: dict, signature: TargetOverloadSignature) -> dict[Calla branches[item.literal or item.predicate] = branch processing_level = branch.levels - + return branch.bind - + def harvest(self, scope: dict, value: Selector) -> dict[Callable, None]: processing_scope: LookupCollection = scope branch = None @@ -90,7 +91,7 @@ def harvest(self, scope: dict, value: Selector) -> dict[Callable, None]: return branch.bind else: return {} - + def access(self, scope: dict, signature: TargetOverloadSignature) -> dict[Callable, None] | None: pattern_items = _parse_follows(signature.order) if not pattern_items: @@ -111,5 +112,5 @@ def access(self, scope: dict, signature: TargetOverloadSignature) -> dict[Callab branch = branches[item.literal or item.predicate] processing_level = branch.levels - + return branch.bind diff --git a/avilla/core/selector.py b/avilla/core/selector.py index 14ba3d54..34295969 100644 --- a/avilla/core/selector.py +++ b/avilla/core/selector.py @@ -221,5 +221,4 @@ def expects( @runtime_checkable class Selectable(Protocol): - def to_selector(self) -> Selector: - ... + def to_selector(self) -> Selector: ... diff --git a/avilla/core/service.py b/avilla/core/service.py index 6ea57057..097726b5 100644 --- a/avilla/core/service.py +++ b/avilla/core/service.py @@ -44,8 +44,7 @@ def required(self) -> set[str]: def stages(self): return {"preparing", "blocking", "cleanup"} - def get_interface(self, interface_type): - ... + def get_interface(self, interface_type): ... async def launch(self, manager: Launart): async with self.stage("preparing"): diff --git a/avilla/core/typing.py b/avilla/core/typing.py index 7df84506..e4595cdf 100644 --- a/avilla/core/typing.py +++ b/avilla/core/typing.py @@ -17,5 +17,4 @@ @runtime_checkable class Ensureable(Protocol[_T]): - def ensure(self, interact: _T) -> Any: - ... + def ensure(self, interact: _T) -> Any: ... diff --git a/avilla/standard/core/activity/__init__.py b/avilla/standard/core/activity/__init__.py index 367ff8d0..d0f07b98 100644 --- a/avilla/standard/core/activity/__init__.py +++ b/avilla/standard/core/activity/__init__.py @@ -1,4 +1,4 @@ -from .capability import ActivityTrigger as ActivityTrigger +from .capability import start_activity as start_activity from .event import ActivityAvailable as ActivityAvailable from .event import ActivityEvent as ActivityEvent from .event import ActivityTrigged as ActivityTrigged diff --git a/avilla/standard/core/activity/capability.py b/avilla/standard/core/activity/capability.py index c1a077e9..ef6f30df 100644 --- a/avilla/standard/core/activity/capability.py +++ b/avilla/standard/core/activity/capability.py @@ -1,10 +1,22 @@ from __future__ import annotations +from typing import Protocol -from avilla.core.ryanvk_old import Capability, Fn, TargetOverload +from flywheel import Fn, FnCompose, FnRecord, OverloadRecorder +from avilla.core.ryanvk import TargetOverload from avilla.core.selector import Selector -class ActivityTrigger(Capability): - @Fn.complex({TargetOverload(): ["target"]}) - async def trigger(self, target: Selector): - ... +@Fn.declare +class start_activity(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector): + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target=target) + + class shapecall(Protocol): + async def __call__(self, target: Selector): ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) diff --git a/avilla/standard/core/message/__init__.py b/avilla/standard/core/message/__init__.py index bc6f3cd1..f7c75cf1 100644 --- a/avilla/standard/core/message/__init__.py +++ b/avilla/standard/core/message/__init__.py @@ -1,6 +1,7 @@ -from .capability import MessageEdit as MessageEdit -from .capability import MessageRevoke as MessageRevoke -from .capability import MessageSend as MessageSend +from .capability import edit_message as edit_message +from .capability import pull_message as pull_message +from .capability import revoke_message as revoke_message +from .capability import send_message as send_message from .event import MessageEdited as MessageEdited from .event import MessageReceived as MessageReceived from .event import MessageRevoked as MessageRevoked diff --git a/avilla/standard/core/message/capability.py b/avilla/standard/core/message/capability.py index bfb9375a..da9d18b9 100644 --- a/avilla/standard/core/message/capability.py +++ b/avilla/standard/core/message/capability.py @@ -1,26 +1,66 @@ from __future__ import annotations +from typing import Protocol, TYPE_CHECKING from graia.amnesia.message import MessageChain -from avilla.core.ryanvk_old import Capability, Fn, TargetOverload +from flywheel import Fn, FnCompose, FnRecord, OverloadRecorder +from avilla.core.ryanvk import TargetOverload from avilla.core.selector import Selector +from avilla.core.builtins.capability import CoreCapability + +if TYPE_CHECKING: + from avilla.core.message import Message # MessageFetch => rs.pull(Message, target=...) -class MessageSend(Capability): - @Fn.complex({TargetOverload(): ["target"]}) - async def send(self, target: Selector, message: MessageChain, *, reply: Selector | None = None) -> Selector: - ... +@Fn.declare +class send_message(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector, message: MessageChain, *, reply: Selector | None = None): + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target, message, reply=reply) + + class shapecall(Protocol): + async def __call__(self, target: Selector, message: MessageChain, *, reply: Selector | None = None): ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) + + +@Fn.declare +class revoke_message(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector): + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target) + + class shapecall(Protocol): + async def __call__(self, target: Selector): ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) + + +@Fn.declare +class edit_message(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector, content: MessageChain): + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target, content) + class shapecall(Protocol): + async def __call__(self, target: Selector, content: MessageChain): ... -class MessageRevoke(Capability): - @Fn.complex({TargetOverload(): ["target"]}) - async def revoke(self, target: Selector) -> None: - ... + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) -class MessageEdit(Capability): - @Fn.complex({TargetOverload(): ["target"]}) - async def edit(self, target: Selector, content: MessageChain) -> None: - ... +async def pull_message(target: Selector) -> Message: + return await CoreCapability.pull(target, Message) diff --git a/avilla/standard/core/privilege/__init__.py b/avilla/standard/core/privilege/__init__.py index 5c6306ec..2915aef5 100644 --- a/avilla/standard/core/privilege/__init__.py +++ b/avilla/standard/core/privilege/__init__.py @@ -1,7 +1,11 @@ -from .capability import BanCapability as BanCapability -from .capability import MuteAllCapability as MuteAllCapability -from .capability import MuteCapability as MuteCapability -from .capability import PrivilegeCapability as PrivilegeCapability +from .capability import ban_entity as ban_entity +from .capability import downgrade_privilege as downgrade_privilege +from .capability import mute_all as mute_all +from .capability import mute_entity as mute_entity +from .capability import unban_entity as unban_entity +from .capability import unmute_all as unmute_all +from .capability import unmute_entity as unmute_entity +from .capability import upgrade_privilege as upgrade_privilege from .metadata import BanInfo as BanInfo from .metadata import MuteInfo as MuteInfo from .metadata import Privilege as Privilege diff --git a/avilla/standard/core/privilege/capability.py b/avilla/standard/core/privilege/capability.py index e6bf8172..c7fd13fc 100644 --- a/avilla/standard/core/privilege/capability.py +++ b/avilla/standard/core/privilege/capability.py @@ -1,48 +1,140 @@ from __future__ import annotations +from typing import Protocol from datetime import timedelta -from avilla.core.ryanvk_old import Capability, Fn, TargetOverload +from flywheel import Fn, FnCompose, FnRecord, OverloadRecorder +from avilla.core.ryanvk import TargetOverload from avilla.core.selector import Selector -class PrivilegeCapability(Capability): - @Fn.complex({TargetOverload(): ["target"]}) - async def upgrade(self, target: Selector, dest: str | None = None) -> None: - ... +@Fn.declare +class upgrade_privilege(FnCompose): + target = TargetOverload("target") - @Fn.complex({TargetOverload(): ["target"]}) - async def downgrade(self, target: Selector, dest: str | None = None) -> None: - ... + async def call(self, record: FnRecord, target: Selector, dest: str | None = None) -> None: + entities = self.load(self.target.dig(record, target)) + return await entities.first(target, dest) -class MuteCapability(Capability): - @Fn.complex({TargetOverload(): ["target"]}) - async def mute(self, target: Selector, duration: timedelta) -> None: - ... + class shapecall(Protocol): + async def __call__(self, target: Selector, dest: str | None = None) -> None: ... - @Fn.complex({TargetOverload(): ["target"]}) - async def unmute(self, target: Selector) -> None: - ... + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) -class MuteAllCapability(Capability): - @Fn.complex({TargetOverload(): ["target"]}) - async def mute_all(self, target: Selector) -> None: - ... +@Fn.declare +class downgrade_privilege(FnCompose): + target = TargetOverload("target") - @Fn.complex({TargetOverload(): ["target"]}) - async def unmute_all(self, target: Selector) -> None: - ... + async def call(self, record: FnRecord, target: Selector, dest: str | None = None) -> None: + entities = self.load(self.target.dig(record, target)) - # Fetch => rs.pull(MuteInfo, target=...) + return await entities.first(target, dest) + class shapecall(Protocol): + async def __call__(self, target: Selector, dest: str | None = None) -> None: ... -class BanCapability(Capability): - @Fn.complex({TargetOverload(): ["target"]}) - async def ban(self, target: Selector, *, duration: timedelta | None = None, reason: str | None = None) -> None: - ... + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) - @Fn.complex({TargetOverload(): ["target"]}) - async def unban(self, target: Selector) -> None: - ... + +@Fn.declare +class mute_entity(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector, duration: timedelta) -> None: + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target, duration) + + class shapecall(Protocol): + async def __call__(self, target: Selector, duration: timedelta) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) + + +@Fn.declare +class unmute_entity(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector) -> None: + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target) + + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) + + +@Fn.declare +class mute_all(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector) -> None: + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target) + + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) + + +@Fn.declare +class unmute_all(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector) -> None: + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target) + + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) + + +@Fn.declare +class ban_entity(FnCompose): + target = TargetOverload("target") + + async def call( + self, record: FnRecord, target: Selector, *, duration: timedelta | None = None, reason: str | None = None + ) -> None: + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target, duration, reason) + + class shapecall(Protocol): + async def __call__( + self, target: Selector, duration: timedelta | None = None, reason: str | None = None + ) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) + + +@Fn.declare +class unban_entity(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector) -> None: + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target) + + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) diff --git a/avilla/standard/core/profile/__init__.py b/avilla/standard/core/profile/__init__.py index 9365d17f..c7fcab81 100644 --- a/avilla/standard/core/profile/__init__.py +++ b/avilla/standard/core/profile/__init__.py @@ -1,5 +1,12 @@ -from .capability import NickCapability as NickCapability -from .capability import SummaryCapability as SummaryCapability +from .capability import get_avatar as get_avatar +from .capability import set_badge as set_badge +from .capability import set_nickname as set_nickname +from .capability import set_summary_description as set_summary_description +from .capability import set_summary_name as set_summary_name +from .capability import unset_badge as unset_badge +from .capability import unset_nickname as unset_nickname +from .capability import unset_summary_description as unset_summary_description +from .capability import unset_summary_name as unset_summary_name from .metadata import Avatar as Avatar from .metadata import Nick as Nick from .metadata import Summary as Summary diff --git a/avilla/standard/core/profile/capability.py b/avilla/standard/core/profile/capability.py index ed699c14..dfa62ce2 100644 --- a/avilla/standard/core/profile/capability.py +++ b/avilla/standard/core/profile/capability.py @@ -1,52 +1,158 @@ from __future__ import annotations - +from typing import Protocol from avilla.core.metadata import Route from avilla.core.resource import Resource -from avilla.core.ryanvk_old import Capability, Fn, MetadataOverload, TargetOverload +from flywheel import Fn, FnCompose, FnRecord, OverloadRecorder, SimpleOverload +from avilla.core.ryanvk import TargetOverload from avilla.core.selector import Selector -class SummaryCapability(Capability): - @Fn.complex({TargetOverload(): ["target"], MetadataOverload(): ["route"]}) - async def set_name(self, target: Selector, route: Route, name: str) -> None: - ... +@Fn.declare +class set_summary_name(FnCompose): + target = TargetOverload("target") + route = SimpleOverload("route") + + async def call(self, record: FnRecord, target: Selector, route: Route, name: str) -> None: + entities = self.load(self.target.dig(record, target), self.route.dig(record, route)) + await entities.first(target, name) + + class shapecall(Protocol): + async def __call__(self, target: Selector, name: str) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str, route: Route): + recorder.use(self.target, (target, {})) + recorder.use(self.route, (route, {})) + + +@Fn.declare +class unset_summary_name(FnCompose): + target = TargetOverload("target") + route = SimpleOverload("route") + + async def call(self, record: FnRecord, target: Selector, route: Route) -> None: + entities = self.load(self.target.dig(record, target), self.route.dig(record, route)) + await entities.first(target) + + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str, route: Route): + recorder.use(self.target, (target, {})) + recorder.use(self.route, (route, {})) + + +@Fn.declare +class set_summary_description(FnCompose): + target = TargetOverload("target") + route = SimpleOverload("route") + + async def call(self, record: FnRecord, target: Selector, route: Route, description: str) -> None: + entities = self.load(self.target.dig(record, target), self.route.dig(record, route)) + await entities.first(target, description) + + class shapecall(Protocol): + async def __call__(self, target: Selector, description: str) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str, route: Route): + recorder.use(self.target, (target, {})) + recorder.use(self.route, (route, {})) + + +@Fn.declare +class unset_summary_description(FnCompose): + target = TargetOverload("target") + route = SimpleOverload("route") + + async def call(self, record: FnRecord, target: Selector, route: Route) -> None: + entities = self.load(self.target.dig(record, target), self.route.dig(record, route)) + await entities.first(target) + + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str, route: Route): + recorder.use(self.target, (target, {})) + recorder.use(self.route, (route, {})) + + +@Fn.declare +class set_nickname(FnCompose): + target = TargetOverload("target") + route = SimpleOverload("route") + + async def call(self, record: FnRecord, target: Selector, route: Route, nickname: str) -> None: + entities = self.load(self.target.dig(record, target), self.route.dig(record, route)) + await entities.first(target, nickname) + + class shapecall(Protocol): + async def __call__(self, target: Selector, nickname: str) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str, route: Route): + recorder.use(self.target, (target, {})) + recorder.use(self.route, (route, {})) + + +@Fn.declare +class unset_nickname(FnCompose): + target = TargetOverload("target") + route = SimpleOverload("route") + + async def call(self, record: FnRecord, target: Selector, route: Route) -> None: + entities = self.load(self.target.dig(record, target), self.route.dig(record, route)) + await entities.first(target) + + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str, route: Route): + recorder.use(self.target, (target, {})) + recorder.use(self.route, (route, {})) + + +@Fn.declare +class set_badge(FnCompose): + target = TargetOverload("target") + route = SimpleOverload("route") + + async def call(self, record: FnRecord, target: Selector, route: Route, badge: str) -> None: + entities = self.load(self.target.dig(record, target), self.route.dig(record, route)) + await entities.first(target, badge) + + class shapecall(Protocol): + async def __call__(self, target: Selector, badge: str) -> None: ... - @Fn.complex({TargetOverload(): ["target"], MetadataOverload(): ["route"]}) - async def unset_name(self, target: Selector, route: Route) -> None: - ... + def collect(self, recorder: OverloadRecorder[shapecall], target: str, route: Route): + recorder.use(self.target, (target, {})) + recorder.use(self.route, (route, {})) - @Fn.complex({TargetOverload(): ["target"], MetadataOverload(): ["route"]}) - async def set_description(self, target: Selector, route: Route, description: str) -> None: - ... - @Fn.complex({TargetOverload(): ["target"], MetadataOverload(): ["route"]}) - async def unset_description(self, target: Selector, route: Route) -> None: - ... +@Fn.declare +class unset_badge(FnCompose): + target = TargetOverload("target") + route = SimpleOverload("route") + async def call(self, record: FnRecord, target: Selector, route: Route) -> None: + entities = self.load(self.target.dig(record, target), self.route.dig(record, route)) + await entities.first(target) -class NickCapability(Capability): - @Fn.complex({TargetOverload(): ["target"], MetadataOverload(): ["route"]}) - async def set_name(self, target: Selector, route: Route, name: str) -> None: - ... + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... - @Fn.complex({TargetOverload(): ["target"]}) - async def set_nickname(self, target: Selector, nickname: str) -> None: - ... + def collect(self, recorder: OverloadRecorder[shapecall], target: str, route: Route): + recorder.use(self.target, (target, {})) + recorder.use(self.route, (route, {})) - @Fn.complex({TargetOverload(): ["target"]}) - async def unset_nickname(self, target: Selector) -> None: - ... - @Fn.complex({TargetOverload(): ["target"]}) - async def set_badge(self, target: Selector, badge: str) -> None: - ... +@Fn.declare +class get_avatar(FnCompose): + target = TargetOverload("target") - @Fn.complex({TargetOverload(): ["target"]}) - async def unset_badge(self, target: Selector) -> None: - ... + def call(self, record: FnRecord, target: Selector) -> Resource[bytes]: + entities = self.load(self.target.dig(record, target)) + return entities.first(target) + class shapecall(Protocol): + def __call__(self, target: Selector) -> Resource[bytes]: ... -class AvatarFetch(Capability): - @Fn.complex({TargetOverload(): ["target"]}) - def get_avatar(self, target: Selector) -> Resource[bytes]: - ... + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) diff --git a/avilla/standard/core/relation/__init__.py b/avilla/standard/core/relation/__init__.py index 0c7166d2..1dfa6c6f 100644 --- a/avilla/standard/core/relation/__init__.py +++ b/avilla/standard/core/relation/__init__.py @@ -1 +1,4 @@ -from .capability import SceneCapability as SceneCapability +from .capability import disband_scene as disband_scene +from .capability import leave_scene as leave_scene +from .capability import remove_member as remove_member +from .capability import terminate_relationship as terminate_relationship diff --git a/avilla/standard/core/relation/capability.py b/avilla/standard/core/relation/capability.py index 8baa337d..e1b285f7 100644 --- a/avilla/standard/core/relation/capability.py +++ b/avilla/standard/core/relation/capability.py @@ -1,49 +1,95 @@ from __future__ import annotations -from typing import Literal +from typing import Literal, Protocol -from avilla.core.ryanvk_old import Capability, Fn, TargetOverload +from flywheel import Fn, FnCompose, FnRecord, OverloadRecorder, SimpleOverload +from avilla.core.ryanvk import TargetOverload from avilla.core.selector import Selector +# # @Fn.with_overload({ +# # TargetOverload(): ['target'] +# # }) +# # async def request_join(self, solver: Isolate) -> None: +# # ... +# +# # TODO: invite someone to join the scene +# +# +# class RequestJoinCapability(Capability): +# @Fn +# async def on_question(self, target: Selector, question_id: str, question: str, optional: bool) -> str | None: +# ... +# +# @Fn +# async def on_reason(self, target: Selector) -> str | None: +# ... +# +# @Fn +# async def on_term(self, term: tuple[Literal["string", "url"] | str, str]) -> bool: +# ... +# +# -class SceneCapability(Capability): - @Fn.complex({TargetOverload(): ["target"]}) - async def leave(self, target: Selector) -> None: - ... - @Fn.complex({TargetOverload(): ["target"]}) - async def disband(self, target: Selector) -> None: - ... +@Fn.declare +class leave_scene(FnCompose): + target = TargetOverload("target") - @Fn.complex({TargetOverload(): ["target"]}) - async def remove_member(self, target: Selector, reason: str | None = None) -> None: - ... + async def call(self, record: FnRecord, target: Selector) -> None: + entities = self.load(self.target.dig(record, target)) - # @Fn.with_overload({ - # TargetOverload(): ['target'] - # }) - # async def request_join(self, solver: Isolate) -> None: - # ... + return await entities.first(target) - # TODO: invite someone to join the scene + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) -class RequestJoinCapability(Capability): - @Fn - async def on_question(self, target: Selector, question_id: str, question: str, optional: bool) -> str | None: - ... - @Fn - async def on_reason(self, target: Selector) -> str | None: - ... +@Fn.declare +class disband_scene(FnCompose): + target = TargetOverload("target") - @Fn - async def on_term(self, term: tuple[Literal["string", "url"] | str, str]) -> bool: - ... + async def call(self, record: FnRecord, target: Selector) -> None: + entities = self.load(self.target.dig(record, target)) + return await entities.first(target) -class RelationshipTerminate(Capability): - @Fn.complex({TargetOverload(): ["target"]}) - async def terminate(self, target: Selector) -> None: - # TODO: use wand - ... + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) + + +@Fn.declare +class remove_member(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector, reason: str | None = None) -> None: + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target, reason) + + class shapecall(Protocol): + async def __call__(self, target: Selector, reason: str | None) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) + + +@Fn.declare +class terminate_relationship(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector) -> None: + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target) + + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) diff --git a/avilla/standard/core/request/__init__.py b/avilla/standard/core/request/__init__.py index 9f15e724..83c18a13 100644 --- a/avilla/standard/core/request/__init__.py +++ b/avilla/standard/core/request/__init__.py @@ -1,4 +1,7 @@ -from .capability import RequestCapability as RequestCapability +from .capability import accept_request as accept_request +from .capability import cancel_request as cancel_request +from .capability import ignore_request as ignore_request +from .capability import reject_request as reject_request from .event import RequestAccepted as RequestAccepted from .event import RequestCancelled as RequestCancelled from .event import RequestEvent as RequestEvent diff --git a/avilla/standard/core/request/capability.py b/avilla/standard/core/request/capability.py index 3022338a..e3392662 100644 --- a/avilla/standard/core/request/capability.py +++ b/avilla/standard/core/request/capability.py @@ -1,22 +1,89 @@ from __future__ import annotations -from avilla.core.ryanvk_old import Capability, Fn, TargetOverload +from typing import Literal, Protocol + +from flywheel import Fn, FnCompose, FnRecord, OverloadRecorder, SimpleOverload +from avilla.core.ryanvk import TargetOverload from avilla.core.selector import Selector -class RequestCapability(Capability): - @Fn.complex({TargetOverload(): ["target"]}) - async def accept(self, target: Selector): - ... +# class RequestCapability(Capability): +# @Fn.complex({TargetOverload(): ["target"]}) +# async def accept(self, target: Selector): +# ... +# +# @Fn.complex({TargetOverload(): ["target"]}) +# async def reject(self, target: Selector, reason: str | None = None, forever: bool = False): +# ... +# +# @Fn.complex({TargetOverload(): ["target"]}) +# async def cancel(self, target: Selector): +# ... +# +# @Fn.complex({TargetOverload(): ["target"]}) +# async def ignore(self, target: Selector): +# ... + + +@Fn.declare +class accept_request(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector): + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target) + + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) + + +@Fn.declare +class reject_request(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector, reason: str | None = None, forever: bool = False): + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target, reason, forever) + + class shapecall(Protocol): + async def __call__(self, target: Selector, reason: str | None, forever: bool) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) + + +@Fn.declare +class cancel_request(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector): + entities = self.load(self.target.dig(record, target)) + + return await entities.first(target) + + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... + + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) + + +@Fn.declare +class ignore_request(FnCompose): + target = TargetOverload("target") + + async def call(self, record: FnRecord, target: Selector): + entities = self.load(self.target.dig(record, target)) - @Fn.complex({TargetOverload(): ["target"]}) - async def reject(self, target: Selector, reason: str | None = None, forever: bool = False): - ... + return await entities.first(target) - @Fn.complex({TargetOverload(): ["target"]}) - async def cancel(self, target: Selector): - ... + class shapecall(Protocol): + async def __call__(self, target: Selector) -> None: ... - @Fn.complex({TargetOverload(): ["target"]}) - async def ignore(self, target: Selector): - ... + def collect(self, recorder: OverloadRecorder[shapecall], target: str): + recorder.use(self.target, (target, {})) diff --git a/avilla/standard/qq/event.py b/avilla/standard/qq/event.py index f70b9dc2..16ec8c8f 100644 --- a/avilla/standard/qq/event.py +++ b/avilla/standard/qq/event.py @@ -3,5 +3,4 @@ from avilla.core.event import AvillaEvent -class PocketLuckyKingNoticed(AvillaEvent): - ... +class PocketLuckyKingNoticed(AvillaEvent): ... diff --git a/ruff.toml b/ruff.toml index ec31b1bd..57d40cba 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,3 +1,5 @@ line-length = 120 + +[lint] ignore = ["I"] extend-select = ["I", "F401"] \ No newline at end of file From c7de3b4c073b28c26d5dc956b8cedb1c7948743e Mon Sep 17 00:00:00 2001 From: Elaina Date: Mon, 18 Mar 2024 19:52:42 +0800 Subject: [PATCH 11/24] format via ruff --- avilla/core/application.py | 4 +--- avilla/core/context/_selector.py | 4 ++-- avilla/core/message.py | 1 - avilla/core/protocol.py | 2 +- avilla/core/request.py | 1 - 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/avilla/core/application.py b/avilla/core/application.py index dbc6d26a..b83fabaa 100644 --- a/avilla/core/application.py +++ b/avilla/core/application.py @@ -2,7 +2,7 @@ import asyncio import signal -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterable, TypeVar, overload +from typing import TYPE_CHECKING, Callable, Iterable, TypeVar, overload from creart import it from graia.amnesia.builtins.memcache import MemcacheService @@ -10,7 +10,6 @@ from launart import Launart from launart.service import Service from loguru import logger -from flywheel import CollectContext from avilla.core.globals import get_current_avilla from avilla.core.account import AccountInfo, BaseAccount @@ -29,7 +28,6 @@ from avilla.core.event import AvillaEvent from avilla.standard.core.application import AvillaLifecycleEvent - from .resource import Resource T = TypeVar("T") TE = TypeVar("TE", bound="AvillaEvent") diff --git a/avilla/core/context/_selector.py b/avilla/core/context/_selector.py index e099cab6..92836d31 100644 --- a/avilla/core/context/_selector.py +++ b/avilla/core/context/_selector.py @@ -2,9 +2,9 @@ from collections.abc import Mapping from copy import copy, deepcopy -from typing import TYPE_CHECKING, Any, Awaitable, Callable, TypeVar, overload +from typing import TYPE_CHECKING, Any, Awaitable, TypeVar -from typing_extensions import Concatenate, ParamSpec, Self, Unpack +from typing_extensions import ParamSpec, Self, Unpack from avilla.core.metadata import Metadata, MetadataRoute from avilla.core.selector import EMPTY_MAP, Selector diff --git a/avilla/core/message.py b/avilla/core/message.py index 60e6b33a..57386b4a 100644 --- a/avilla/core/message.py +++ b/avilla/core/message.py @@ -9,7 +9,6 @@ from avilla.core.selector import Selector from avilla.standard.core.message.capability import revoke_message -from avilla.core.globals import CONTEXT_CONTEXT_VAR from .metadata import Metadata diff --git a/avilla/core/protocol.py b/avilla/core/protocol.py index 73cf3a3d..c4b891b8 100644 --- a/avilla/core/protocol.py +++ b/avilla/core/protocol.py @@ -2,7 +2,7 @@ from contextlib import nullcontext from functools import cached_property -from typing import TYPE_CHECKING, Any, ClassVar +from typing import TYPE_CHECKING, Any from typing_extensions import Self from flywheel import CollectContext diff --git a/avilla/core/request.py b/avilla/core/request.py index f9ffe5a1..7d3dd0ff 100644 --- a/avilla/core/request.py +++ b/avilla/core/request.py @@ -6,7 +6,6 @@ from avilla.standard.core.request.capability import accept_request, reject_request, cancel_request, ignore_request -from avilla.core.globals import CONTEXT_CONTEXT_VAR from .metadata import Metadata from .platform import Land From 6bcb254657d7b25f57dd3b2840cefa48683881a4 Mon Sep 17 00:00:00 2001 From: Elaina Date: Mon, 18 Mar 2024 20:19:35 +0800 Subject: [PATCH 12/24] cachedstatic --- avilla/core/protocol.py | 10 ++++----- avilla/core/utilles/__init__.py | 18 ++++++++++++++++ avilla/standard/core/relation/capability.py | 4 ++-- avilla/standard/core/request/capability.py | 23 ++------------------- 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/avilla/core/protocol.py b/avilla/core/protocol.py index c4b891b8..601a7040 100644 --- a/avilla/core/protocol.py +++ b/avilla/core/protocol.py @@ -1,14 +1,14 @@ from __future__ import annotations from contextlib import nullcontext -from functools import cached_property from typing import TYPE_CHECKING, Any -from typing_extensions import Self from flywheel import CollectContext +from typing_extensions import Self -from avilla.core.globals import AVILLA_CONTEXT_VAR, CONTEXT_CONTEXT_VAR, PROTOCOL_CONTEXT_VAR from avilla.core.event import AvillaEvent +from avilla.core.globals import AVILLA_CONTEXT_VAR, CONTEXT_CONTEXT_VAR, PROTOCOL_CONTEXT_VAR +from avilla.core.utilles import cachedstatic if TYPE_CHECKING: from avilla.core.application import Avilla @@ -21,8 +21,8 @@ class ProtocolConfig: ... class BaseProtocol: avilla: Avilla - @cached_property - def artifacts(self): + @cachedstatic + def artifacts(): with CollectContext().collect_scope() as collect_context: ... diff --git a/avilla/core/utilles/__init__.py b/avilla/core/utilles/__init__.py index f6bf5004..d1c7936b 100644 --- a/avilla/core/utilles/__init__.py +++ b/avilla/core/utilles/__init__.py @@ -22,3 +22,21 @@ def __init__(self, fget: Callable[[Any], _R_co] | classmethod[_T, [], _R_co]) -> def __get__(self, __obj: _T, __type: type[_T] | None = None, /) -> _R_co: return self.fget[0].__get__(__obj, __type)() + + +class cachedstatic(Generic[_T, _R_co]): + fget: tuple[staticmethod[[], _R_co]] + res: _R_co | None = None + + def __init__(self, fget: Callable[[], _R_co] | staticmethod[[], _R_co]) -> None: + if not isinstance(fget, staticmethod): + fget = staticmethod(fget) + + self.fget = (fget,) + + def __get__(self, __obj: _T, __type: type[_T] | None = None, /) -> _R_co: + if self.res is not None: + return self.res + + self.res = self.fget[0].__get__(__obj, __type)() + return self.res # type: ignore diff --git a/avilla/standard/core/relation/capability.py b/avilla/standard/core/relation/capability.py index e1b285f7..18cb2055 100644 --- a/avilla/standard/core/relation/capability.py +++ b/avilla/standard/core/relation/capability.py @@ -1,8 +1,8 @@ from __future__ import annotations -from typing import Literal, Protocol +from typing import Protocol -from flywheel import Fn, FnCompose, FnRecord, OverloadRecorder, SimpleOverload +from flywheel import Fn, FnCompose, FnRecord, OverloadRecorder from avilla.core.ryanvk import TargetOverload from avilla.core.selector import Selector diff --git a/avilla/standard/core/request/capability.py b/avilla/standard/core/request/capability.py index e3392662..482bee0c 100644 --- a/avilla/standard/core/request/capability.py +++ b/avilla/standard/core/request/capability.py @@ -1,30 +1,11 @@ from __future__ import annotations -from typing import Literal, Protocol +from typing import Protocol -from flywheel import Fn, FnCompose, FnRecord, OverloadRecorder, SimpleOverload +from flywheel import Fn, FnCompose, FnRecord, OverloadRecorder from avilla.core.ryanvk import TargetOverload from avilla.core.selector import Selector - -# class RequestCapability(Capability): -# @Fn.complex({TargetOverload(): ["target"]}) -# async def accept(self, target: Selector): -# ... -# -# @Fn.complex({TargetOverload(): ["target"]}) -# async def reject(self, target: Selector, reason: str | None = None, forever: bool = False): -# ... -# -# @Fn.complex({TargetOverload(): ["target"]}) -# async def cancel(self, target: Selector): -# ... -# -# @Fn.complex({TargetOverload(): ["target"]}) -# async def ignore(self, target: Selector): -# ... - - @Fn.declare class accept_request(FnCompose): target = TargetOverload("target") From 44d1581bfe46c1099cc95cec00560581df320f20 Mon Sep 17 00:00:00 2001 From: Elaina Date: Mon, 18 Mar 2024 20:53:46 +0800 Subject: [PATCH 13/24] docs: instanceof for connection --- avilla/core/context/__init__.py | 13 ++++++++++ avilla/core/dispatchers.py | 3 +++ avilla/core/event.py | 2 ++ docs/flywheel-migration.md | 45 +++++++++++++++++++++++++++------ 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/avilla/core/context/__init__.py b/avilla/core/context/__init__.py index 0429c837..8967b2f9 100644 --- a/avilla/core/context/__init__.py +++ b/avilla/core/context/__init__.py @@ -5,6 +5,8 @@ from typing_extensions import ParamSpec, Unpack +from flywheel import InstanceContext + from avilla.core.globals import CONTEXT_CONTEXT_VAR from avilla.core.account import BaseAccount from avilla.core.builtins.capability import CoreCapability @@ -65,6 +67,17 @@ def __init__( self.cache = {"meta": metadatas or {}} + @property + def instance_context(self): + ins = InstanceContext() + ins.instances.update({type(i): i for i in [ + self, + self.avilla, + self.protocol, + self.account + ]}) + return ins + @property def protocol(self): return self.account.info.protocol diff --git a/avilla/core/dispatchers.py b/avilla/core/dispatchers.py index 2105cd63..3e15de3a 100644 --- a/avilla/core/dispatchers.py +++ b/avilla/core/dispatchers.py @@ -28,14 +28,17 @@ async def catch(self, interface: DispatcherInterface[AvillaEvent]): if interface.annotation is Avilla: return self.avilla + if interface.annotation in self.avilla._protocol_map: return self.avilla._protocol_map[interface.annotation] + if ( isclass(interface.annotation) and issubclass(interface.annotation, BaseProtocol) and isinstance(PROTOCOL_CONTEXT_VAR.get(None), interface.annotation) ): return PROTOCOL_CONTEXT_VAR.get(None) + if ( isinstance(interface.event, AvillaEvent) and isclass(interface.annotation) diff --git a/avilla/core/event.py b/avilla/core/event.py index f71537d2..77171959 100644 --- a/avilla/core/event.py +++ b/avilla/core/event.py @@ -9,6 +9,7 @@ from graia.broadcast.entities.dispatcher import BaseDispatcher from graia.broadcast.entities.event import Dispatchable from typing_extensions import Unpack +from flywheel import InstanceContext from avilla.core.account import BaseAccount from avilla.core.metadata import Metadata, MetadataRoute @@ -36,6 +37,7 @@ async def beforeExecution(interface: DispatcherInterface[AvillaEvent]): stack: AsyncExitStack = interface.local_storage["_depend_lifespan_manager"] stack.enter_context(interface.event.context.protocol.artifacts.lookup_scope()) + stack.enter_context(interface.event.context.instance_context.scope()) @staticmethod async def catch(interface: DispatcherInterface[AvillaEvent]): diff --git a/docs/flywheel-migration.md b/docs/flywheel-migration.md index ed16692e..38023dae 100644 --- a/docs/flywheel-migration.md +++ b/docs/flywheel-migration.md @@ -8,7 +8,7 @@ Flywheel 致力于用轻巧灵活的设计来迅捷而准确的构筑 Avilla 的 ## Todo List - Overloads - - [ ] `SelectorOverload`,取代现有的 `TargetOverload` 设施。 + - [x] `SelectorOverload`,取代现有的 `TargetOverload` 设施。 - ~~`QueryOverload`~~,参考下文详细叙述。 - ~~`MetadataOverload`~~,已可被 Flywheel 中的 `SimpleOverload` 取代。 - ~~`ResourceOverload`~~,已可被 Flywheel 中的 `TypeOverload` 或 `SimpleOverload` 取代。 @@ -129,16 +129,45 @@ with connection.instance_ctx: ### 具体实现的模块如何导入 -我们推荐声明一个 `_import_performs` 方法,然后在 `Protocol.ensure` 配合 `CollectContext` 使用。 +我们推荐声明用 `cachedstatic` 装饰的 `Protocol.artifacts`,其返回一个 `CollectContext`,结果则被 `cachedstatic` 缓存。 -当然,前提是你里面的全部用了 `@local_collect` 或是 `scoped_context.env()`。 +当然,前提是你里面的各种 collect 全部用了 `@local_collect` 或是 `scoped_context.env()`。 ```python -def _import_performs(): - ... +from avilla.core.utilles import cachedstatic class Protocol: - def ensure(self, avilla): - with CollectContext() as self.perform_context: - _import_performs() + @cachedstatic + def artifacts(): + with CollectContext() as ctx: + import ... + + return ctx +``` + + +### 没有 Collector 后如何置入要调用的依赖 + +之前的 Ryanvk v1.2 允许你直接使用 `avilla.core.ryanvk.collector` 中的各种特别预制的 Collector, +但 Flywheel 则会有点麻烦 —— 他得间接的使用 `InstanceContext` 实现。 + +当然,我们已经为应用生成了 Avilla, Context, 对应 Protocol 和对应 Account 的 mapping,在 AvillaEvent 监听下他们会被自动应用。 +但是我们注意到对于例如 `event_parse` 特别映射了一些例如 `connection` 的情况,这种情况下你需要这样做: + +```python +from flywheel import InstanceOf + +class ConnectionPerformBase: + connection = InstanceOf(ConnectionBase) + +class ConnectionBase: + def parse_event(self): + with InstanceContext.scope() as ctx: + ctx[ConnectionBase] = self # 这里一定要填 ConnectionBase,也就是基类,如果你用了的话。 + + ... # other fn call + + +class GroupEventParsing(m := scoped_context.env().target, ConnectionPerformBase): + ... # self.connection => ConnectionBase ``` From 834aca6c4c911833af00fdc05ca8c9bc18348c99 Mon Sep 17 00:00:00 2001 From: Elaina Date: Mon, 18 Mar 2024 20:54:35 +0800 Subject: [PATCH 14/24] format via ruff --- avilla/console/capability.py | 9 +++----- avilla/core/context/__init__.py | 7 +----- avilla/core/dispatchers.py | 6 +++--- avilla/core/event.py | 1 - avilla/elizabeth/capability.py | 3 +-- avilla/elizabeth/collector/connection.py | 3 +-- avilla/elizabeth/connection/base.py | 15 +++++-------- avilla/elizabeth/connection/util.py | 6 ++---- avilla/elizabeth/exception.py | 1 - avilla/elizabeth/file/capability.py | 24 ++++++--------------- avilla/elizabeth/perform/action/file.py | 3 ++- avilla/elizabeth/perform/event/message.py | 5 +++-- avilla/elizabeth/perform/query/file.py | 4 +++- avilla/nonebridge/capability.py | 3 --- avilla/nonebridge/service.py | 2 +- avilla/onebot/v11/capability.py | 3 +-- avilla/onebot/v11/collector/connection.py | 3 +-- avilla/onebot/v11/net/base.py | 12 ++++------- avilla/qqapi/capability.py | 9 +++----- avilla/qqapi/collector/connection.py | 3 +-- avilla/qqapi/connection/base.py | 15 +++++-------- avilla/qqapi/connection/ws_client.py | 3 +-- avilla/qqapi/role/capability.py | 15 +++++-------- avilla/red/capability.py | 6 ++---- avilla/red/collector/connection.py | 3 +-- avilla/red/exception.py | 1 - avilla/red/net/base.py | 21 ++++++------------ avilla/red/net/ws_client.py | 9 +++++--- avilla/satori/capability.py | 9 +++----- avilla/satori/collector/connection.py | 3 +-- avilla/satori/element.py | 3 +-- avilla/satori/event.py | 6 ++---- avilla/satori/model.py | 16 +++++++++++--- avilla/satori/perform/action/request.py | 8 +++++-- avilla/satori/perform/event/message.py | 6 +----- avilla/satori/perform/event/metadata.py | 4 ++-- avilla/satori/perform/event/relationship.py | 7 +++--- avilla/satori/perform/event/request.py | 1 + avilla/satori/protocol.py | 3 +-- avilla/standard/core/request/capability.py | 1 + avilla/twilight/base.py | 4 ++-- avilla/twilight/twilight.py | 10 ++++----- avilla/twilight/typing.py | 1 - avilla/twilight/util.py | 7 +++--- 44 files changed, 113 insertions(+), 171 deletions(-) diff --git a/avilla/console/capability.py b/avilla/console/capability.py index 65ee115c..75f04125 100644 --- a/avilla/console/capability.py +++ b/avilla/console/capability.py @@ -17,13 +17,10 @@ class ConsoleCapability((m := ApplicationCollector())._): @Fn.complex({TypeOverload(): ["event"]}) - async def event_callback(self, event: Any) -> AvillaEvent: - ... + async def event_callback(self, event: Any) -> AvillaEvent: ... @Fn.complex({TypeOverload(): ["element"]}) - async def deserialize_element(self, element: Any) -> GraiaElement: - ... + async def deserialize_element(self, element: Any) -> GraiaElement: ... @Fn.complex({TypeOverload(): ["element"]}) - async def serialize_element(self, element: Any) -> ConsoleElement: - ... + async def serialize_element(self, element: Any) -> ConsoleElement: ... diff --git a/avilla/core/context/__init__.py b/avilla/core/context/__init__.py index 8967b2f9..fbbd8522 100644 --- a/avilla/core/context/__init__.py +++ b/avilla/core/context/__init__.py @@ -70,12 +70,7 @@ def __init__( @property def instance_context(self): ins = InstanceContext() - ins.instances.update({type(i): i for i in [ - self, - self.avilla, - self.protocol, - self.account - ]}) + ins.instances.update({type(i): i for i in [self, self.avilla, self.protocol, self.account]}) return ins @property diff --git a/avilla/core/dispatchers.py b/avilla/core/dispatchers.py index 3e15de3a..09da9edb 100644 --- a/avilla/core/dispatchers.py +++ b/avilla/core/dispatchers.py @@ -28,17 +28,17 @@ async def catch(self, interface: DispatcherInterface[AvillaEvent]): if interface.annotation is Avilla: return self.avilla - + if interface.annotation in self.avilla._protocol_map: return self.avilla._protocol_map[interface.annotation] - + if ( isclass(interface.annotation) and issubclass(interface.annotation, BaseProtocol) and isinstance(PROTOCOL_CONTEXT_VAR.get(None), interface.annotation) ): return PROTOCOL_CONTEXT_VAR.get(None) - + if ( isinstance(interface.event, AvillaEvent) and isclass(interface.annotation) diff --git a/avilla/core/event.py b/avilla/core/event.py index 77171959..c59dbf25 100644 --- a/avilla/core/event.py +++ b/avilla/core/event.py @@ -9,7 +9,6 @@ from graia.broadcast.entities.dispatcher import BaseDispatcher from graia.broadcast.entities.event import Dispatchable from typing_extensions import Unpack -from flywheel import InstanceContext from avilla.core.account import BaseAccount from avilla.core.metadata import Metadata, MetadataRoute diff --git a/avilla/elizabeth/capability.py b/avilla/elizabeth/capability.py index c1881aed..78f939b3 100644 --- a/avilla/elizabeth/capability.py +++ b/avilla/elizabeth/capability.py @@ -14,8 +14,7 @@ class ElizabethCapability((m := ApplicationCollector())._): @Fn.complex({PredicateOverload(lambda _, raw: raw["type"]): ["raw_event"]}) - async def event_callback(self, raw_event: dict) -> AvillaEvent | None: - ... + async def event_callback(self, raw_event: dict) -> AvillaEvent | None: ... @Fn.complex({PredicateOverload(lambda _, raw: raw["type"]): ["raw_element"]}) async def deserialize_element(self, raw_element: dict) -> Element: # type: ignore diff --git a/avilla/elizabeth/collector/connection.py b/avilla/elizabeth/collector/connection.py index 03f5c90b..1b954dc4 100644 --- a/avilla/elizabeth/collector/connection.py +++ b/avilla/elizabeth/collector/connection.py @@ -32,7 +32,6 @@ class PerformTemplate( ConnectionBasedPerformTemplate, upper, native=True, - ): - ... + ): ... return PerformTemplate diff --git a/avilla/elizabeth/connection/base.py b/avilla/elizabeth/connection/base.py index c8cc2a50..e0ebd056 100644 --- a/avilla/elizabeth/connection/base.py +++ b/avilla/elizabeth/connection/base.py @@ -47,18 +47,14 @@ def get_staff_artifacts(self): def staff(self): return Staff(self.get_staff_artifacts(), self.get_staff_components()) - def message_receive(self) -> AsyncIterator[tuple[Self, dict]]: - ... + def message_receive(self) -> AsyncIterator[tuple[Self, dict]]: ... @property - def alive(self) -> bool: - ... + def alive(self) -> bool: ... - async def wait_for_available(self): - ... + async def wait_for_available(self): ... - async def send(self, payload: dict) -> None: - ... + async def send(self, payload: dict) -> None: ... async def message_handle(self): async for connection, data in self.message_receive(): @@ -140,5 +136,4 @@ async def call( finally: del self.response_waiters[echo] - async def call_http(self, method: CallMethod, action: str, params: dict | None = None) -> dict: - ... + async def call_http(self, method: CallMethod, action: str, params: dict | None = None) -> dict: ... diff --git a/avilla/elizabeth/connection/util.py b/avilla/elizabeth/connection/util.py index da9a636a..e3f76462 100644 --- a/avilla/elizabeth/connection/util.py +++ b/avilla/elizabeth/connection/util.py @@ -34,13 +34,11 @@ @overload -def validate_response(data: Any, raising: Literal[False]) -> Any | Exception: - ... +def validate_response(data: Any, raising: Literal[False]) -> Any | Exception: ... @overload -def validate_response(data: Any, raising: Literal[True] = True) -> Any: - ... +def validate_response(data: Any, raising: Literal[True] = True) -> Any: ... def validate_response(data: dict, raising: bool = True): diff --git a/avilla/elizabeth/exception.py b/avilla/elizabeth/exception.py index ea9bc3b4..6492dc05 100644 --- a/avilla/elizabeth/exception.py +++ b/avilla/elizabeth/exception.py @@ -1,6 +1,5 @@ """Ariadne 的异常定义""" - from avilla.core.exceptions import ( InvalidAuthentication, InvalidOperation, diff --git a/avilla/elizabeth/file/capability.py b/avilla/elizabeth/file/capability.py index 226b60fb..53658832 100644 --- a/avilla/elizabeth/file/capability.py +++ b/avilla/elizabeth/file/capability.py @@ -12,37 +12,27 @@ class FileUpload(Capability): @Fn.complex({TargetOverload(): ["target"]}) async def upload( self, target: Selector, name: str, file: bytes | IO[bytes] | os.PathLike, path: str | None = None - ) -> Selector: - ... + ) -> Selector: ... class FileDirectoryCreate(Capability): @Fn.complex({TargetOverload(): ["target"]}) - async def create( - self, target: Selector, name: str, parent: str | None = None - ) -> Selector: - ... + async def create(self, target: Selector, name: str, parent: str | None = None) -> Selector: ... class FileDelete(Capability): @Fn.complex({TargetOverload(): ["file"]}) async def delete( - self, file: Selector, - ) -> None: - ... + self, + file: Selector, + ) -> None: ... class FileMove(Capability): @Fn.complex({TargetOverload(): ["file"]}) - async def move( - self, file: Selector, to: Selector - ) -> None: - ... + async def move(self, file: Selector, to: Selector) -> None: ... class FileRename(Capability): @Fn.complex({TargetOverload(): ["file"]}) - async def rename( - self, file: Selector, name: str - ) -> None: - ... \ No newline at end of file + async def rename(self, file: Selector, name: str) -> None: ... diff --git a/avilla/elizabeth/perform/action/file.py b/avilla/elizabeth/perform/action/file.py index e8a6e1b4..c93eaa2d 100644 --- a/avilla/elizabeth/perform/action/file.py +++ b/avilla/elizabeth/perform/action/file.py @@ -45,7 +45,8 @@ async def get_file(self, target: Selector, route: ...) -> FileData: ) file = FileData.parse(result) await cache.set( - f"elizabeth/account({self.account.route['account']}).group({target['group']}).file({target['file']})", file, + f"elizabeth/account({self.account.route['account']}).group({target['group']}).file({target['file']})", + file, timedelta(minutes=5), ) return file diff --git a/avilla/elizabeth/perform/event/message.py b/avilla/elizabeth/perform/event/message.py index 6bbb6c1d..3422a2b5 100644 --- a/avilla/elizabeth/perform/event/message.py +++ b/avilla/elizabeth/perform/event/message.py @@ -152,11 +152,12 @@ async def group_recall(self, raw_event: dict): group = Selector().land("qq").group(str(group_data["id"])) author = group.member(str(raw_event["authorId"])) author_data = await self.connection.call( - "fetch", "memberInfo", + "fetch", + "memberInfo", { "target": group_data["id"], "memberId": raw_event["authorId"], - } + }, ) operator_data = raw_event["operator"] operator = group.member(str(operator_data["id"])) diff --git a/avilla/elizabeth/perform/query/file.py b/avilla/elizabeth/perform/query/file.py index 294f6eb5..f4d43da0 100644 --- a/avilla/elizabeth/perform/query/file.py +++ b/avilla/elizabeth/perform/query/file.py @@ -23,7 +23,9 @@ class ElizabethAnnouncementQueryPerform((m := AccountCollector["ElizabethProtoco async def query_group_file(self, predicate: Callable[[str, str], bool] | str, previous: Selector): cache = self.protocol.avilla.launch_manager.get_component(MemcacheService).cache result = await self.account.connection.call( - "fetch", "file_list", {"id": "", "target": int(previous["group"]), "offset": 0, "size": 1, "withDownloadInfo": "True"} + "fetch", + "file_list", + {"id": "", "target": int(previous["group"]), "offset": 0, "size": 1, "withDownloadInfo": "True"}, ) result = cast(list, result) for i in result: diff --git a/avilla/nonebridge/capability.py b/avilla/nonebridge/capability.py index 812ade60..7dbf2d9a 100644 --- a/avilla/nonebridge/capability.py +++ b/avilla/nonebridge/capability.py @@ -2,11 +2,8 @@ from typing import TYPE_CHECKING -from graia.amnesia.message import Element, MessageChain -from avilla.core.event import AvillaEvent from avilla.core.ryanvk_old.collector.application import ApplicationCollector -from graia.ryanvk import Fn, PredicateOverload, TypeOverload if TYPE_CHECKING: pass diff --git a/avilla/nonebridge/service.py b/avilla/nonebridge/service.py index 7cf7bf55..e32b421f 100644 --- a/avilla/nonebridge/service.py +++ b/avilla/nonebridge/service.py @@ -16,7 +16,7 @@ from avilla.core.ryanvk_old.staff import Staff from avilla.core.utilles import identity from avilla.standard.core.account import AccountRegistered, AccountUnregistered -from graia.ryanvk import merge, ref +from graia.ryanvk import ref from graia.ryanvk.aio import queue_task from .adapter import NoneBridgeAdapter diff --git a/avilla/onebot/v11/capability.py b/avilla/onebot/v11/capability.py index 0382458e..4c4fca6c 100644 --- a/avilla/onebot/v11/capability.py +++ b/avilla/onebot/v11/capability.py @@ -22,8 +22,7 @@ def onebot11_event_type(raw: dict) -> str: class OneBot11Capability((m := ApplicationCollector())._): @Fn.complex({PredicateOverload(lambda _, raw: onebot11_event_type(raw)): ["raw_event"]}) - async def event_callback(self, raw_event: dict) -> AvillaEvent | AvillaLifecycleEvent | None: - ... + async def event_callback(self, raw_event: dict) -> AvillaEvent | AvillaLifecycleEvent | None: ... @Fn.complex({PredicateOverload(lambda _, raw: raw["type"]): ["raw_element"]}) async def deserialize_element(self, raw_element: dict) -> Element: # type: ignore diff --git a/avilla/onebot/v11/collector/connection.py b/avilla/onebot/v11/collector/connection.py index a36ff635..70bed67f 100644 --- a/avilla/onebot/v11/collector/connection.py +++ b/avilla/onebot/v11/collector/connection.py @@ -32,7 +32,6 @@ class PerformTemplate( ConnectionBasedPerformTemplate, upper, native=True, - ): - ... + ): ... return PerformTemplate diff --git a/avilla/onebot/v11/net/base.py b/avilla/onebot/v11/net/base.py index 88625264..ce54b09a 100644 --- a/avilla/onebot/v11/net/base.py +++ b/avilla/onebot/v11/net/base.py @@ -39,18 +39,14 @@ def get_staff_artifacts(self): def staff(self): return Staff(self.get_staff_artifacts(), self.get_staff_components()) - def message_receive(self) -> AsyncIterator[tuple[Self, dict]]: - ... + def message_receive(self) -> AsyncIterator[tuple[Self, dict]]: ... @property - def alive(self) -> bool: - ... + def alive(self) -> bool: ... - async def wait_for_available(self): - ... + async def wait_for_available(self): ... - async def send(self, payload: dict) -> None: - ... + async def send(self, payload: dict) -> None: ... async def message_handle(self): async for connection, data in self.message_receive(): diff --git a/avilla/qqapi/capability.py b/avilla/qqapi/capability.py index d09ae6db..1f8bec3f 100644 --- a/avilla/qqapi/capability.py +++ b/avilla/qqapi/capability.py @@ -16,16 +16,13 @@ class QQAPICapability((m := ApplicationCollector())._): @Fn.complex({SimpleOverload(): ["event_type"]}) - async def event_callback(self, event_type: str, raw_event: dict) -> AvillaEvent | AvillaLifecycleEvent | None: - ... + async def event_callback(self, event_type: str, raw_event: dict) -> AvillaEvent | AvillaLifecycleEvent | None: ... @Fn.complex({PredicateOverload(lambda _, raw: raw["type"]): ["raw_element"]}) - async def deserialize_element(self, raw_element: dict) -> Element: - ... + async def deserialize_element(self, raw_element: dict) -> Element: ... @Fn.complex({TypeOverload(): ["element"]}) - async def serialize_element(self, element: Any) -> str | tuple[str, Any]: - ... + async def serialize_element(self, element: Any) -> str | tuple[str, Any]: ... @Fn.complex({TargetOverload(): ["target"]}) async def create_dms(self, target: Selector) -> Selector: diff --git a/avilla/qqapi/collector/connection.py b/avilla/qqapi/collector/connection.py index cf57d79a..b5faecde 100644 --- a/avilla/qqapi/collector/connection.py +++ b/avilla/qqapi/collector/connection.py @@ -32,7 +32,6 @@ class PerformTemplate( ConnectionBasedPerformTemplate, upper, native=True, - ): - ... + ): ... return PerformTemplate diff --git a/avilla/qqapi/connection/base.py b/avilla/qqapi/connection/base.py index 3420ade1..8b718af8 100644 --- a/avilla/qqapi/connection/base.py +++ b/avilla/qqapi/connection/base.py @@ -52,18 +52,14 @@ def get_staff_artifacts(self): def staff(self): return Staff(self.get_staff_artifacts(), self.get_staff_components()) - def message_receive(self, shard: tuple[int, int]) -> AsyncIterator[tuple[Self, dict]]: - ... + def message_receive(self, shard: tuple[int, int]) -> AsyncIterator[tuple[Self, dict]]: ... @property - def alive(self) -> bool: - ... + def alive(self) -> bool: ... - async def wait_for_available(self): - ... + async def wait_for_available(self): ... - async def send(self, payload: dict, shard: tuple[int, int]) -> None: - ... + async def send(self, payload: dict, shard: tuple[int, int]) -> None: ... async def message_handle(self, shard: tuple[int, int]): async for connection, data in self.message_receive(shard): @@ -93,5 +89,4 @@ async def connection_closed(self): self.session_id = None self.close_signal.set() - async def call_http(self, method: CallMethod, action: str, params: dict | None = None) -> dict: - ... + async def call_http(self, method: CallMethod, action: str, params: dict | None = None) -> dict: ... diff --git a/avilla/qqapi/connection/ws_client.py b/avilla/qqapi/connection/ws_client.py index feb50a23..3d3828b8 100644 --- a/avilla/qqapi/connection/ws_client.py +++ b/avilla/qqapi/connection/ws_client.py @@ -210,8 +210,7 @@ def get_staff_components(self): def get_staff_artifacts(self): return [self.protocol.artifacts, self.protocol.avilla.global_artifacts] - def __staff_generic__(self, element_type: dict, event_type: dict): - ... + def __staff_generic__(self, element_type: dict, event_type: dict): ... @property def alive(self): diff --git a/avilla/qqapi/role/capability.py b/avilla/qqapi/role/capability.py index 3e9c8885..6c23f07c 100644 --- a/avilla/qqapi/role/capability.py +++ b/avilla/qqapi/role/capability.py @@ -9,29 +9,24 @@ class RoleCreate(Capability): @Fn.complex({TargetOverload(): ["target"]}) async def create( self, target: Selector, name: str, hoist: bool | None = None, color: int | None = None - ) -> Selector: - ... + ) -> Selector: ... class RoleDelete(Capability): @Fn.complex({TargetOverload(): ["target"]}) - async def delete(self, target: Selector) -> None: - ... + async def delete(self, target: Selector) -> None: ... class RoleEdit(Capability): @Fn.complex({TargetOverload(): ["target"]}) async def edit( self, target: Selector, name: str | None = None, hoist: bool | None = None, color: int | None = None - ) -> None: - ... + ) -> None: ... class RoleMemberCapability(Capability): @Fn.complex({TargetOverload(): ["target"]}) - async def add(self, target: Selector, member: Selector) -> None: - ... + async def add(self, target: Selector, member: Selector) -> None: ... @Fn.complex({TargetOverload(): ["target"]}) - async def remove(self, target: Selector, member: Selector) -> None: - ... + async def remove(self, target: Selector, member: Selector) -> None: ... diff --git a/avilla/red/capability.py b/avilla/red/capability.py index 46905d79..8a38c2f9 100644 --- a/avilla/red/capability.py +++ b/avilla/red/capability.py @@ -15,8 +15,7 @@ class RedCapability((m := ApplicationCollector())._): @Fn.complex({SimpleOverload(): ["event_type"]}) - async def event_callback(self, event_type: str, raw_event: dict) -> AvillaEvent | AvillaLifecycleEvent | None: - ... + async def event_callback(self, event_type: str, raw_event: dict) -> AvillaEvent | AvillaLifecycleEvent | None: ... @Fn.complex({PredicateOverload(lambda _, raw: raw["type"]): ["element"]}) async def deserialize_element(self, element: dict) -> Element: # type: ignore @@ -31,8 +30,7 @@ async def forward_export(self, element: Any) -> dict: # type: ignore ... @Fn.complex({TargetOverload(): ["target"]}) - async def send_forward(self, target: Selector, forward: Forward) -> Selector: - ... + async def send_forward(self, target: Selector, forward: Forward) -> Selector: ... async def deserialize(self, elements: list[dict]): _elements = [] diff --git a/avilla/red/collector/connection.py b/avilla/red/collector/connection.py index 450e7184..a4fa1135 100644 --- a/avilla/red/collector/connection.py +++ b/avilla/red/collector/connection.py @@ -32,7 +32,6 @@ class PerformTemplate( ConnectionBasedPerformTemplate, upper, native=True, - ): - ... + ): ... return PerformTemplate diff --git a/avilla/red/exception.py b/avilla/red/exception.py index b5af6eb3..62af8136 100644 --- a/avilla/red/exception.py +++ b/avilla/red/exception.py @@ -1,6 +1,5 @@ """Ariadne 的异常定义""" - from avilla.core.exceptions import ( InvalidAuthentication, InvalidOperation, diff --git a/avilla/red/net/base.py b/avilla/red/net/base.py index d9f0ab56..5fe57f43 100644 --- a/avilla/red/net/base.py +++ b/avilla/red/net/base.py @@ -37,18 +37,14 @@ def get_staff_artifacts(self): def staff(self): return Staff(self.get_staff_artifacts(), self.get_staff_components()) - def message_receive(self) -> AsyncIterator[tuple[Self, dict]]: - ... + def message_receive(self) -> AsyncIterator[tuple[Self, dict]]: ... @property - def alive(self) -> bool: - ... + def alive(self) -> bool: ... - async def wait_for_available(self): - ... + async def wait_for_available(self): ... - async def send(self, payload: dict) -> None: - ... + async def send(self, payload: dict) -> None: ... async def message_handle(self): async for connection, data in self.message_receive(): @@ -121,8 +117,7 @@ async def call(self, action: str, params: dict | None = None) -> None: @overload async def call_http( self, method: Literal["get", "post", "multipart"], action: str, params: dict | None = None - ) -> dict: - ... + ) -> dict: ... @overload async def call_http( @@ -131,10 +126,8 @@ async def call_http( action: str, params: dict | None = None, raw: Literal[True] = True, - ) -> bytes: - ... + ) -> bytes: ... async def call_http( self, method: Literal["get", "post", "multipart"], action: str, params: dict | None = None, raw: bool = False - ) -> dict | bytes: - ... + ) -> dict | bytes: ... diff --git a/avilla/red/net/ws_client.py b/avilla/red/net/ws_client.py index c4e62deb..5127c819 100644 --- a/avilla/red/net/ws_client.py +++ b/avilla/red/net/ws_client.py @@ -95,8 +95,7 @@ async def wait_for_available(self): def get_staff_components(self): return {"connection": self, "protocol": self.protocol, "avilla": self.protocol.avilla} - def __staff_generic__(self, element_type: dict, event_type: dict): - ... + def __staff_generic__(self, element_type: dict, event_type: dict): ... def get_staff_artifacts(self): return [self.protocol.artifacts, self.protocol.avilla.global_artifacts] @@ -135,7 +134,11 @@ async def connection_daemon(self, manager: Launart, session: aiohttp.ClientSessi self.close_signal.set() self.connection = None for v in list(avilla.accounts.values()): - if v.protocol is self.protocol and self.account and v.route["account"] == self.account.route["account"]: # type: ignore + if ( + v.protocol is self.protocol + and self.account + and v.route["account"] == self.account.route["account"] + ): # type: ignore self.account = None del avilla.accounts[v.route] await avilla.broadcast.postEvent(AccountUnregistered(avilla, v.account)) diff --git a/avilla/satori/capability.py b/avilla/satori/capability.py index 32b83951..cb4897c3 100644 --- a/avilla/satori/capability.py +++ b/avilla/satori/capability.py @@ -15,16 +15,13 @@ class SatoriCapability((m := ApplicationCollector())._): @Fn.complex({PredicateOverload(lambda _, raw: raw.type): ["raw_event"]}) - async def event_callback(self, raw_event: Event) -> AvillaEvent | AvillaLifecycleEvent | list[Any] | None: - ... + async def event_callback(self, raw_event: Event) -> AvillaEvent | AvillaLifecycleEvent | list[Any] | None: ... @Fn.complex({TypeOverload(): ["raw_element"]}) - async def deserialize_element(self, raw_element: Any) -> Element: - ... + async def deserialize_element(self, raw_element: Any) -> Element: ... @Fn.complex({TypeOverload(): ["element"]}) - async def serialize_element(self, element: Any) -> str: - ... + async def serialize_element(self, element: Any) -> str: ... async def deserialize(self, content: str): elements = [] diff --git a/avilla/satori/collector/connection.py b/avilla/satori/collector/connection.py index c9b4eaa1..9a54f34c 100644 --- a/avilla/satori/collector/connection.py +++ b/avilla/satori/collector/connection.py @@ -33,7 +33,6 @@ class PerformTemplate( ConnectionBasedPerformTemplate, upper, native=True, - ): - ... + ): ... return PerformTemplate diff --git a/avilla/satori/element.py b/avilla/satori/element.py index 436cecce..fac0f41b 100644 --- a/avilla/satori/element.py +++ b/avilla/satori/element.py @@ -5,5 +5,4 @@ from avilla.core.elements import Element -class Button(SatoriButton, Element): - ... +class Button(SatoriButton, Element): ... diff --git a/avilla/satori/event.py b/avilla/satori/event.py index d5836be0..bfe1837e 100644 --- a/avilla/satori/event.py +++ b/avilla/satori/event.py @@ -4,10 +4,8 @@ @dataclass -class RoleCreated(RelationshipCreated): - ... +class RoleCreated(RelationshipCreated): ... @dataclass -class RoleDestroyed(RelationshipDestroyed): - ... +class RoleDestroyed(RelationshipDestroyed): ... diff --git a/avilla/satori/model.py b/avilla/satori/model.py index 443b328f..b8463130 100644 --- a/avilla/satori/model.py +++ b/avilla/satori/model.py @@ -1,8 +1,18 @@ from __future__ import annotations -from datetime import datetime -from satori.model import Channel, Event, Login, Member, MessageObject, Role, User, Guild, ButtonInteraction, ArgvInteraction +from satori.model import ( + Channel, + Event, + Login, + Member, + MessageObject, + Role, + User, + Guild, + ButtonInteraction, + ArgvInteraction, +) class MessageEvent(Event): @@ -16,7 +26,6 @@ class DirectEvent(Event): user: User - class GuildEvent(Event): guild: Guild @@ -26,6 +35,7 @@ class GuildMemberEvent(Event): user: User member: Member + class GuildRoleEvent(Event): guild: Guild role: Role diff --git a/avilla/satori/perform/action/request.py b/avilla/satori/perform/action/request.py index 573ea33b..3f8e1edb 100644 --- a/avilla/satori/perform/action/request.py +++ b/avilla/satori/perform/action/request.py @@ -25,7 +25,9 @@ async def accept_member_join_request(self, target: Selector) -> None: ) @m.entity(RequestCapability.reject, target="land.guild.member.request") - async def reject_member_join_request(self, target: Selector, reason: str | None = None, forever: bool = False) -> None: + async def reject_member_join_request( + self, target: Selector, reason: str | None = None, forever: bool = False + ) -> None: request_id = target.pattern["request"] await self.account.client.guild_approve( request_id=request_id, @@ -43,7 +45,9 @@ async def accept_new_friend_request(self, target: Selector) -> None: ) @m.entity(RequestCapability.reject, target="land.user.request") - async def reject_new_friend_request(self, target: Selector, reason: str | None = None, forever: bool = False) -> None: + async def reject_new_friend_request( + self, target: Selector, reason: str | None = None, forever: bool = False + ) -> None: request_id = target.pattern["request"] await self.account.client.friend_approve( request_id=request_id, diff --git a/avilla/satori/perform/event/message.py b/avilla/satori/perform/event/message.py index 46c0c5aa..1769161c 100644 --- a/avilla/satori/perform/event/message.py +++ b/avilla/satori/perform/event/message.py @@ -52,11 +52,7 @@ async def message_create(self, raw_event: Event): reply=reply, ) else: - guild = ( - Selector() - .land(account.route["land"]) - .guild(raw_event.guild.id if raw_event.guild else "True") - ) + guild = Selector().land(account.route["land"]).guild(raw_event.guild.id if raw_event.guild else "True") channel = guild.channel(raw_event.channel.id) member = channel.member( raw_event.member.user.id if raw_event.member and raw_event.member.user else raw_event.user.id diff --git a/avilla/satori/perform/event/metadata.py b/avilla/satori/perform/event/metadata.py index 0e78a71d..f55a8b8d 100644 --- a/avilla/satori/perform/event/metadata.py +++ b/avilla/satori/perform/event/metadata.py @@ -55,7 +55,7 @@ async def guild_updated(self, raw_event: Event): { Avatar.inh().url: ModifyDetail("set", raw_event.guild.avatar, None), }, - ) + ), ] @m.entity(SatoriCapability.event_callback, raw_event="guild-member-updated") @@ -99,7 +99,7 @@ async def guild_member_updated(self, raw_event: Event): { Avatar.inh().url: ModifyDetail("set", raw_event.member.avatar, None), }, - ) + ), ] @m.entity(SatoriCapability.event_callback, raw_event="guild-role-updated") diff --git a/avilla/satori/perform/event/relationship.py b/avilla/satori/perform/event/relationship.py index c5a87092..8afdcab9 100644 --- a/avilla/satori/perform/event/relationship.py +++ b/avilla/satori/perform/event/relationship.py @@ -53,11 +53,10 @@ async def guild_removed(self, raw_event: Event): guild.member(account.route["account"]), ) return SceneDestroyed( - context, + context, active=bool(raw_event.operator) and raw_event.operator.id == account.route["account"], indirect=not bool(raw_event.operator), ) - @m.entity(SatoriCapability.event_callback, raw_event="guild-member-added") async def guild_member_added(self, raw_event: Event): @@ -94,7 +93,7 @@ async def guild_member_removed(self, raw_event: Event): guild.member(account.route["account"]), ) return MemberDestroyed( - context, + context, active=bool(raw_event.operator) and raw_event.operator.id == member["member"], indirect=not bool(raw_event.operator), ) @@ -134,7 +133,7 @@ async def guild_role_deleted(self, raw_event: Event): guild.member(account.route["account"]), ) return RoleDestroyed( - context, + context, active=bool(raw_event.operator) and raw_event.operator.id == account.route["account"], indirect=not bool(raw_event.operator), ) diff --git a/avilla/satori/perform/event/request.py b/avilla/satori/perform/event/request.py index f772de7c..d13d665b 100644 --- a/avilla/satori/perform/event/request.py +++ b/avilla/satori/perform/event/request.py @@ -15,6 +15,7 @@ from avilla.standard.core.request import RequestReceived from satori.model import Event + class SatoriEventRequestPerform((m := ConnectionCollector())._): m.namespace = "avilla.protocol/satori::event" m.identify = "request" diff --git a/avilla/satori/protocol.py b/avilla/satori/protocol.py index 9114fd20..89816c7a 100644 --- a/avilla/satori/protocol.py +++ b/avilla/satori/protocol.py @@ -9,8 +9,7 @@ from .service import SatoriService -class SatoriConfig(ProtocolConfig, WebsocketsInfo): - ... +class SatoriConfig(ProtocolConfig, WebsocketsInfo): ... def _import_performs(): diff --git a/avilla/standard/core/request/capability.py b/avilla/standard/core/request/capability.py index 482bee0c..1ef4509b 100644 --- a/avilla/standard/core/request/capability.py +++ b/avilla/standard/core/request/capability.py @@ -6,6 +6,7 @@ from avilla.core.ryanvk import TargetOverload from avilla.core.selector import Selector + @Fn.declare class accept_request(FnCompose): target = TargetOverload("target") diff --git a/avilla/twilight/base.py b/avilla/twilight/base.py index 45169bc4..319cf3fb 100644 --- a/avilla/twilight/base.py +++ b/avilla/twilight/base.py @@ -1,4 +1,5 @@ """Ariadne 基础的 parser, 包括 DetectPrefix 与 DetectSuffix""" + import abc import difflib import fnmatch @@ -37,8 +38,7 @@ class ChainDecorator(abc.ABC, Decorator, Derive[MessageChain]): pre = True @abc.abstractmethod - async def __call__(self, chain: MessageChain, interface: DispatcherInterface) -> Optional[MessageChain]: - ... + async def __call__(self, chain: MessageChain, interface: DispatcherInterface) -> Optional[MessageChain]: ... async def target(self, interface: DecoratorInterface): return await self( diff --git a/avilla/twilight/twilight.py b/avilla/twilight/twilight.py index dca0ae62..2dad0dd9 100644 --- a/avilla/twilight/twilight.py +++ b/avilla/twilight/twilight.py @@ -1,4 +1,5 @@ """Twilight: 混合式消息链处理器""" + import abc import contextlib import enum @@ -457,16 +458,13 @@ def __init__(self, match_result: Dict[Union[int, str], MatchResult]): self.res = match_result @overload - def __getitem__(self, item: Union[int, str]) -> MatchResult: - ... + def __getitem__(self, item: Union[int, str]) -> MatchResult: ... @overload - def __getitem__(self, item: Type[int]) -> List[MatchResult]: - ... + def __getitem__(self, item: Type[int]) -> List[MatchResult]: ... @overload - def __getitem__(self, item: Type[str]) -> Dict[str, MatchResult]: - ... + def __getitem__(self, item: Type[str]) -> Dict[str, MatchResult]: ... def __getitem__(self, item: Union[int, str, Type[int], Type[str]]): if not isinstance(item, type): diff --git a/avilla/twilight/typing.py b/avilla/twilight/typing.py index e179c784..39281c64 100644 --- a/avilla/twilight/typing.py +++ b/avilla/twilight/typing.py @@ -1,6 +1,5 @@ """Ariadne 的类型标注""" - import contextlib import enum import sys diff --git a/avilla/twilight/util.py b/avilla/twilight/util.py index def3bd2a..216915ba 100644 --- a/avilla/twilight/util.py +++ b/avilla/twilight/util.py @@ -1,4 +1,5 @@ """消息链处理器用到的工具函数, 类""" + import argparse import inspect import re @@ -237,8 +238,7 @@ def get_help( *, prefix_src: Literal["brief", "usage", "description"] = "brief", fmt_cls: Type[argparse.HelpFormatter] = argparse.HelpFormatter, - ) -> str: - ... + ) -> str: ... @overload def get_help( @@ -249,8 +249,7 @@ def get_help( prefix_src: Literal["brief", "usage", "description"] = "brief", fmt_func: Callable[[str], T], fmt_cls: Type[argparse.HelpFormatter] = argparse.HelpFormatter, - ) -> T: - ... + ) -> T: ... def get_help( self, From 81dfaf64d756bcbe0a245b7fec295208d16d3b8f Mon Sep 17 00:00:00 2001 From: Elaina Date: Mon, 18 Mar 2024 23:25:20 +0800 Subject: [PATCH 15/24] feat(wip): migrated console --- avilla/console/backend.py | 2 +- avilla/console/bases.py | 11 ++++ avilla/console/capability.py | 60 +++++++++++++++---- avilla/console/perform/action/activity.py | 11 ++-- avilla/console/perform/action/message.py | 13 ++-- avilla/console/perform/action/profile.py | 17 +++--- avilla/console/perform/context.py | 13 ++-- avilla/console/perform/event/message.py | 12 ++-- avilla/console/perform/message/deserialize.py | 34 +++++------ avilla/console/perform/message/serialize.py | 35 +++++------ avilla/console/protocol.py | 42 ++++++------- avilla/core/ryanvk/bases.py | 8 +++ avilla/standard/core/message/capability.py | 2 +- 13 files changed, 152 insertions(+), 108 deletions(-) create mode 100644 avilla/console/bases.py create mode 100644 avilla/core/ryanvk/bases.py diff --git a/avilla/console/backend.py b/avilla/console/backend.py index 8812928f..339cea8a 100644 --- a/avilla/console/backend.py +++ b/avilla/console/backend.py @@ -73,7 +73,7 @@ def on_console_unmount(self): async def post_event(self, event: Event): with suppress(NotImplementedError): - res = await ConsoleCapability(self.account.staff).event_callback(event) + res = await ConsoleCapability.event_callback(event) self._service.protocol.post_event(res) return diff --git a/avilla/console/bases.py b/avilla/console/bases.py new file mode 100644 index 00000000..cfec1123 --- /dev/null +++ b/avilla/console/bases.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from flywheel import InstanceOf +from .protocol import ConsoleProtocol +from .account import ConsoleAccount + +class InstanceOfProtocol: + protocol = InstanceOf(ConsoleProtocol) + +class InstanceOfAccount(InstanceOfProtocol): + account = InstanceOf(ConsoleAccount) diff --git a/avilla/console/capability.py b/avilla/console/capability.py index 75f04125..50dba7cf 100644 --- a/avilla/console/capability.py +++ b/avilla/console/capability.py @@ -1,26 +1,60 @@ from __future__ import annotations -from typing import Any, TypeVar +from typing import Any, Protocol, TypeVar from graia.amnesia.message import Element as GraiaElement from nonechat.info import Event as ConsoleEvent from nonechat.message import Element as ConsoleElement from avilla.core.event import AvillaEvent -from avilla.core.ryanvk_old.collector.application import ApplicationCollector -from graia.ryanvk import Fn, TypeOverload +from flywheel import TypeOverload, Fn, FnCompose, FnRecord, OverloadRecorder -CE = TypeVar("CE", bound=ConsoleElement) -GE = TypeVar("GE", bound=GraiaElement) -CV = TypeVar("CV", bound=ConsoleEvent) +CE = TypeVar("CE", bound=ConsoleElement, contravariant=True) +GE = TypeVar("GE", bound=GraiaElement, contravariant=True) +CV = TypeVar("CV", bound=ConsoleEvent, contravariant=True) -class ConsoleCapability((m := ApplicationCollector())._): - @Fn.complex({TypeOverload(): ["event"]}) - async def event_callback(self, event: Any) -> AvillaEvent: ... +# NOTE: 全使用 global_collect 或是 scoped_collect.globals() 最好。 - @Fn.complex({TypeOverload(): ["element"]}) - async def deserialize_element(self, element: Any) -> GraiaElement: ... +class ConsoleCapability: + @Fn.declare + class event_callback(FnCompose): + type = TypeOverload("type") - @Fn.complex({TypeOverload(): ["element"]}) - async def serialize_element(self, element: Any) -> ConsoleElement: ... + async def call(self, record: FnRecord, event: ConsoleEvent): + entities = self.load(self.type.dig(record, event)) + return await entities.first(event=event) + + class shapecall(Protocol[CV]): + async def __call__(self, event: CV) -> AvillaEvent: ... + + def collect(self, recorder: OverloadRecorder[shapecall[CV]], event: type[CV]): + recorder.use(self.type, event) + + @Fn.declare + class deserialize_element(FnCompose): + type = TypeOverload("type") + + async def call(self, record: FnRecord, element: ConsoleElement): + entities = self.load(self.type.dig(record, element)) + return await entities.first(element=element) + + class shapecall(Protocol[CE]): + async def __call__(self, element: CE) -> GraiaElement: ... + + def collect(self, recorder: OverloadRecorder[shapecall[CE]], element: type[CE]): + recorder.use(self.type, element) + + @Fn.declare + class serialize_element(FnCompose): + type = TypeOverload("type") + + async def call(self, record: FnRecord, element: GraiaElement): + entities = self.load(self.type.dig(record, element)) + return await entities.first(element=element) + + class shapecall(Protocol[GE]): + async def __call__(self, element: GE) -> ConsoleElement: ... + + def collect(self, recorder: OverloadRecorder[shapecall[GE]], element: type[GE]): + recorder.use(self.type, element) diff --git a/avilla/console/perform/action/activity.py b/avilla/console/perform/action/activity.py index 39fa4e98..4a5736db 100644 --- a/avilla/console/perform/action/activity.py +++ b/avilla/console/perform/action/activity.py @@ -2,18 +2,17 @@ from typing import TYPE_CHECKING -from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector -from avilla.standard.core.activity import ActivityTrigger +from avilla.standard.core.activity import start_activity +from flywheel import scoped_collect +from avilla.console.bases import InstanceOfAccount if TYPE_CHECKING: from avilla.console.account import ConsoleAccount # noqa from avilla.console.protocol import ConsoleProtocol # noqa -class ConsoleActivityActionPerform((m := AccountCollector["ConsoleProtocol", "ConsoleAccount"]())._): - m.namespace = "avilla.protocol/console::action/activity" - - @m.entity(ActivityTrigger.trigger, target="land.user.activity(bell)") +class ConsoleActivityActionPerform(m := scoped_collect.env().target, InstanceOfAccount): + @m.impl(start_activity, target="land.user.activity(bell)") async def bell(self, target: Selector | None = None): await self.account.client.call("bell", {}) diff --git a/avilla/console/perform/action/message.py b/avilla/console/perform/action/message.py index a3dda3b7..8acc7c97 100644 --- a/avilla/console/perform/action/message.py +++ b/avilla/console/perform/action/message.py @@ -12,19 +12,18 @@ from avilla.console.capability import ConsoleCapability from avilla.core.context import Context from avilla.core.message import Message -from avilla.core.ryanvk_old.collector.account import AccountCollector +from avilla.console.bases import InstanceOfAccount from avilla.core.selector import Selector -from avilla.standard.core.message import MessageSend, MessageSent +from avilla.standard.core.message import MessageSent, send_message +from flywheel import scoped_collect if TYPE_CHECKING: from avilla.console.account import ConsoleAccount # noqa from avilla.console.protocol import ConsoleProtocol # noqa -class ConsoleMessageActionPerform((m := AccountCollector["ConsoleProtocol", "ConsoleAccount"]())._): - m.namespace = "avilla.protocol/console::action/message" - - @m.entity(MessageSend.send, target="land.user") +class ConsoleMessageActionPerform(m := scoped_collect.env().target, InstanceOfAccount): + @m.impl(send_message, target="land.user") async def send_console_message( self, target: Selector, @@ -35,7 +34,7 @@ async def send_console_message( if TYPE_CHECKING: assert isinstance(self.protocol, ConsoleProtocol) serialized_msg = ConsoleMessage( - [await ConsoleCapability(self.account.staff).serialize_element(i) for i in message] + [await ConsoleCapability.serialize_element(i) for i in message] ) await self.account.client.call( diff --git a/avilla/console/perform/action/profile.py b/avilla/console/perform/action/profile.py index d1f4e4e7..52980aa0 100644 --- a/avilla/console/perform/action/profile.py +++ b/avilla/console/perform/action/profile.py @@ -2,24 +2,25 @@ from typing import TYPE_CHECKING -from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.profile import Nick, Summary +from avilla.console.bases import InstanceOfAccount +from flywheel import scoped_collect + +from avilla.core.builtins.capability import CoreCapability if TYPE_CHECKING: from avilla.console.account import ConsoleAccount # noqa from avilla.console.protocol import ConsoleProtocol # noqa -class ConsoleProfileActionPerform((m := AccountCollector["ConsoleProtocol", "ConsoleAccount"]())._): - m.namespace = "avilla.protocol/console::action/profile" - - @m.pull("lang.user", Nick) - async def get_console_nick(self, target: Selector, route: type[Nick]) -> Nick: +class ConsoleProfileActionPerform(m := scoped_collect.env().target, InstanceOfAccount): + @m.impl(CoreCapability.pull, "lang.user", Nick) + async def get_console_nick(self, target: Selector) -> Nick: console = self.account.client.storage.current_user return Nick(console.nickname, console.nickname, "") - @m.pull("lang.user", Summary) - async def get_summary(self, target: Selector, route: type[Summary]) -> Summary: + @m.impl(CoreCapability.pull, "lang.user", Summary) + async def get_summary(self, target: Selector) -> Summary: console = self.account.client.storage.current_user return Summary(console.nickname, console.nickname) diff --git a/avilla/console/perform/context.py b/avilla/console/perform/context.py index 58cb7bbd..be1d5120 100644 --- a/avilla/console/perform/context.py +++ b/avilla/console/perform/context.py @@ -4,18 +4,17 @@ from avilla.core.builtins.capability import CoreCapability from avilla.core.context import Context -from avilla.core.ryanvk_old.collector.account import AccountCollector +from ..bases import InstanceOfAccount from avilla.core.selector import Selector +from flywheel import scoped_collect if TYPE_CHECKING: from avilla.console.account import ConsoleAccount # noqa from avilla.console.protocol import ConsoleProtocol # noqa -class ConsoleContextPerform((m := AccountCollector["ConsoleProtocol", "ConsoleAccount"]())._): - m.namespace = "avilla.protocol/console::action/get_context" - - @CoreCapability.get_context.collect(m, target="land.user") +class ConsoleContextPerform(m := scoped_collect.env().target, InstanceOfAccount): + @m.impl(CoreCapability.get_context, target="land.user") def get_context_from_channel(self, target: Selector, *, via: Selector | None = None): return Context( account=self.account, @@ -25,10 +24,10 @@ def get_context_from_channel(self, target: Selector, *, via: Selector | None = N selft=self.account.route, ) - @m.entity(CoreCapability.channel, target="land.user") + @m.impl(CoreCapability.channel, target="land.user") def channel_from_channel(self, target: Selector): return target["user"] - @m.entity(CoreCapability.user, target="land.user") + @m.impl(CoreCapability.user, target="land.user") def user_from_friend(self, target: Selector): return target["user"] diff --git a/avilla/console/perform/event/message.py b/avilla/console/perform/event/message.py index b2658761..0136a2ca 100644 --- a/avilla/console/perform/event/message.py +++ b/avilla/console/perform/event/message.py @@ -9,23 +9,23 @@ from avilla.console.capability import ConsoleCapability from avilla.core.context import Context from avilla.core.message import Message -from avilla.core.ryanvk_old.collector.account import AccountCollector + from avilla.core.selector import Selector from avilla.standard.core.message import MessageReceived +from flywheel import scoped_collect +from avilla.console.bases import InstanceOfAccount if TYPE_CHECKING: from avilla.console.account import ConsoleAccount # noqa from avilla.console.protocol import ConsoleProtocol # noqa -class ConsoleEventMessagePerform((m := AccountCollector["ConsoleProtocol", "ConsoleAccount"]())._): - m.namespace = "avilla.protocol/console::event/message" - - @m.entity(ConsoleCapability.event_callback, event=MessageEvent) +class ConsoleEventMessagePerform(m := scoped_collect.globals().target, InstanceOfAccount): + @m.impl(ConsoleCapability.event_callback, event=MessageEvent) async def console_message(self, event: MessageEvent): console = Selector().land(self.account.route["land"]).user(str(event.user.id)) message = MessageChain( - [await ConsoleCapability(self.account.staff).deserialize_element(i) for i in event.message.content] + [await ConsoleCapability.deserialize_element(i) for i in event.message.content] ) context = Context( account=self.account, diff --git a/avilla/console/perform/message/deserialize.py b/avilla/console/perform/message/deserialize.py index 829d01fe..8b33735b 100644 --- a/avilla/console/perform/message/deserialize.py +++ b/avilla/console/perform/message/deserialize.py @@ -10,27 +10,27 @@ from avilla.console.capability import ConsoleCapability from avilla.console.element import Markdown, Markup from avilla.core.elements import Face, Text -from avilla.core.ryanvk_old.collector.application import ApplicationCollector +from flywheel import global_collect +@global_collect +@ConsoleCapability.deserialize_element.impl(element=CslText) +async def text(element: CslText) -> Text: + return Text(element.text) -class ConsoleMessageDeserializePerform((m := ApplicationCollector())._): - m.namespace = "avilla.protocol/console::message" - m.identify = "deserialize" - # LINK: https://github.com/microsoft/pyright/issues/5409 +@global_collect +@ConsoleCapability.deserialize_element.impl(element=CslEmoji) +async def emoji(element: CslEmoji) -> Face: + return Face(element.name) - @m.entity(ConsoleCapability.deserialize_element, element=CslText) - async def text(self, element: CslText) -> Text: - return Text(element.text) - @m.entity(ConsoleCapability.deserialize_element, element=CslEmoji) - async def emoji(self, element: CslEmoji) -> Face: - return Face(element.name) +@global_collect +@ConsoleCapability.deserialize_element.impl(element=CslMarkup) +async def markup(element: CslMarkup) -> Markup: + return Markup(**asdict(element)) - @m.entity(ConsoleCapability.deserialize_element, element=CslMarkup) - async def markup(self, element: CslMarkup) -> Markup: - return Markup(**asdict(element)) - @m.entity(ConsoleCapability.deserialize_element, element=CslMarkdown) - async def markdown(self, element: CslMarkdown) -> Markdown: - return Markdown(**asdict(element)) +@global_collect +@ConsoleCapability.deserialize_element.impl(element=CslMarkdown) +async def markdown(element: CslMarkdown) -> Markdown: + return Markdown(**asdict(element)) diff --git a/avilla/console/perform/message/serialize.py b/avilla/console/perform/message/serialize.py index a61dbdd9..54bc3ead 100644 --- a/avilla/console/perform/message/serialize.py +++ b/avilla/console/perform/message/serialize.py @@ -11,31 +11,32 @@ from avilla.console.capability import ConsoleCapability from avilla.console.element import Markdown, Markup from avilla.core.elements import Face, Text -from avilla.core.ryanvk_old.collector.account import AccountCollector +from flywheel import global_collect if TYPE_CHECKING: from ...account import ConsoleAccount # noqa from ...protocol import ConsoleProtocol # noqa -class ConsoleMessageSerializePerform((m := AccountCollector["ConsoleProtocol", "ConsoleAccount"]())._): - m.namespace = "avilla.protocol/console::message" - m.identify = "serialize" +@global_collect +@ConsoleCapability.serialize_element.impl(element=Text) +async def text(element: Text): + return CslText(element.text) - # LINK: https://github.com/microsoft/pyright/issues/5409 - @m.entity(ConsoleCapability.serialize_element, element=Text) - async def text(self, element: Text): - return CslText(element.text) +@global_collect +@ConsoleCapability.serialize_element.impl(element=Face) +async def emoji(element: Face): + return CslEmoji(element.id) - @m.entity(ConsoleCapability.serialize_element, element=Face) - async def emoji(self, element: Face): - return CslEmoji(element.id) - @m.entity(ConsoleCapability.serialize_element, element=Markup) - async def markup(self, element: Markup): - return CslMarkup(**asdict(element)) +@global_collect +@ConsoleCapability.serialize_element.impl(element=Markup) +async def markup(element: Markup): + return CslMarkup(**asdict(element)) - @m.entity(ConsoleCapability.serialize_element, element=Markdown) - async def markdown(self, element: Markdown): - return CslMarkdown(**asdict(element)) + +@global_collect +@ConsoleCapability.serialize_element.impl(element=Markdown) +async def markdown(element: Markdown): + return CslMarkdown(**asdict(element)) diff --git a/avilla/console/protocol.py b/avilla/console/protocol.py index 1a4e7eb0..92439fa0 100644 --- a/avilla/console/protocol.py +++ b/avilla/console/protocol.py @@ -2,38 +2,30 @@ from avilla.core.application import Avilla from avilla.core.protocol import BaseProtocol -from graia.ryanvk import merge, ref +from avilla.core.utilles import cachedstatic +from flywheel import CollectContext from .service import ConsoleService -def import_perform(): - # isort: off - - import avilla.console.perform.action.activity # noqa - import avilla.console.perform.action.message - import avilla.console.perform.action.profile - import avilla.console.perform.context - import avilla.console.perform.event.message - import avilla.console.perform.message.deserialize - import avilla.console.perform.message.serialize # noqa - - class ConsoleProtocol(BaseProtocol): service: ConsoleService name: str - import_perform() - artifacts = { - **merge( - ref("avilla.protocol/console::action/activity"), - ref("avilla.protocol/console::action/message"), - ref("avilla.protocol/console::action/profile"), - ref("avilla.protocol/console::action/get_context"), - ref("avilla.protocol/console::message", "deserialize"), - ref("avilla.protocol/console::message", "serialize"), - ref("avilla.protocol/console::event/message"), - ), - } + + @cachedstatic + def artifacts(): + with CollectContext().collect_scope() as collect_context: + # isort: off + + import avilla.console.perform.action.activity # noqa + import avilla.console.perform.action.message + import avilla.console.perform.action.profile + import avilla.console.perform.context + import avilla.console.perform.event.message + import avilla.console.perform.message.deserialize + import avilla.console.perform.message.serialize # noqa + + return collect_context def __init__(self, name: str = "robot"): self.name = name diff --git a/avilla/core/ryanvk/bases.py b/avilla/core/ryanvk/bases.py new file mode 100644 index 00000000..3610a250 --- /dev/null +++ b/avilla/core/ryanvk/bases.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +from flywheel import InstanceOf +from avilla.core.application import Avilla + + +class InstanceOfAvilla: + avilla = InstanceOf(Avilla) diff --git a/avilla/standard/core/message/capability.py b/avilla/standard/core/message/capability.py index da9d18b9..f42f1464 100644 --- a/avilla/standard/core/message/capability.py +++ b/avilla/standard/core/message/capability.py @@ -24,7 +24,7 @@ async def call(self, record: FnRecord, target: Selector, message: MessageChain, return await entities.first(target, message, reply=reply) class shapecall(Protocol): - async def __call__(self, target: Selector, message: MessageChain, *, reply: Selector | None = None): ... + async def __call__(self, target: Selector, message: MessageChain, *, reply: Selector | None = None) -> Selector: ... def collect(self, recorder: OverloadRecorder[shapecall], target: str): recorder.use(self.target, (target, {})) From 97f12dd742d955c88f378147e32d587bafcbeb2b Mon Sep 17 00:00:00 2001 From: Elaina Date: Tue, 19 Mar 2024 09:06:14 +0800 Subject: [PATCH 16/24] fix console --- avilla/console/backend.py | 14 ++++++++++---- avilla/console/capability.py | 4 +++- avilla/console/perform/action/activity.py | 2 +- avilla/console/perform/action/message.py | 4 +--- avilla/console/perform/action/profile.py | 2 +- avilla/console/perform/context.py | 2 +- avilla/console/perform/event/message.py | 2 +- avilla/console/protocol.py | 2 +- avilla/core/builtins/capability.py | 5 +++-- avilla/core/builtins/resource_fetch.py | 2 +- avilla/core/event.py | 2 +- avilla/core/ryanvk/overloads.py | 3 +++ pdm.lock | 6 +++--- test-console.py | 2 ++ 14 files changed, 32 insertions(+), 20 deletions(-) diff --git a/avilla/console/backend.py b/avilla/console/backend.py index 339cea8a..3d1f4ad5 100644 --- a/avilla/console/backend.py +++ b/avilla/console/backend.py @@ -4,6 +4,8 @@ from contextlib import suppress from typing import TYPE_CHECKING +from flywheel import InstanceContext +from flywheel.globals import INSTANCE_CONTEXT_VAR from loguru import logger from nonechat import Backend, Frontend from nonechat.info import Event @@ -72,9 +74,13 @@ def on_console_unmount(self): logger.warning("Press Ctrl-C for Application exit") async def post_event(self, event: Event): - with suppress(NotImplementedError): - res = await ConsoleCapability.event_callback(event) - self._service.protocol.post_event(res) - return + with InstanceContext().scope() as context: + context.instances[type(self.account)] = self.account + context.instances[type(self._service.protocol)] = self._service.protocol + + with suppress(NotImplementedError): + res = await ConsoleCapability.event_callback(event) + self._service.protocol.post_event(res) + return logger.warning(f"received unsupported event {event.type}: {event}") diff --git a/avilla/console/capability.py b/avilla/console/capability.py index 50dba7cf..e2407a6c 100644 --- a/avilla/console/capability.py +++ b/avilla/console/capability.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Protocol, TypeVar +from typing import Protocol, TypeVar from graia.amnesia.message import Element as GraiaElement from nonechat.info import Event as ConsoleEvent @@ -22,6 +22,8 @@ class event_callback(FnCompose): type = TypeOverload("type") async def call(self, record: FnRecord, event: ConsoleEvent): + from loguru import logger + logger.info(event) entities = self.load(self.type.dig(record, event)) return await entities.first(event=event) diff --git a/avilla/console/perform/action/activity.py b/avilla/console/perform/action/activity.py index 4a5736db..d3c80be2 100644 --- a/avilla/console/perform/action/activity.py +++ b/avilla/console/perform/action/activity.py @@ -12,7 +12,7 @@ from avilla.console.protocol import ConsoleProtocol # noqa -class ConsoleActivityActionPerform(m := scoped_collect.env().target, InstanceOfAccount): +class ConsoleActivityActionPerform(m := scoped_collect.env().target, InstanceOfAccount, static=True): @m.impl(start_activity, target="land.user.activity(bell)") async def bell(self, target: Selector | None = None): await self.account.client.call("bell", {}) diff --git a/avilla/console/perform/action/message.py b/avilla/console/perform/action/message.py index 8acc7c97..d4cbb63f 100644 --- a/avilla/console/perform/action/message.py +++ b/avilla/console/perform/action/message.py @@ -22,7 +22,7 @@ from avilla.console.protocol import ConsoleProtocol # noqa -class ConsoleMessageActionPerform(m := scoped_collect.env().target, InstanceOfAccount): +class ConsoleMessageActionPerform(m := scoped_collect.env().target, InstanceOfAccount, static=True): @m.impl(send_message, target="land.user") async def send_console_message( self, @@ -31,8 +31,6 @@ async def send_console_message( *, reply: Selector | None = None, ) -> Selector: - if TYPE_CHECKING: - assert isinstance(self.protocol, ConsoleProtocol) serialized_msg = ConsoleMessage( [await ConsoleCapability.serialize_element(i) for i in message] ) diff --git a/avilla/console/perform/action/profile.py b/avilla/console/perform/action/profile.py index 52980aa0..0a6ae8b2 100644 --- a/avilla/console/perform/action/profile.py +++ b/avilla/console/perform/action/profile.py @@ -14,7 +14,7 @@ from avilla.console.protocol import ConsoleProtocol # noqa -class ConsoleProfileActionPerform(m := scoped_collect.env().target, InstanceOfAccount): +class ConsoleProfileActionPerform(m := scoped_collect.env().target, InstanceOfAccount, static=True): @m.impl(CoreCapability.pull, "lang.user", Nick) async def get_console_nick(self, target: Selector) -> Nick: console = self.account.client.storage.current_user diff --git a/avilla/console/perform/context.py b/avilla/console/perform/context.py index be1d5120..b7c21e2f 100644 --- a/avilla/console/perform/context.py +++ b/avilla/console/perform/context.py @@ -13,7 +13,7 @@ from avilla.console.protocol import ConsoleProtocol # noqa -class ConsoleContextPerform(m := scoped_collect.env().target, InstanceOfAccount): +class ConsoleContextPerform(m := scoped_collect.env().target, InstanceOfAccount, static=True): @m.impl(CoreCapability.get_context, target="land.user") def get_context_from_channel(self, target: Selector, *, via: Selector | None = None): return Context( diff --git a/avilla/console/perform/event/message.py b/avilla/console/perform/event/message.py index 0136a2ca..8b6a774d 100644 --- a/avilla/console/perform/event/message.py +++ b/avilla/console/perform/event/message.py @@ -20,7 +20,7 @@ from avilla.console.protocol import ConsoleProtocol # noqa -class ConsoleEventMessagePerform(m := scoped_collect.globals().target, InstanceOfAccount): +class ConsoleEventMessagePerform(m := scoped_collect.globals().target, InstanceOfAccount, static=True): @m.impl(ConsoleCapability.event_callback, event=MessageEvent) async def console_message(self, event: MessageEvent): console = Selector().land(self.account.route["land"]).user(str(event.user.id)) diff --git a/avilla/console/protocol.py b/avilla/console/protocol.py index 92439fa0..25e7af55 100644 --- a/avilla/console/protocol.py +++ b/avilla/console/protocol.py @@ -16,7 +16,6 @@ class ConsoleProtocol(BaseProtocol): def artifacts(): with CollectContext().collect_scope() as collect_context: # isort: off - import avilla.console.perform.action.activity # noqa import avilla.console.perform.action.message import avilla.console.perform.action.profile @@ -31,6 +30,7 @@ def __init__(self, name: str = "robot"): self.name = name def ensure(self, avilla: Avilla): + self.artifacts # access at last 1 time. self.avilla = avilla self.service = ConsoleService(self) avilla.launch_manager.add_component(self.service) diff --git a/avilla/core/builtins/capability.py b/avilla/core/builtins/capability.py index 86bb96c0..578217a8 100644 --- a/avilla/core/builtins/capability.py +++ b/avilla/core/builtins/capability.py @@ -3,7 +3,8 @@ from typing import TYPE_CHECKING, Any, Protocol, TypeVar, overload from flywheel.fn.base import Fn -from flywheel.fn.compose import FnCompose, OverloadRecorder +from flywheel.fn.compose import FnCompose +from flywheel.fn.implement import OverloadRecorder from flywheel.fn.record import FnRecord from flywheel.overloads import SimpleOverload, TypeOverload from typing_extensions import Unpack @@ -68,7 +69,7 @@ def collect(self, recorder: OverloadRecorder, target: str, via: str | None = Non @Fn.declare class pull(FnCompose): - route: SimpleOverload + route = SimpleOverload("route") async def call( self, record: FnRecord, target: Selector, route: type[M] | MetadataRoute[Unpack[tuple[Any, ...]], M] diff --git a/avilla/core/builtins/resource_fetch.py b/avilla/core/builtins/resource_fetch.py index 0a1fcacf..56562773 100644 --- a/avilla/core/builtins/resource_fetch.py +++ b/avilla/core/builtins/resource_fetch.py @@ -12,7 +12,7 @@ from .capability import CoreCapability -class CoreResourceFetchPerform(m := scoped_collect.globals().target): +class CoreResourceFetchPerform(m := scoped_collect.globals().target, static=True): @m.impl(CoreCapability.fetch, resource=LocalFileResource) async def fetch_localfile(self, resource: LocalFileResource): return resource.file.read_bytes() diff --git a/avilla/core/event.py b/avilla/core/event.py index c59dbf25..4012880a 100644 --- a/avilla/core/event.py +++ b/avilla/core/event.py @@ -36,7 +36,7 @@ async def beforeExecution(interface: DispatcherInterface[AvillaEvent]): stack: AsyncExitStack = interface.local_storage["_depend_lifespan_manager"] stack.enter_context(interface.event.context.protocol.artifacts.lookup_scope()) - stack.enter_context(interface.event.context.instance_context.scope()) + #stack.enter_context(interface.event.context.instance_context.scope()) @staticmethod async def catch(interface: DispatcherInterface[AvillaEvent]): diff --git a/avilla/core/ryanvk/overloads.py b/avilla/core/ryanvk/overloads.py index 6bcc6aff..2214dd04 100644 --- a/avilla/core/ryanvk/overloads.py +++ b/avilla/core/ryanvk/overloads.py @@ -29,6 +29,9 @@ class TargetOverloadSignature: order: str predicators: dict[str, FollowsPredicater] = field(default_factory=dict) + def __hash__(self) -> int: + return hash(("TOS", self.order, tuple(self.predicators.items()))) + class TargetOverload(FnOverload[TargetOverloadSignature, tuple[str, dict[str, FollowsPredicater]], Selector]): def digest(self, collect_value: tuple[str, dict[str, FollowsPredicater]]) -> TargetOverloadSignature: diff --git a/pdm.lock b/pdm.lock index a489f04e..2b76d0c7 100644 --- a/pdm.lock +++ b/pdm.lock @@ -373,15 +373,15 @@ files = [ [[package]] name = "elaina-flywheel" -version = "0.1.1" +version = "0.1.4" requires_python = ">=3.9" summary = "" dependencies = [ "typing-extensions>=4.10.0", ] files = [ - {file = "elaina_flywheel-0.1.1-py3-none-any.whl", hash = "sha256:133d3d3af05465f37bee6e7e655b19697dea9081b0abbf4450e502ae65cf2f5e"}, - {file = "elaina_flywheel-0.1.1.tar.gz", hash = "sha256:78422e8e917618e3f3429e77448704269d1cd09fd062def10c3a6329a76b9069"}, + {file = "elaina_flywheel-0.1.4-py3-none-any.whl", hash = "sha256:490b460dc5f2724d7ff506e39f89edbc360b6a9df7a46d4de1362c65d18446a9"}, + {file = "elaina_flywheel-0.1.4.tar.gz", hash = "sha256:bc7690613f13c57492fb815bdb3ad32524747f874ea202c782342c5b86f11d1b"}, ] [[package]] diff --git a/test-console.py b/test-console.py index 8606b34c..961b26f5 100644 --- a/test-console.py +++ b/test-console.py @@ -7,6 +7,8 @@ avilla.apply_protocols(ConsoleProtocol()) +from flywheel.globals import GLOBAL_INSTANCE_CONTEXT + @avilla.listen(MessageReceived) async def on_message_received(ctx: Context, event: MessageReceived): msg = str(event.message.content) From 0f35265058bf931ab76a9b522a6b2b94a0e6e79a Mon Sep 17 00:00:00 2001 From: Elaina Date: Tue, 19 Mar 2024 09:15:36 +0800 Subject: [PATCH 17/24] cleanup console backend --- avilla/console/backend.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/avilla/console/backend.py b/avilla/console/backend.py index 3d1f4ad5..909d94b0 100644 --- a/avilla/console/backend.py +++ b/avilla/console/backend.py @@ -1,11 +1,11 @@ from __future__ import annotations +from functools import cached_property import sys from contextlib import suppress from typing import TYPE_CHECKING from flywheel import InstanceContext -from flywheel.globals import INSTANCE_CONTEXT_VAR from loguru import logger from nonechat import Backend, Frontend from nonechat.info import Event @@ -73,14 +73,17 @@ def on_console_unmount(self): logger.success("Console exit.") logger.warning("Press Ctrl-C for Application exit") - async def post_event(self, event: Event): - with InstanceContext().scope() as context: - context.instances[type(self.account)] = self.account - context.instances[type(self._service.protocol)] = self._service.protocol + @cached_property + def event_instance_ctx(self): + res = InstanceContext() + res.instances[type(self.account)] = self.account + res.instances[type(self._service.protocol)] = self._service.protocol + return res - with suppress(NotImplementedError): - res = await ConsoleCapability.event_callback(event) - self._service.protocol.post_event(res) - return + async def post_event(self, event: Event): + with self.event_instance_ctx.scope(), suppress(NotImplementedError): + res = await ConsoleCapability.event_callback(event) + self._service.protocol.post_event(res) + return logger.warning(f"received unsupported event {event.type}: {event}") From 825cd5581c57a75fa79bb0bd21b837fc9f756f27 Mon Sep 17 00:00:00 2001 From: Elaina Date: Tue, 19 Mar 2024 15:06:32 +0800 Subject: [PATCH 18/24] sync ob11 process --- avilla/core/account.py | 2 +- avilla/core/service.py | 12 + avilla/onebot/v11/bases.py | 12 + avilla/onebot/v11/capability.py | 83 ++++-- avilla/onebot/v11/collector/__init__.py | 0 avilla/onebot/v11/collector/connection.py | 37 --- avilla/onebot/v11/net/base.py | 13 +- avilla/onebot/v11/perform/context.py | 33 +-- .../onebot/v11/perform/message/deserialize.py | 184 ++++++------ .../onebot/v11/perform/message/serialize.py | 274 ++++++++++-------- avilla/onebot/v11/perform/resource_fetch.py | 30 +- avilla/onebot/v11/protocol.py | 58 ++-- 12 files changed, 375 insertions(+), 363 deletions(-) create mode 100644 avilla/onebot/v11/bases.py delete mode 100644 avilla/onebot/v11/collector/__init__.py delete mode 100644 avilla/onebot/v11/collector/connection.py diff --git a/avilla/core/account.py b/avilla/core/account.py index d8e85411..f73c8c61 100644 --- a/avilla/core/account.py +++ b/avilla/core/account.py @@ -30,7 +30,7 @@ class AccountInfo: class BaseAccount: route: Selector avilla: Avilla - artifacts: CollectContext = field(default_factory=CollectContext) + artifacts: CollectContext = field(default_factory=CollectContext, init=False) @property def info(self) -> AccountInfo: diff --git a/avilla/core/service.py b/avilla/core/service.py index 097726b5..ecb6ec86 100644 --- a/avilla/core/service.py +++ b/avilla/core/service.py @@ -1,8 +1,10 @@ from __future__ import annotations from collections import defaultdict +from functools import cached_property from typing import TYPE_CHECKING +from flywheel.context import InstanceContext from launart import Launart, Service from loguru import logger @@ -46,8 +48,17 @@ def stages(self): def get_interface(self, interface_type): ... + @cached_property + def instances_endpoint(self): + res = InstanceContext() + res.instances[type(self.avilla)] = self.avilla + return res + async def launch(self, manager: Launart): + endp = self.instances_endpoint.scope() + async with self.stage("preparing"): + endp.__enter__() await self.avilla.broadcast.postEvent(ApplicationPreparing(self.avilla)) logger.info(AVILLA_ASCII_RAW_LOGO, alt=AVILLA_ASCII_LOGO) @@ -68,3 +79,4 @@ async def launch(self, manager: Launart): await self.avilla.broadcast.postEvent(ApplicationClosing(self.avilla)) await self.avilla.broadcast.postEvent(ApplicationClosed(self.avilla)) + endp.__exit__(None, None, None) diff --git a/avilla/onebot/v11/bases.py b/avilla/onebot/v11/bases.py new file mode 100644 index 00000000..62a7759e --- /dev/null +++ b/avilla/onebot/v11/bases.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from flywheel import InstanceOf +from avilla.core.ryanvk.bases import InstanceOfAvilla +from avilla.onebot.v11.protocol import OneBot11Protocol +from avilla.onebot.v11.account import OneBot11Account + +class InstanceOfProtocol(InstanceOfAvilla): + protocol = InstanceOf(OneBot11Protocol) + +class InstanceOfAccount(InstanceOfProtocol): + account = InstanceOf(OneBot11Account) diff --git a/avilla/onebot/v11/capability.py b/avilla/onebot/v11/capability.py index 4c4fca6c..a6616d80 100644 --- a/avilla/onebot/v11/capability.py +++ b/avilla/onebot/v11/capability.py @@ -1,13 +1,18 @@ from __future__ import annotations -from typing import Any +from typing import Any, Protocol, TypeVar +from flywheel.globals import INSTANCE_CONTEXT_VAR from graia.amnesia.message import Element, MessageChain from avilla.core.event import AvillaEvent -from avilla.core.ryanvk_old.collector.application import ApplicationCollector +from flywheel import Fn, FnCompose, OverloadRecorder, FnRecord, SimpleOverload, TypeOverload +from avilla.core.application import Avilla from avilla.standard.core.application import AvillaLifecycleEvent -from graia.ryanvk import Fn, PredicateOverload, TypeOverload + +El = TypeVar("El", bound=Element) +El_co = TypeVar("El_co", bound=Element, covariant=True) +El_contra = TypeVar("El_contra", bound=Element, contravariant=True) SPECIAL_POST_TYPE = {"message_sent": "message"} @@ -20,37 +25,75 @@ def onebot11_event_type(raw: dict) -> str: ) -class OneBot11Capability((m := ApplicationCollector())._): - @Fn.complex({PredicateOverload(lambda _, raw: onebot11_event_type(raw)): ["raw_event"]}) - async def event_callback(self, raw_event: dict) -> AvillaEvent | AvillaLifecycleEvent | None: ... +class OneBot11Capability: + @Fn.declare + class event_callback(FnCompose): + event_type = SimpleOverload("raw_event") + + async def call(self, record: FnRecord, raw_event: dict): + entities = self.load(self.event_type.dig(record, onebot11_event_type(raw_event))) + return await entities.first(raw_event=raw_event) + + class shapecall(Protocol): + async def __call__(self, raw_event: dict) -> AvillaEvent | AvillaLifecycleEvent: + ... + + def collect(self, recorder: OverloadRecorder[shapecall], event: str): + recorder.use(self.event_type, event) + + @Fn.declare + class serialize_element(FnCompose): + element = TypeOverload("element") + + async def call(self, record: FnRecord, element: Element): + entities = self.load(self.element.dig(record, element)) + return await entities.first(element=element) + + class shapecall(Protocol[El_contra]): + async def __call__(self, element: El_contra) -> dict: + ... + + def collect(self, recorder: OverloadRecorder[shapecall[El]], element_type: type[El]): + recorder.use(self.element, element_type) + + @Fn.declare + class deserialize_element(FnCompose): + element = SimpleOverload("element") + + async def call(self, record: FnRecord, element: dict): + entities = self.load(self.element.dig(record, element['type'])) + return await entities.first(element=element) - @Fn.complex({PredicateOverload(lambda _, raw: raw["type"]): ["raw_element"]}) - async def deserialize_element(self, raw_element: dict) -> Element: # type: ignore - ... + class shapecall(Protocol): + async def __call__(self, element: dict) -> Element: + ... - @Fn.complex({TypeOverload(): ["element"]}) - async def serialize_element(self, element: Any) -> dict: # type: ignore - ... + def collect(self, recorder: OverloadRecorder[shapecall], element_type: str): + recorder.use(self.element, element_type) - async def deserialize_chain(self, chain: list[dict]): + @staticmethod + async def deserialize_chain(chain: list[dict]): elements = [] for raw_element in chain: - elements.append(await self.deserialize_element(raw_element)) + elements.append(await OneBot11Capability.deserialize_element(raw_element)) return MessageChain(elements) - async def serialize_chain(self, chain: MessageChain): + @staticmethod + async def serialize_chain(chain: MessageChain): elements = [] for element in chain: - elements.append(await self.serialize_element(element)) + elements.append(await OneBot11Capability.serialize_element(element)) return elements - async def handle_event(self, event: dict): - maybe_event = await self.event_callback(event) + @staticmethod + async def handle_event(event: dict): + maybe_event = await OneBot11Capability.event_callback(event) + avilla = INSTANCE_CONTEXT_VAR.get().instances[Avilla] if maybe_event is not None: - self.avilla.event_record(maybe_event) - self.avilla.broadcast.postEvent(maybe_event) + avilla.event_record(maybe_event) + avilla.broadcast.postEvent(maybe_event) diff --git a/avilla/onebot/v11/collector/__init__.py b/avilla/onebot/v11/collector/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/avilla/onebot/v11/collector/connection.py b/avilla/onebot/v11/collector/connection.py deleted file mode 100644 index 70bed67f..00000000 --- a/avilla/onebot/v11/collector/connection.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, ClassVar, TypeVar - -from avilla.core.ryanvk_old.collector.base import AvillaBaseCollector -from graia.ryanvk import Access, BasePerform - -if TYPE_CHECKING: - from avilla.onebot.v11.net.ws_client import OneBot11WsClientNetworking - from avilla.onebot.v11.protocol import OneBot11Protocol - - -T = TypeVar("T") -T1 = TypeVar("T1") - - -class ConnectionBasedPerformTemplate(BasePerform, native=True): - __collector__: ClassVar[ConnectionCollector] - - protocol: Access[OneBot11Protocol] = Access() - connection: Access[OneBot11WsClientNetworking] = Access() - - -class ConnectionCollector(AvillaBaseCollector): - post_applying: bool = False - - @property - def _(self): - upper = super()._ - - class PerformTemplate( - ConnectionBasedPerformTemplate, - upper, - native=True, - ): ... - - return PerformTemplate diff --git a/avilla/onebot/v11/net/base.py b/avilla/onebot/v11/net/base.py index ce54b09a..40a7e097 100644 --- a/avilla/onebot/v11/net/base.py +++ b/avilla/onebot/v11/net/base.py @@ -8,7 +8,6 @@ from typing_extensions import Self from avilla.core.exceptions import ActionFailed -from avilla.core.ryanvk_old.staff import Staff from avilla.onebot.v11.capability import OneBot11Capability if TYPE_CHECKING: @@ -29,16 +28,6 @@ def __init__(self, protocol: OneBot11Protocol): self.response_waiters = {} self.close_signal = asyncio.Event() - def get_staff_components(self): - return {"connection": self, "protocol": self.protocol, "avilla": self.protocol.avilla} - - def get_staff_artifacts(self): - return [self.protocol.artifacts, self.protocol.avilla.global_artifacts] - - @property - def staff(self): - return Staff(self.get_staff_artifacts(), self.get_staff_components()) - def message_receive(self) -> AsyncIterator[tuple[Self, dict]]: ... @property @@ -57,7 +46,7 @@ async def message_handle(self): async def event_parse_task(data: dict): with suppress(NotImplementedError): - await OneBot11Capability(connection.staff).handle_event(data) + await OneBot11Capability.handle_event(data) return logger.warning(f"received unsupported event: {data}") diff --git a/avilla/onebot/v11/perform/context.py b/avilla/onebot/v11/perform/context.py index 921b4321..7a777976 100644 --- a/avilla/onebot/v11/perform/context.py +++ b/avilla/onebot/v11/perform/context.py @@ -4,7 +4,8 @@ from avilla.core.builtins.capability import CoreCapability from avilla.core.context import Context -from avilla.core.ryanvk_old.collector.account import AccountCollector +from flywheel import scoped_collect +from avilla.onebot.v11.bases import InstanceOfAccount from avilla.core.selector import Selector if TYPE_CHECKING: @@ -12,10 +13,8 @@ from avilla.onebot.v11.protocol import OneBot11Protocol # noqa -class OneBot11ContextPerform((m := AccountCollector["OneBot11Protocol", "OneBot11Account"]())._): - m.namespace = "avilla.protocol/onebot11::context" - - @m.entity(CoreCapability.get_context, target="land.group") +class OneBot11ContextPerform(m := scoped_collect.env().target, InstanceOfAccount, static=True): + @m.impl(CoreCapability.get_context, target="land.group") def get_context_from_group(self, target: Selector, *, via: Selector | None = None): return Context( self.account, @@ -25,7 +24,7 @@ def get_context_from_group(self, target: Selector, *, via: Selector | None = Non target.member(self.account.route["account"]), ) - @m.entity(CoreCapability.get_context, target="land.friend") + @m.impl(CoreCapability.get_context, target="land.friend") def get_context_from_friend(self, target: Selector, *, via: Selector | None = None): if via: return Context( @@ -37,11 +36,11 @@ def get_context_from_friend(self, target: Selector, *, via: Selector | None = No ) return Context(self.account, target, self.account.route, target, self.account.route) - @m.entity(CoreCapability.get_context, target="land.stranger") + @m.impl(CoreCapability.get_context, target="land.stranger") def get_context_from_stranger(self, target: Selector, *, via: Selector | None = None): return Context(self.account, target, self.account.route, target, self.account.route) - @m.entity(CoreCapability.get_context, target="land.group.member") + @m.impl(CoreCapability.get_context, target="land.group.member") def get_context_from_member(self, target: Selector, *, via: Selector | None = None): return Context( self.account, @@ -51,26 +50,26 @@ def get_context_from_member(self, target: Selector, *, via: Selector | None = No target.into(f"~.member({self.account.route['account']})"), ) - @m.entity(CoreCapability.channel, target="land.group") - @m.entity(CoreCapability.channel, target="land.group.member") + @m.impl(CoreCapability.channel, target="land.group") + @m.impl(CoreCapability.channel, target="land.group.member") def channel_from_group(self, target: Selector): return target["group"] - @m.entity(CoreCapability.guild, target="land.group") - @m.entity(CoreCapability.guild, target="land.group.member") + @m.impl(CoreCapability.guild, target="land.group") + @m.impl(CoreCapability.guild, target="land.group.member") def guild_from_group(self, target: Selector): return target["group"] - @m.entity(CoreCapability.user, target="land.group.member") + @m.impl(CoreCapability.user, target="land.group.member") def user_from_member(self, target: Selector): return target["member"] - @m.entity(CoreCapability.user, target="land.friend") - @m.entity(CoreCapability.channel, target="land.friend") + @m.impl(CoreCapability.user, target="land.friend") + @m.impl(CoreCapability.channel, target="land.friend") def user_from_friend(self, target: Selector): return target["friend"] - @m.entity(CoreCapability.user, target="land.stranger") - @m.entity(CoreCapability.channel, target="land.stranger") + @m.impl(CoreCapability.user, target="land.stranger") + @m.impl(CoreCapability.channel, target="land.stranger") def user_from_stranger(self, target: Selector): return target["stranger"] diff --git a/avilla/onebot/v11/perform/message/deserialize.py b/avilla/onebot/v11/perform/message/deserialize.py index c7bddd57..7bb85d7f 100644 --- a/avilla/onebot/v11/perform/message/deserialize.py +++ b/avilla/onebot/v11/perform/message/deserialize.py @@ -1,13 +1,13 @@ from __future__ import annotations from datetime import datetime -from typing import TYPE_CHECKING +from contextlib import suppress from avilla.core.elements import Face, Notice, NoticeAll, Picture, Reference, Text -from avilla.core.ryanvk_old.collector.application import ApplicationCollector from avilla.core.selector import Selector from avilla.onebot.v11.capability import OneBot11Capability from avilla.onebot.v11.resource import OneBot11ImageResource +from avilla.core.context import Context from avilla.standard.qq.elements import ( Dice, FlashImage, @@ -18,95 +18,97 @@ Share, Xml, ) -from graia.ryanvk import OptionalAccess - -if TYPE_CHECKING: - from avilla.core.context import Context - from avilla.onebot.v11.account import OneBot11Account - - -class OneBot11MessageDeserializePerform((m := ApplicationCollector())._): - m.namespace = "avilla.protocol/onebot11::message" - m.identify = "deserialize" - - context: OptionalAccess[Context] = OptionalAccess() - account: OptionalAccess[OneBot11Account] = OptionalAccess() - # LINK: https://github.com/microsoft/pyright/issues/5409 - - @m.entity(OneBot11Capability.deserialize_element, raw_element="text") - async def text(self, raw_element: dict) -> Text: - return Text(raw_element["data"]["text"]) - - @m.entity(OneBot11Capability.deserialize_element, raw_element="face") - async def face(self, raw_element: dict) -> Face: - return Face(raw_element["data"]["id"]) - - @m.entity(OneBot11Capability.deserialize_element, raw_element="image") - async def image(self, raw_element: dict) -> Picture | FlashImage: - data: dict = raw_element["data"] - resource = OneBot11ImageResource(Selector().land("qq").picture(file := data["file"]), file, data["url"]) - return FlashImage(resource) if raw_element.get("type") == "flash" else Picture(resource) - - @m.entity(OneBot11Capability.deserialize_element, raw_element="at") - async def at(self, raw_element: dict) -> Notice | NoticeAll: - if raw_element["data"]["qq"] == "all": - return NoticeAll() - if self.context: - return Notice(self.context.scene.member(raw_element["data"]["qq"])) - return Notice(Selector().land("qq").member(raw_element["data"]["qq"])) - - @m.entity(OneBot11Capability.deserialize_element, raw_element="reply") - async def reply(self, raw_element: dict): - if self.context: - return Reference(self.context.scene.message(raw_element["data"]["id"])) - return Reference(Selector().land("qq").message(raw_element["data"]["id"])) - - @m.entity(OneBot11Capability.deserialize_element, raw_element="dice") - async def dice(self, raw_element: dict): - return Dice() - - @m.entity(OneBot11Capability.deserialize_element, raw_element="shake") - async def shake(self, raw_element: dict): - return Poke() - - @m.entity(OneBot11Capability.deserialize_element, raw_element="json") - async def json(self, raw_element: dict): - return Json(raw_element["data"]["content"]) - - @m.entity(OneBot11Capability.deserialize_element, raw_element="xml") - async def xml(self, raw_element: dict): - return Xml(raw_element["data"]["content"]) - - @m.entity(OneBot11Capability.deserialize_element, raw_element="share") - async def share(self, raw_element: dict): - return Share( - raw_element["data"]["url"], - raw_element["data"]["title"], - raw_element["data"].get("content", None), - raw_element["data"].get("image", None), - ) +from flywheel.globals import INSTANCE_CONTEXT_VAR - @m.entity(OneBot11Capability.deserialize_element, raw_element="forward") - async def forward(self, raw_element: dict): - elem = Forward(raw_element["data"]["id"]) - if not self.account: - return elem - result = await self.account.connection.call( - "get_forward_msg", - { - "message_id": raw_element["data"]["id"], - }, - ) - if result is None: - return elem - for msg in result["messages"]: - node = Node( - name=msg["sender"]["nickname"], - uid=str(msg["sender"]["user_id"]), - time=datetime.fromtimestamp(msg["time"]), - content=await OneBot11Capability(self.account.staff).deserialize_chain(msg["content"]), - ) - elem.nodes.append(node) +from avilla.onebot.v11.account import OneBot11Account + +@OneBot11Capability.deserialize_element.impl(element_type="text") +async def text(element: dict) -> Text: + return Text(element["data"]["text"]) + + +@OneBot11Capability.deserialize_element.impl(element_type="face") +async def face(element: dict) -> Face: + return Face(element["data"]["id"]) + + +@OneBot11Capability.deserialize_element.impl(element_type="image") +async def image(element: dict) -> Picture | FlashImage: + data: dict = element["data"] + resource = OneBot11ImageResource(Selector().land("qq").picture(file := data["file"]), file, data["url"]) + return FlashImage(resource) if element.get("type") == "flash" else Picture(resource) + + +@OneBot11Capability.deserialize_element.impl(element_type="at") +async def at(element: dict) -> Notice | NoticeAll: + if element["data"]["qq"] == "all": + return NoticeAll() + with suppress(LookupError): + return Notice(Context.current.scene.member(element["data"]["qq"])) + return Notice(Selector().land("qq").member(element["data"]["qq"])) + + +@OneBot11Capability.deserialize_element.impl(element_type="reply") +async def reply(element: dict): + with suppress(LookupError): + return Reference(Context.current.scene.message(element["data"]["id"])) + return Reference(Selector().land("qq").message(element["data"]["id"])) + + +@OneBot11Capability.deserialize_element.impl(element_type="dice") +async def dice(element: dict): + return Dice() + + +@OneBot11Capability.deserialize_element.impl(element_type="shake") +async def shake(element: dict): + return Poke() + + +@OneBot11Capability.deserialize_element.impl(element_type="json") +async def json(element: dict): + return Json(element["data"]["content"]) + + +@OneBot11Capability.deserialize_element.impl(element_type="xml") +async def xml(element: dict): + return Xml(element["data"]["content"]) + + +@OneBot11Capability.deserialize_element.impl(element_type="share") +async def share(element: dict): + return Share( + element["data"]["url"], + element["data"]["title"], + element["data"].get("content", None), + element["data"].get("image", None), + ) + + +@OneBot11Capability.deserialize_element.impl(element_type="forward") +async def forward(element: dict): + elem = Forward(element["data"]["id"]) + + if OneBot11Account not in INSTANCE_CONTEXT_VAR.get().instances: return elem - # TODO + result = await INSTANCE_CONTEXT_VAR.get().instances[OneBot11Account].connection.call( + "get_forward_msg", + { + "message_id": element["data"]["id"], + }, + ) + if result is None: + return elem + for msg in result["messages"]: + node = Node( + name=msg["sender"]["nickname"], + uid=str(msg["sender"]["user_id"]), + time=datetime.fromtimestamp(msg["time"]), + content=await OneBot11Capability.deserialize_chain(msg["content"]), + ) + elem.nodes.append(node) + return elem + + +# TODO diff --git a/avilla/onebot/v11/perform/message/serialize.py b/avilla/onebot/v11/perform/message/serialize.py index c7d541b8..bf5133db 100644 --- a/avilla/onebot/v11/perform/message/serialize.py +++ b/avilla/onebot/v11/perform/message/serialize.py @@ -1,11 +1,13 @@ from __future__ import annotations import base64 -from typing import TYPE_CHECKING, cast +from typing import cast +from flywheel import global_collect + +from avilla.core.builtins.capability import CoreCapability from avilla.core.elements import Face, Notice, NoticeAll, Picture, Text from avilla.core.resource import LocalFileResource, RawResource, UrlResource -from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.onebot.v11.capability import OneBot11Capability from avilla.onebot.v11.resource import OneBot11ImageResource from avilla.standard.qq.elements import ( @@ -20,137 +22,153 @@ Xml, ) -if TYPE_CHECKING: - from avilla.onebot.v11.account import OneBot11Account # noqa - from avilla.onebot.v11.protocol import OneBot11Protocol # noqa - - -class OneBot11MessageSerializePerform((m := AccountCollector["OneBot11Protocol", "OneBot11Account"]())._): - m.namespace = "avilla.protocol/onebot11::message" - m.identify = "serialize" - - # LINK: https://github.com/microsoft/pyright/issues/5409 - - @m.entity(OneBot11Capability.serialize_element, element=Text) - async def text(self, element: Text) -> dict: - return {"type": "text", "data": {"text": element.text}} - - @m.entity(OneBot11Capability.serialize_element, element=Face) - async def face(self, element: Face) -> dict: - return {"type": "face", "data": {"id": int(element.id)}} - - @m.entity(OneBot11Capability.serialize_element, element=Picture) - async def picture(self, element: Picture) -> dict: - if isinstance(element.resource, OneBot11ImageResource): - return { - "type": "image", - "data": { - "file": element.resource.file, - "url": element.resource.url, - }, - } - elif isinstance(element.resource, UrlResource): - return { - "type": "image", - "data": { - "url": element.resource.url, - }, - } - elif isinstance(element.resource, LocalFileResource): - data = base64.b64encode(element.resource.file.read_bytes()).decode("utf-8") - return { - "type": "image", - "data": { - "file": "base64://" + data, - }, - } - elif isinstance(element.resource, RawResource): - data = base64.b64encode(element.resource.data).decode("utf-8") - return { - "type": "image", - "data": { - "file": "base64://" + data, - }, - } - else: - return { - "type": "image", - "data": { - "file": "base64://" - + base64.b64encode(cast(bytes, await self.account.staff.fetch_resource(element.resource))).decode( - "utf-8" - ), - }, - } - - @m.entity(OneBot11Capability.serialize_element, element=FlashImage) - async def flash_image(self, element: FlashImage): - raw = await self.picture(element) - raw["data"]["type"] = "flash" - return raw - - @m.entity(OneBot11Capability.serialize_element, element=Notice) - async def notice(self, element: Notice): - return {"type": "at", "data": {"qq": element.target["member"]}} - - @m.entity(OneBot11Capability.serialize_element, element=NoticeAll) - async def notice_all(self, element: NoticeAll): - return {"type": "at", "data": {"qq": "all"}} - - @m.entity(OneBot11Capability.serialize_element, element=Dice) - async def dice(self, element: Dice): - return {"type": "dice", "data": {}} - - @m.entity(OneBot11Capability.serialize_element, element=MusicShare) - async def music_share(self, element: MusicShare): - raw = { - "type": "music", + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=Text) +async def text(element: Text) -> dict: + return {"type": "text", "data": {"text": element.text}} + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=Face) +async def face(element: Face) -> dict: + return {"type": "face", "data": {"id": int(element.id)}} + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=Picture) +async def picture(element: Picture) -> dict: + if isinstance(element.resource, OneBot11ImageResource): + return { + "type": "image", "data": { - "type": "custom", - "url": element.url, - "audio": element.audio, - "title": element.title, + "file": element.resource.file, + "url": element.resource.url, }, } - if element.content: - raw["data"]["content"] = element.content - if element.thumbnail: - raw["data"]["image"] = element.thumbnail - return raw - - @m.entity(OneBot11Capability.serialize_element, element=Gift) - async def gift(self, element: Gift): - return {"type": "gift", "data": {"id": element.kind.value, "qq": element.target["member"]}} - - @m.entity(OneBot11Capability.serialize_element, element=Json) - async def json(self, element: Json): - return {"type": "json", "data": {"data": element.content}} - - @m.entity(OneBot11Capability.serialize_element, element=Xml) - async def xml(self, element: Xml): - return {"type": "xml", "data": {"data": element.content}} - - @m.entity(OneBot11Capability.serialize_element, element=App) - async def app(self, element: App): - return {"type": "json", "data": {"data": element.content}} - - @m.entity(OneBot11Capability.serialize_element, element=Share) - async def share(self, element: Share): - res = { - "type": "share", + elif isinstance(element.resource, UrlResource): + return { + "type": "image", "data": { - "url": element.url, - "title": element.title, + "url": element.resource.url, }, } - if element.content: - res["data"]["content"] = element.content - if element.thumbnail: - res["data"]["image"] = element.thumbnail - return res + elif isinstance(element.resource, LocalFileResource): + data = base64.b64encode(element.resource.file.read_bytes()).decode("utf-8") + return { + "type": "image", + "data": { + "file": "base64://" + data, + }, + } + elif isinstance(element.resource, RawResource): + data = base64.b64encode(element.resource.data).decode("utf-8") + return { + "type": "image", + "data": { + "file": "base64://" + data, + }, + } + else: + return { + "type": "image", + "data": { + "file": "base64://" + + base64.b64encode(cast(bytes, await CoreCapability.fetch(element.resource))).decode("utf-8"), + }, + } + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=FlashImage) +async def flash_image(element: FlashImage): + raw = await picture(element) + raw["data"]["type"] = "flash" + return raw + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=Notice) +async def notice(element: Notice): + return {"type": "at", "data": {"qq": element.target["member"]}} + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=NoticeAll) +async def notice_all(element: NoticeAll): + return {"type": "at", "data": {"qq": "all"}} + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=Dice) +async def dice(element: Dice): + return {"type": "dice", "data": {}} + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=MusicShare) +async def music_share(element: MusicShare): + raw = { + "type": "music", + "data": { + "type": "custom", + "url": element.url, + "audio": element.audio, + "title": element.title, + }, + } + if element.content: + raw["data"]["content"] = element.content + if element.thumbnail: + raw["data"]["image"] = element.thumbnail + return raw + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=Gift) +async def gift(element: Gift): + return {"type": "gift", "data": {"id": element.kind.value, "qq": element.target["member"]}} + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=Json) +async def json(element: Json): + return {"type": "json", "data": {"data": element.content}} + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=Xml) +async def xml(element: Xml): + return {"type": "xml", "data": {"data": element.content}} + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=App) +async def app(element: App): + return {"type": "json", "data": {"data": element.content}} + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=Share) +async def share(element: Share): + res = { + "type": "share", + "data": { + "url": element.url, + "title": element.title, + }, + } + if element.content: + res["data"]["content"] = element.content + if element.thumbnail: + res["data"]["image"] = element.thumbnail + return res + + +@global_collect +@OneBot11Capability.serialize_element.impl(element_type=Poke) +async def poke(element: Poke): + return {"type": "shake", "data": {}} - @m.entity(OneBot11Capability.serialize_element, element=Poke) - async def poke(self, element: Poke): - return {"type": "shake", "data": {}} - # TODO +# TODO diff --git a/avilla/onebot/v11/perform/resource_fetch.py b/avilla/onebot/v11/perform/resource_fetch.py index 3b0233f8..c1b48ee3 100644 --- a/avilla/onebot/v11/perform/resource_fetch.py +++ b/avilla/onebot/v11/perform/resource_fetch.py @@ -1,11 +1,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING - from aiohttp import ClientSession from avilla.core.builtins.capability import CoreCapability -from avilla.core.ryanvk_old.collector.protocol import ProtocolCollector from avilla.onebot.v11.resource import ( OneBot11FileResource, OneBot11ImageResource, @@ -13,20 +10,15 @@ OneBot11Resource, OneBot11VideoResource, ) +from flywheel import global_collect -if TYPE_CHECKING: - from avilla.onebot.v11.protocol import OneBot11Protocol # noqa - - -class OneBot11ResourceFetchPerform((m := ProtocolCollector["OneBot11Protocol"]())._): - m.namespace = "avilla.protocol/onebot11::resource_fetch" - - @m.entity(CoreCapability.fetch, resource=OneBot11Resource) - @m.entity(CoreCapability.fetch, resource=OneBot11RecordResource) - @m.entity(CoreCapability.fetch, resource=OneBot11FileResource) - @m.entity(CoreCapability.fetch, resource=OneBot11ImageResource) - @m.entity(CoreCapability.fetch, resource=OneBot11VideoResource) - async def fetch_resource(self, resource: OneBot11Resource) -> bytes: - async with ClientSession() as session: - async with session.get(resource.url) as resp: - return await resp.read() +@global_collect +@CoreCapability.fetch.impl(resource=OneBot11RecordResource) # type: ignore +@CoreCapability.fetch.impl(resource=OneBot11FileResource) # type: ignore +@CoreCapability.fetch.impl(resource=OneBot11ImageResource) # type: ignore +@CoreCapability.fetch.impl(resource=OneBot11VideoResource) +@CoreCapability.fetch.impl(resource=OneBot11Resource) +async def fetch_resource(resource: OneBot11Resource) -> bytes: + async with ClientSession() as session: + async with session.get(resource.url) as resp: + return await resp.read() diff --git a/avilla/onebot/v11/protocol.py b/avilla/onebot/v11/protocol.py index aa07efaf..d9709227 100644 --- a/avilla/onebot/v11/protocol.py +++ b/avilla/onebot/v11/protocol.py @@ -6,7 +6,8 @@ from avilla.core.application import Avilla from avilla.core.protocol import BaseProtocol -from graia.ryanvk import merge, ref +from flywheel import CollectContext +from avilla.core.utilles import cachedstatic from .net.ws_client import OneBot11WsClientNetworking from .net.ws_server import OneBot11WsServerNetworking @@ -25,46 +26,27 @@ class OneBot11ReverseConfig: access_token: str | None = None -def _import_performs(): - from avilla.onebot.v11.perform import context, resource_fetch # noqa: F401 - from avilla.onebot.v11.perform.action import admin # noqa: F401 - from avilla.onebot.v11.perform.action import ban # noqa: F401 - from avilla.onebot.v11.perform.action import leave # noqa: F401 - from avilla.onebot.v11.perform.action import message # noqa: F401 - from avilla.onebot.v11.perform.action import mute # noqa: F401 - from avilla.onebot.v11.perform.event import lifespan # noqa: F401 - from avilla.onebot.v11.perform.event import message # noqa: F401, F811 - from avilla.onebot.v11.perform.event import notice # noqa: F401 - from avilla.onebot.v11.perform.event import request # noqa: F401 - from avilla.onebot.v11.perform.message import deserialize # noqa: F401 - from avilla.onebot.v11.perform.message import serialize # noqa: F401 - from avilla.onebot.v11.perform.query import group # noqa: F401 - - -_import_performs() - - class OneBot11Protocol(BaseProtocol): service: OneBot11Service - artifacts = { - **merge( - ref("avilla.protocol/onebot11::context"), - ref("avilla.protocol/onebot11::resource_fetch"), - ref("avilla.protocol/onebot11::action", "admin"), - ref("avilla.protocol/onebot11::action", "ban"), - ref("avilla.protocol/onebot11::action", "leave"), - ref("avilla.protocol/onebot11::action", "message"), - ref("avilla.protocol/onebot11::action", "mute"), - ref("avilla.protocol/onebot11::event", "message"), - ref("avilla.protocol/onebot11::event", "lifespan"), - ref("avilla.protocol/onebot11::event", "notice"), - ref("avilla.protocol/onebot11::event", "request"), - ref("avilla.protocol/onebot11::message", "deserialize"), - ref("avilla.protocol/onebot11::message", "serialize"), - ref("avilla.protocol/onebot11::query", "group"), - ), - } + @cachedstatic + def artifacts(): + with CollectContext().lookup_scope() as collect_context: + from avilla.onebot.v11.perform import context, resource_fetch # noqa: F401 + from avilla.onebot.v11.perform.action import admin # noqa: F401 + from avilla.onebot.v11.perform.action import ban # noqa: F401 + from avilla.onebot.v11.perform.action import leave # noqa: F401 + from avilla.onebot.v11.perform.action import message # noqa: F401 + from avilla.onebot.v11.perform.action import mute # noqa: F401 + from avilla.onebot.v11.perform.event import lifespan # noqa: F401 + from avilla.onebot.v11.perform.event import message # noqa: F401, F811 + from avilla.onebot.v11.perform.event import notice # noqa: F401 + from avilla.onebot.v11.perform.event import request # noqa: F401 + from avilla.onebot.v11.perform.message import deserialize # noqa: F401 + from avilla.onebot.v11.perform.message import serialize # noqa: F401 + from avilla.onebot.v11.perform.query import group # noqa: F401 + + return collect_context def __init__(self): self.service = OneBot11Service(self) From 4036dd770f611e8a3d9baf7fb0de79337a8dc8a4 Mon Sep 17 00:00:00 2001 From: rf_tar_railt <3165388245@qq.com> Date: Tue, 19 Mar 2024 19:54:10 +0800 Subject: [PATCH 19/24] feat(satori): upgrade --- avilla/core/protocol.py | 3 +- .../onebot/v11/perform/message/deserialize.py | 12 + avilla/satori/bases.py | 19 ++ avilla/satori/capability.py | 93 ++++-- avilla/satori/collector/__init__.py | 0 avilla/satori/collector/connection.py | 38 --- avilla/satori/model.py | 7 +- avilla/satori/perform/action/message.py | 76 ++--- avilla/satori/perform/action/request.py | 26 +- avilla/satori/perform/context.py | 29 +- avilla/satori/perform/event/activity.py | 44 +-- avilla/satori/perform/event/lifespan.py | 42 +-- avilla/satori/perform/event/message.py | 57 ++-- avilla/satori/perform/event/metadata.py | 74 +++-- avilla/satori/perform/event/relationship.py | 104 +++---- avilla/satori/perform/event/request.py | 85 +++--- avilla/satori/perform/message/deserialize.py | 272 ++++++++++-------- avilla/satori/perform/message/serialize.py | 183 ++++++------ avilla/satori/perform/resource_fetch.py | 38 ++- avilla/satori/protocol.py | 28 +- avilla/satori/service.py | 41 ++- 21 files changed, 633 insertions(+), 638 deletions(-) create mode 100644 avilla/satori/bases.py delete mode 100644 avilla/satori/collector/__init__.py delete mode 100644 avilla/satori/collector/connection.py diff --git a/avilla/core/protocol.py b/avilla/core/protocol.py index 601a7040..543f355f 100644 --- a/avilla/core/protocol.py +++ b/avilla/core/protocol.py @@ -9,6 +9,7 @@ from avilla.core.event import AvillaEvent from avilla.core.globals import AVILLA_CONTEXT_VAR, CONTEXT_CONTEXT_VAR, PROTOCOL_CONTEXT_VAR from avilla.core.utilles import cachedstatic +from avilla.standard.core.application import AvillaLifecycleEvent if TYPE_CHECKING: from avilla.core.application import Avilla @@ -32,7 +33,7 @@ def ensure(self, avilla: Avilla) -> Any: ... def configure(self, config: ProtocolConfig) -> Self: ... - def post_event(self, event: AvillaEvent, context: Context | None = None): + def post_event(self, event: AvillaEvent | AvillaLifecycleEvent, context: Context | None = None): with ( AVILLA_CONTEXT_VAR.use(self.avilla), PROTOCOL_CONTEXT_VAR.use(self), diff --git a/avilla/onebot/v11/perform/message/deserialize.py b/avilla/onebot/v11/perform/message/deserialize.py index 7bb85d7f..c4c1abf8 100644 --- a/avilla/onebot/v11/perform/message/deserialize.py +++ b/avilla/onebot/v11/perform/message/deserialize.py @@ -18,20 +18,24 @@ Share, Xml, ) +from flywheel import global_collect from flywheel.globals import INSTANCE_CONTEXT_VAR from avilla.onebot.v11.account import OneBot11Account +@global_collect @OneBot11Capability.deserialize_element.impl(element_type="text") async def text(element: dict) -> Text: return Text(element["data"]["text"]) +@global_collect @OneBot11Capability.deserialize_element.impl(element_type="face") async def face(element: dict) -> Face: return Face(element["data"]["id"]) +@global_collect @OneBot11Capability.deserialize_element.impl(element_type="image") async def image(element: dict) -> Picture | FlashImage: data: dict = element["data"] @@ -39,6 +43,7 @@ async def image(element: dict) -> Picture | FlashImage: return FlashImage(resource) if element.get("type") == "flash" else Picture(resource) +@global_collect @OneBot11Capability.deserialize_element.impl(element_type="at") async def at(element: dict) -> Notice | NoticeAll: if element["data"]["qq"] == "all": @@ -48,6 +53,7 @@ async def at(element: dict) -> Notice | NoticeAll: return Notice(Selector().land("qq").member(element["data"]["qq"])) +@global_collect @OneBot11Capability.deserialize_element.impl(element_type="reply") async def reply(element: dict): with suppress(LookupError): @@ -55,26 +61,31 @@ async def reply(element: dict): return Reference(Selector().land("qq").message(element["data"]["id"])) +@global_collect @OneBot11Capability.deserialize_element.impl(element_type="dice") async def dice(element: dict): return Dice() +@global_collect @OneBot11Capability.deserialize_element.impl(element_type="shake") async def shake(element: dict): return Poke() +@global_collect @OneBot11Capability.deserialize_element.impl(element_type="json") async def json(element: dict): return Json(element["data"]["content"]) +@global_collect @OneBot11Capability.deserialize_element.impl(element_type="xml") async def xml(element: dict): return Xml(element["data"]["content"]) +@global_collect @OneBot11Capability.deserialize_element.impl(element_type="share") async def share(element: dict): return Share( @@ -85,6 +96,7 @@ async def share(element: dict): ) +@global_collect @OneBot11Capability.deserialize_element.impl(element_type="forward") async def forward(element: dict): elem = Forward(element["data"]["id"]) diff --git a/avilla/satori/bases.py b/avilla/satori/bases.py new file mode 100644 index 00000000..a7ee52a0 --- /dev/null +++ b/avilla/satori/bases.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from flywheel import InstanceOf +from satori.client.account import Account + +from .account import SatoriAccount +from .protocol import SatoriProtocol + + +class InstanceOfProtocol: + protocol = InstanceOf(SatoriProtocol) + + +class InstanceOfAccount(InstanceOfProtocol): + account = InstanceOf(SatoriAccount) + + +class InstanceOfConnection(InstanceOfProtocol): + connection = InstanceOf(Account) diff --git a/avilla/satori/capability.py b/avilla/satori/capability.py index cb4897c3..c5097f06 100644 --- a/avilla/satori/capability.py +++ b/avilla/satori/capability.py @@ -1,52 +1,87 @@ from __future__ import annotations -from typing import Any +from typing import Any, Protocol, TypeVar -from graia.amnesia.message import Element, MessageChain -from satori.parser import parse +from flywheel import ( + Fn, + FnCompose, + FnRecord, + OverloadRecorder, + SimpleOverload, + TypeOverload, +) +from graia.amnesia.message import Element as GraiaElement +from graia.amnesia.message import MessageChain +from satori.element import Element as SatoriElement from satori.element import transform -from satori.model import Event +from satori.model import Event as SatoriEvent +from satori.parser import parse from avilla.core.event import AvillaEvent -from avilla.core.ryanvk_old.collector.application import ApplicationCollector from avilla.standard.core.application.event import AvillaLifecycleEvent -from graia.ryanvk import Fn, PredicateOverload, TypeOverload +SE = TypeVar("SE", bound=SatoriElement, contravariant=True) +GE = TypeVar("GE", bound=GraiaElement, contravariant=True) +SV = TypeVar("SV", bound=SatoriEvent, contravariant=True) + + +class SatoriCapability: + @Fn.declare + class event_callback(FnCompose): + raw_event = SimpleOverload("raw_event") + + async def call(self, record: FnRecord, event: SatoriEvent): + entities = self.load(self.raw_event.dig(record, event.type)) + return await entities.first(event=event) + + class shapecall(Protocol[SV]): + async def __call__(self, event: SV) -> AvillaEvent | AvillaLifecycleEvent | list[Any]: ... + + def collect(self, recorder: OverloadRecorder[shapecall], raw_event: str): + recorder.use(self.raw_event, raw_event) -class SatoriCapability((m := ApplicationCollector())._): - @Fn.complex({PredicateOverload(lambda _, raw: raw.type): ["raw_event"]}) - async def event_callback(self, raw_event: Event) -> AvillaEvent | AvillaLifecycleEvent | list[Any] | None: ... + @Fn.declare + class deserialize_element(FnCompose): + type = TypeOverload("type") - @Fn.complex({TypeOverload(): ["raw_element"]}) - async def deserialize_element(self, raw_element: Any) -> Element: ... + async def call(self, record: FnRecord, element: SatoriElement): + entities = self.load(self.type.dig(record, element)) + return await entities.first(element=element) - @Fn.complex({TypeOverload(): ["element"]}) - async def serialize_element(self, element: Any) -> str: ... + class shapecall(Protocol[SE]): + async def __call__(self, element: SE) -> GraiaElement: ... - async def deserialize(self, content: str): + def collect(self, recorder: OverloadRecorder[shapecall[SE]], element: type[SE]): + recorder.use(self.type, element) + + @Fn.declare + class serialize_element(FnCompose): + type = TypeOverload("type") + + async def call(self, record: FnRecord, element: GraiaElement): + entities = self.load(self.type.dig(record, element)) + return await entities.first(element=element) + + class shapecall(Protocol[GE]): + async def __call__(self, element: GE) -> str: ... + + def collect(self, recorder: OverloadRecorder[shapecall[GE]], element: type[GE]): + recorder.use(self.type, element) + + @staticmethod + async def deserialize(content: str): elements = [] for raw_element in transform(parse(content)): - elements.append(await self.deserialize_element(raw_element)) + elements.append(await SatoriCapability.deserialize_element(raw_element)) return MessageChain(elements) - async def serialize(self, message: MessageChain): + @staticmethod + async def serialize(message: MessageChain): chain = [] for element in message: - chain.append(await self.serialize_element(element)) + chain.append(await SatoriCapability.serialize_element(element)) return "".join(chain) - - async def handle_event(self, event: Event): - maybe_event = await self.event_callback(event) - - if maybe_event is not None: - if isinstance(maybe_event, list): - for _event in maybe_event: - self.avilla.event_record(_event) - self.avilla.broadcast.postEvent(_event) - else: - self.avilla.event_record(maybe_event) - self.avilla.broadcast.postEvent(maybe_event) diff --git a/avilla/satori/collector/__init__.py b/avilla/satori/collector/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/avilla/satori/collector/connection.py b/avilla/satori/collector/connection.py deleted file mode 100644 index 9a54f34c..00000000 --- a/avilla/satori/collector/connection.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, ClassVar, TypeVar - -from satori.client.account import Account - -from avilla.core.ryanvk_old.collector.base import AvillaBaseCollector -from graia.ryanvk import Access, BasePerform - -if TYPE_CHECKING: - from avilla.satori.protocol import SatoriProtocol - - -T = TypeVar("T") -T1 = TypeVar("T1") - - -class ConnectionBasedPerformTemplate(BasePerform, native=True): - __collector__: ClassVar[ConnectionCollector] - - protocol: Access[SatoriProtocol] = Access() - connection: Access[Account] = Access() - - -class ConnectionCollector(AvillaBaseCollector): - post_applying: bool = False - - @property - def _(self): - upper = super()._ - - class PerformTemplate( - ConnectionBasedPerformTemplate, - upper, - native=True, - ): ... - - return PerformTemplate diff --git a/avilla/satori/model.py b/avilla/satori/model.py index b8463130..593678b2 100644 --- a/avilla/satori/model.py +++ b/avilla/satori/model.py @@ -1,17 +1,16 @@ from __future__ import annotations - from satori.model import ( + ArgvInteraction, + ButtonInteraction, Channel, Event, + Guild, Login, Member, MessageObject, Role, User, - Guild, - ButtonInteraction, - ArgvInteraction, ) diff --git a/avilla/satori/perform/action/message.py b/avilla/satori/perform/action/message.py index be48130a..d582ff49 100644 --- a/avilla/satori/perform/action/message.py +++ b/avilla/satori/perform/action/message.py @@ -2,29 +2,25 @@ from datetime import datetime, timedelta from secrets import token_urlsafe -from typing import TYPE_CHECKING +from flywheel import scoped_collect from graia.amnesia.builtins.memcache import Memcache, MemcacheService from graia.amnesia.message import MessageChain +from avilla.core.builtins.capability import CoreCapability from avilla.core.context import Context from avilla.core.elements import Reference +from avilla.core.globals import CONTEXT_CONTEXT_VAR from avilla.core.message import Message -from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector +from avilla.satori.bases import InstanceOfAccount from avilla.satori.capability import SatoriCapability -from avilla.standard.core.message import MessageRevoke, MessageSend, MessageSent +from avilla.standard.core.message import MessageSent, revoke_message, send_message -if TYPE_CHECKING: - from avilla.satori.account import SatoriAccount # noqa - from avilla.satori.protocol import SatoriProtocol # noqa +class SatoriMessageActionPerform(m := scoped_collect.env().target, InstanceOfAccount, static=True): -class SatoriMessageActionPerform((m := AccountCollector["SatoriProtocol", "SatoriAccount"]())._): - m.namespace = "avilla.protocol/satori::action" - m.identify = "message" - - @m.entity(MessageSend.send, target="land.guild.channel") + @m.impl(send_message, target="land.guild.channel") async def send_public_message( self, target: Selector, @@ -36,7 +32,7 @@ async def send_public_message( if reply: message = Reference(reply) + message result = await self.account.client.message_create( - channel_id=target["channel"], content=await SatoriCapability(self.account.staff).serialize(message) + channel_id=target["channel"], content=await SatoriCapability.serialize(message) ) for msg in result: _ctx = Context( @@ -46,7 +42,8 @@ async def send_public_message( target, target.member(self.account.route["account"]), ) - content = await SatoriCapability(self.account.staff.ext({"context": _ctx})).deserialize(msg.content) + with CONTEXT_CONTEXT_VAR.use(_ctx): + content = await SatoriCapability.deserialize(msg.content) content = content.exclude(Reference) _msg = Message( id=f"{msg.id}", @@ -65,8 +62,8 @@ async def send_public_message( ) return target.message(token) - @m.entity(MessageSend.send, target="land.user") - @m.entity(MessageSend.send, target="land.private.user") + @m.impl(send_message, target="land.user") + @m.impl(send_message, target="land.private.user") async def send_private_message( self, target: Selector, @@ -79,11 +76,11 @@ async def send_private_message( message = Reference(reply) + message if target.follows("::private.user"): result = await self.account.client.message_create( - channel_id=target["private"], content=await SatoriCapability(self.account.staff).serialize(message) + channel_id=target["private"], content=await SatoriCapability.serialize(message) ) else: result = await self.account.client.send_private_message( - user_id=target["user"], message=await SatoriCapability(self.account.staff).serialize(message) + user_id=target["user"], message=await SatoriCapability.serialize(message) ) for msg in result: _ctx = Context( @@ -93,7 +90,8 @@ async def send_private_message( target, self.account.route, ) - content = await SatoriCapability(self.account.staff.ext({"context": _ctx})).deserialize(msg.content) + with CONTEXT_CONTEXT_VAR.use(_ctx): + content = await SatoriCapability.deserialize(msg.content) content = content.exclude(Reference) _msg = Message( id=f"{msg.id}", @@ -112,7 +110,7 @@ async def send_private_message( ) return target.message(token) - @m.entity(MessageRevoke.revoke, target="land.guild.channel.message") + @m.impl(revoke_message, target="land.guild.channel.message") async def revoke_public_message(self, target: Selector): cache = self.protocol.avilla.launch_manager.get_component(MemcacheService).cache if result := await cache.get(f"satori/account({self.account.route['account']}).messages({target['message']})"): @@ -121,7 +119,7 @@ async def revoke_public_message(self, target: Selector): return await self.account.client.message_delete(channel_id=target["channel"], message_id=target["message"]) - @m.entity(MessageRevoke.revoke, target="land.private.user.message") + @m.impl(revoke_message, target="land.private.user.message") async def revoke_private_message(self, target: Selector): cache = self.protocol.avilla.launch_manager.get_component(MemcacheService).cache if result := await cache.get(f"satori/account({self.account.route['account']}).messages({target['message']})"): @@ -130,26 +128,27 @@ async def revoke_private_message(self, target: Selector): return await self.account.client.message_delete(channel_id=target["private"], message_id=target["message"]) - @m.pull("land.guild.channel.message", Message) - async def get_public_message(self, message: Selector, route: ...) -> Message: + @m.impl(CoreCapability.pull, "land.guild.channel.message", Message) + async def get_public_message(self, target: Selector) -> Message: msg = await self.account.client.message_get( - channel_id=message["channel"], - message_id=message["message"], + channel_id=target["channel"], + message_id=target["message"], ) _ctx = self.account.get_context( - message.info("::guild.channel").member( + target.info("::guild.channel").member( msg.member.user.id if msg.member and msg.member.user else self.account.route["account"] ) ) - content = await SatoriCapability(self.account.staff.ext({"context": _ctx})).deserialize(msg.content) + with CONTEXT_CONTEXT_VAR.use(_ctx): + content = await SatoriCapability.deserialize(msg.content) reply = None if replys := content.get(Reference): - reply = message.info(f"~.message({replys[0].message['message']})") + reply = target.info(f"~.message({replys[0].message['message']})") content = content.exclude(Reference) return Message( id=f"{msg.id}", - scene=message.info("::guild.channel"), - sender=message.info("::guild.channel").member( + scene=target.info("::guild.channel"), + sender=target.info("::guild.channel").member( msg.member.user.id if msg.member and msg.member.user else self.account.route["account"] ), content=content, @@ -157,22 +156,23 @@ async def get_public_message(self, message: Selector, route: ...) -> Message: reply=reply, ) - @m.pull("land.private.user.message", Message) - async def get_private_message(self, message: Selector, route: ...) -> Message: + @m.impl(CoreCapability.pull, "land.private.user.message", Message) + async def get_private_message(self, target: Selector) -> Message: msg = await self.account.client.message_get( - channel_id=message["private"], - message_id=message["message"], + channel_id=target["private"], + message_id=target["message"], ) - _ctx = self.account.get_context(message.info("::private.user")) - content = await SatoriCapability(self.account.staff.ext({"context": _ctx})).deserialize(msg.content) + _ctx = self.account.get_context(target.info("::private.user")) + with CONTEXT_CONTEXT_VAR.use(_ctx): + content = await SatoriCapability.deserialize(msg.content) reply = None if replys := content.get(Reference): - reply = message.info(f"~.message({replys[0].message['message']})") + reply = target.info(f"~.message({replys[0].message['message']})") content = content.exclude(Reference) return Message( id=f"{msg.id}", - scene=message.info("::private.user"), - sender=message.info("::private.user"), + scene=target.info("::private.user"), + sender=target.info("::private.user"), content=content, time=datetime.now(), reply=reply, diff --git a/avilla/satori/perform/action/request.py b/avilla/satori/perform/action/request.py index 3f8e1edb..bae81de4 100644 --- a/avilla/satori/perform/action/request.py +++ b/avilla/satori/perform/action/request.py @@ -1,21 +1,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from flywheel import scoped_collect -from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector -from avilla.standard.core.request import RequestCapability +from avilla.satori.bases import InstanceOfAccount +from avilla.standard.core.request import accept_request, reject_request -if TYPE_CHECKING: - from avilla.satori.account import SatoriAccount # noqa - from avilla.satori.protocol import SatoriProtocol # noqa +class SatoriRequestActionPerform(m := scoped_collect.env().target, InstanceOfAccount, static=True): -class SatoriRequestActionPerform((m := AccountCollector["SatoriProtocol", "SatoriAccount"]())._): - m.namespace = "avilla.protocol/satori::action" - m.identify = "request" - - @m.entity(RequestCapability.accept, target="land.guild.member.request") + @m.impl(accept_request, target="land.guild.member.request") async def accept_member_join_request(self, target: Selector) -> None: request_id = target.pattern["request"] await self.account.client.guild_approve( @@ -24,7 +18,7 @@ async def accept_member_join_request(self, target: Selector) -> None: comment="", ) - @m.entity(RequestCapability.reject, target="land.guild.member.request") + @m.impl(reject_request, target="land.guild.member.request") async def reject_member_join_request( self, target: Selector, reason: str | None = None, forever: bool = False ) -> None: @@ -35,7 +29,7 @@ async def reject_member_join_request( comment=reason or "", ) - @m.entity(RequestCapability.accept, target="land.user.request") + @m.impl(accept_request, target="land.user.request") async def accept_new_friend_request(self, target: Selector) -> None: request_id = target.pattern["request"] await self.account.client.friend_approve( @@ -44,7 +38,7 @@ async def accept_new_friend_request(self, target: Selector) -> None: comment="", ) - @m.entity(RequestCapability.reject, target="land.user.request") + @m.impl(reject_request, target="land.user.request") async def reject_new_friend_request( self, target: Selector, reason: str | None = None, forever: bool = False ) -> None: @@ -55,7 +49,7 @@ async def reject_new_friend_request( comment=reason or "", ) - @m.entity(RequestCapability.accept, target="land.guild.request") + @m.impl(accept_request, target="land.guild.request") async def accept_bot_invited_request(self, target: Selector) -> None: request_id = target.pattern["request"] await self.account.client.guild_approve( @@ -64,7 +58,7 @@ async def accept_bot_invited_request(self, target: Selector) -> None: comment="", ) - @m.entity(RequestCapability.reject, target="land.guild.request") + @m.impl(reject_request, target="land.guild.request") async def reject_bot_invited_request( self, target: Selector, reason: str | None = None, forever: bool = False ) -> None: diff --git a/avilla/satori/perform/context.py b/avilla/satori/perform/context.py index 702cc068..def38578 100644 --- a/avilla/satori/perform/context.py +++ b/avilla/satori/perform/context.py @@ -2,20 +2,21 @@ from typing import TYPE_CHECKING +from flywheel import scoped_collect + from avilla.core.builtins.capability import CoreCapability from avilla.core.context import Context -from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.core.selector import Selector +from avilla.satori.bases import InstanceOfAccount if TYPE_CHECKING: from avilla.satori.account import SatoriAccount # noqa from avilla.satori.protocol import SatoriProtocol # noqa -class SatoriContextPerform((m := AccountCollector["SatoriProtocol", "SatoriAccount"]())._): - m.namespace = "avilla.protocol/satori::context" +class SatoriContextPerform(m := scoped_collect.env().target, InstanceOfAccount, static=True): - @m.entity(CoreCapability.get_context, target="land.public.channel") + @m.impl(CoreCapability.get_context, target="land.guild.channel") def get_context_from_public(self, target: Selector, *, via: Selector | None = None): return Context( self.account, @@ -25,36 +26,36 @@ def get_context_from_public(self, target: Selector, *, via: Selector | None = No target.member(self.account.route["account"]), ) - @m.entity(CoreCapability.get_context, target="land.private.user") + @m.impl(CoreCapability.get_context, target="land.private.user") def get_context_from_user(self, target: Selector, *, via: Selector | None = None): return Context(self.account, target, self.account.route, target, self.account.route) - @m.entity(CoreCapability.get_context, target="land.public.channel.member") + @m.impl(CoreCapability.get_context, target="land.guild.channel.member") def get_context_from_member(self, target: Selector, *, via: Selector | None = None): return Context( self.account, target, - target.into("::public.channel"), - target.into("::public.channel"), + target.into("::guild.channel"), + target.into("::guild.channel"), target.into(f"~.member({self.account.route['account']})"), ) - @m.entity(CoreCapability.channel, target="land.private.user") + @m.impl(CoreCapability.channel, target="land.private.user") def channel_from_user(self, target: Selector): return target["private"] - @m.entity(CoreCapability.channel, target="land.public.channel") + @m.impl(CoreCapability.channel, target="land.guild.channel") def channel_from_channel(self, target: Selector): return target["channel"] - @m.entity(CoreCapability.guild, target="land.public.channel") + @m.impl(CoreCapability.guild, target="land.guild.channel") def guild_from_channel(self, target: Selector): - return target["public"] + return target["guild"] - @m.entity(CoreCapability.user, target="land.private.user") + @m.impl(CoreCapability.user, target="land.private.user") def user_from_user(self, target: Selector): return target["user"] - @m.entity(CoreCapability.user, target="land.public.channel.member") + @m.impl(CoreCapability.user, target="land.guild.channel.member") def user_from_member(self, target: Selector): return target["member"] diff --git a/avilla/satori/perform/event/activity.py b/avilla/satori/perform/event/activity.py index a518dda4..11064735 100644 --- a/avilla/satori/perform/event/activity.py +++ b/avilla/satori/perform/event/activity.py @@ -1,31 +1,23 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -from satori.model import ChannelType, Event +from flywheel import scoped_collect +from satori.model import ChannelType from avilla.core.context import Context from avilla.core.selector import Selector +from avilla.satori.bases import InstanceOfAccount from avilla.satori.capability import SatoriCapability -from avilla.satori.collector.connection import ConnectionCollector from avilla.satori.model import ButtonInteractionEvent from avilla.standard.core.activity import ActivityAvailable -class SatoriEventActivityPerform((m := ConnectionCollector())._): - m.namespace = "avilla.protocol/satori::event" - m.identify = "activity" - - @m.entity(SatoriCapability.event_callback, raw_event="interaction/button") - async def button_interaction(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] - if not raw_event.channel: - return - if TYPE_CHECKING: - assert isinstance(raw_event, ButtonInteractionEvent) - if raw_event.channel.type == ChannelType.DIRECT: - private = Selector().land(account.route["land"]).private(raw_event.channel.id) - user = private.user(raw_event.user.id) # type: ignore +class SatoriEventActivityPerform(m := scoped_collect.globals().target, InstanceOfAccount, static=True): + @m.impl(SatoriCapability.event_callback, raw_event="interaction/button") + async def button_interaction(self, event: ButtonInteractionEvent): + account = self.account + if event.channel.type == ChannelType.DIRECT: + private = Selector().land(account.route["land"]).private(event.channel.id) + user = private.user(event.user.id) # type: ignore context = Context( account, user, @@ -33,17 +25,11 @@ async def button_interaction(self, raw_event: Event): user, account.route, ) - activity = private.button(raw_event.button.id) # type: ignore + activity = private.button(event.button.id) # type: ignore else: - public = ( - Selector() - .land(account.route["land"]) - .public(raw_event.guild.id if raw_event.guild else raw_event.channel.id) - ) - channel = public.channel(raw_event.channel.id) - member = channel.member( - raw_event.member.user.id if raw_event.member and raw_event.member.user else raw_event.user.id - ) + public = Selector().land(account.route["land"]).public(event.guild.id if event.guild else event.channel.id) + channel = public.channel(event.channel.id) + member = channel.member(event.member.user.id if event.member and event.member.user else event.user.id) context = Context( account, member, @@ -51,5 +37,5 @@ async def button_interaction(self, raw_event: Event): channel, channel.member(account.route["account"]), ) - activity = channel.button(raw_event.button.id) # type: ignore + activity = channel.button(event.button.id) # type: ignore return ActivityAvailable(context, "button_interaction", context.scene, activity) diff --git a/avilla/satori/perform/event/lifespan.py b/avilla/satori/perform/event/lifespan.py index aba95606..9165bdc7 100644 --- a/avilla/satori/perform/event/lifespan.py +++ b/avilla/satori/perform/event/lifespan.py @@ -1,5 +1,6 @@ from __future__ import annotations +from flywheel import scoped_collect from loguru import logger from satori.account import Account from satori.model import Event, LoginStatus @@ -7,8 +8,8 @@ from avilla.core.account import AccountInfo from avilla.core.selector import Selector from avilla.satori.account import SatoriAccount +from avilla.satori.bases import InstanceOfConnection from avilla.satori.capability import SatoriCapability -from avilla.satori.collector.connection import ConnectionCollector from avilla.satori.const import platform from avilla.standard.core.account.event import ( AccountAvailable, @@ -18,15 +19,13 @@ ) -class SatoriEventLifespanPerform((m := ConnectionCollector())._): - m.namespace = "avilla.protocol/satori::event" - m.identify = "lifespan" +class SatoriEventLifespanPerform(m := scoped_collect.globals().target, InstanceOfConnection, static=True): - @m.entity(SatoriCapability.event_callback, raw_event="login-added") - async def connect(self, raw_event: Event): - self_id = raw_event.self_id - account = Account(raw_event.platform, self_id, self.connection.config) - route = Selector().land(raw_event.platform).account(self_id) + @m.impl(SatoriCapability.event_callback, raw_event="login-added") + async def connect(self, event: Event): + self_id = event.self_id + account = Account(event.platform, self_id, self.connection.config) + route = Selector().land(event.platform).account(self_id) _account = SatoriAccount(route=route, protocol=self.protocol) self.protocol.service.accounts[account.identity] = account @@ -39,32 +38,33 @@ async def connect(self, raw_event: Event): _account.client = self.connection return AccountRegistered(self.protocol.avilla, _account) - @m.entity(SatoriCapability.event_callback, raw_event="login-updated") - async def enable(self, raw_event: Event): - identity = f"{raw_event.platform}/{raw_event.self_id}" + @m.impl(SatoriCapability.event_callback, raw_event="login-updated") + async def enable(self, event: Event): + identity = f"{event.platform}/{event.self_id}" account = self.protocol.service.accounts.get(identity) if account is None: - logger.warning(f"Unknown account {identity} received enable event {raw_event}") - return + logger.warning(f"Unknown account {identity} received enable event {event}") + raise NotImplementedError _account = self.protocol.service._accounts[identity] - if _account.status.enabled and raw_event.login and raw_event.login.status != LoginStatus.ONLINE: + if _account.status.enabled and event.login and event.login.status != LoginStatus.ONLINE: _account.status.enabled = False logger.warning(f"Account {identity} disabled by remote") account.connected.clear() return AccountUnavailable(self.protocol.avilla, _account) - if not _account.status.enabled and raw_event.login and raw_event.login.status == LoginStatus.ONLINE: + if not _account.status.enabled and event.login and event.login.status == LoginStatus.ONLINE: _account.status.enabled = True account.connected.set() logger.warning(f"Account {identity} enabled by remote") return AccountAvailable(self.protocol.avilla, _account) + raise NotImplementedError - @m.entity(SatoriCapability.event_callback, raw_event="login-removed") - async def disable(self, raw_event: Event): - identity = f"{raw_event.platform}/{raw_event.self_id}" + @m.impl(SatoriCapability.event_callback, raw_event="login-removed") + async def disable(self, event: Event): + identity = f"{event.platform}/{event.self_id}" account = self.protocol.service.accounts.get(identity) if account is None: - logger.warning(f"Unknown account {identity} received disable event {raw_event}") - return + logger.warning(f"Unknown account {identity} received disable event {event}") + raise NotImplementedError _account = self.protocol.service._accounts[identity] _account.status.enabled = False logger.warning(f"Account {identity} disabled by remote") diff --git a/avilla/satori/perform/event/message.py b/avilla/satori/perform/event/message.py index 1769161c..81857c4f 100644 --- a/avilla/satori/perform/event/message.py +++ b/avilla/satori/perform/event/message.py @@ -1,35 +1,32 @@ from __future__ import annotations from datetime import timedelta -from typing import TYPE_CHECKING +from flywheel import scoped_collect from graia.amnesia.builtins.memcache import Memcache, MemcacheService -from satori.model import ChannelType, Event +from satori.model import ChannelType from avilla.core.context import Context from avilla.core.elements import Reference +from avilla.core.globals import CONTEXT_CONTEXT_VAR from avilla.core.message import Message from avilla.core.selector import Selector +from avilla.satori.bases import InstanceOfAccount from avilla.satori.capability import SatoriCapability -from avilla.satori.collector.connection import ConnectionCollector from avilla.satori.model import MessageEvent from avilla.standard.core.message import MessageReceived, MessageSent -class SatoriEventMessagePerform((m := ConnectionCollector())._): - m.namespace = "avilla.protocol/satori::event" - m.identify = "message" +class SatoriEventMessagePerform(m := scoped_collect.globals().target, InstanceOfAccount, static=True): - @m.entity(SatoriCapability.event_callback, raw_event="message-created") - async def message_create(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="message-created") + async def message_create(self, event: MessageEvent): + account = self.account cache: Memcache = self.protocol.avilla.launch_manager.get_component(MemcacheService).cache reply = None - if TYPE_CHECKING: - assert isinstance(raw_event, MessageEvent) - if raw_event.channel.type == ChannelType.DIRECT: - private = Selector().land(account.route["land"]).private(raw_event.channel.id) - user = private.user(raw_event.user.id) + if event.channel.type == ChannelType.DIRECT: + private = Selector().land(account.route["land"]).private(event.channel.id) + user = private.user(event.user.id) context = Context( account, user, @@ -37,26 +34,23 @@ async def message_create(self, raw_event: Event): user, account.route, ) - message = await SatoriCapability(account.staff.ext({"context": context})).deserialize( - raw_event.message.content - ) + with CONTEXT_CONTEXT_VAR.use(context): + message = await SatoriCapability.deserialize(event.message.content) if message.get(Reference): reply = message.get_first(Reference).message message = message.exclude(Reference) msg = Message( - id=f"{raw_event.message.id}", + id=f"{event.message.id}", scene=private, sender=private, content=message, - time=raw_event.timestamp, + time=event.timestamp, reply=reply, ) else: - guild = Selector().land(account.route["land"]).guild(raw_event.guild.id if raw_event.guild else "True") - channel = guild.channel(raw_event.channel.id) - member = channel.member( - raw_event.member.user.id if raw_event.member and raw_event.member.user else raw_event.user.id - ) + guild = Selector().land(account.route["land"]).guild(event.guild.id if event.guild else "True") + channel = guild.channel(event.channel.id) + member = channel.member(event.member.user.id if event.member and event.member.user else event.user.id) context = Context( account, member, @@ -64,26 +58,23 @@ async def message_create(self, raw_event: Event): channel, channel.member(account.route["account"]), ) - message = await SatoriCapability(account.staff.ext({"context": context})).deserialize( - raw_event.message.content - ) + with CONTEXT_CONTEXT_VAR.use(context): + message = await SatoriCapability.deserialize(event.message.content) if message.get(Reference): reply = message.get_first(Reference).message message = message.exclude(Reference) msg = Message( - id=f"{raw_event.message.id}", + id=f"{event.message.id}", scene=channel, sender=member, content=message, - time=raw_event.timestamp, + time=event.timestamp, reply=reply, ) - await cache.set( - f"satori/account({account.route['account']}).message({msg.id})", raw_event, timedelta(minutes=5) - ) + await cache.set(f"satori/account({account.route['account']}).message({msg.id})", event, timedelta(minutes=5)) context._collect_metadatas(msg.to_selector(), msg) return ( MessageSent(context, msg, account) - if msg.sender.last_value == raw_event.self_id + if msg.sender.last_value == event.self_id else MessageReceived(context, msg) ) diff --git a/avilla/satori/perform/event/metadata.py b/avilla/satori/perform/event/metadata.py index f55a8b8d..e0281fe9 100644 --- a/avilla/satori/perform/event/metadata.py +++ b/avilla/satori/perform/event/metadata.py @@ -1,28 +1,24 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from flywheel import scoped_collect + from avilla.core.context import Context from avilla.core.event import MetadataModified, ModifyDetail from avilla.core.selector import Selector +from avilla.satori.bases import InstanceOfAccount from avilla.satori.capability import SatoriCapability -from avilla.satori.collector.connection import ConnectionCollector from avilla.satori.model import GuildEvent, GuildMemberEvent, GuildRoleEvent -from avilla.standard.core.profile import Summary, Avatar, Nick -from satori.model import Event +from avilla.standard.core.profile import Avatar, Nick, Summary -class SatoriEventMetadataPerform((m := ConnectionCollector())._): - m.namespace = "avilla.protocol/satori::event" - m.identify = "metadata" +class SatoriEventMetadataPerform(m := scoped_collect.globals().target, InstanceOfAccount, static=True): - @m.entity(SatoriCapability.event_callback, raw_event="guild-updated") - async def guild_updated(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="guild-updated") + async def guild_updated(self, event: GuildEvent): + account = self.account land = Selector().land(account.route["land"]) - if TYPE_CHECKING: - assert isinstance(raw_event, GuildEvent) - guild = land.guild(raw_event.guild.id) - operator = guild.member(raw_event.operator.id) if raw_event.operator else guild + guild = land.guild(event.guild.id) + operator = guild.member(event.operator.id) if event.operator else guild context = Context( account, operator, @@ -36,7 +32,7 @@ async def guild_updated(self, raw_event: Event): guild, Summary, { - Summary.inh().name: ModifyDetail("set", raw_event.guild.name, None), + Summary.inh().name: ModifyDetail("set", event.guild.name, None), }, ), MetadataModified( @@ -44,8 +40,8 @@ async def guild_updated(self, raw_event: Event): guild, Nick, { - Nick.inh().name: ModifyDetail("set", raw_event.guild.name, None), - Nick.inh().nickname: ModifyDetail("set", raw_event.guild.name, None), + Nick.inh().name: ModifyDetail("set", event.guild.name, None), + Nick.inh().nickname: ModifyDetail("set", event.guild.name, None), }, ), MetadataModified( @@ -53,20 +49,18 @@ async def guild_updated(self, raw_event: Event): guild, Avatar, { - Avatar.inh().url: ModifyDetail("set", raw_event.guild.avatar, None), + Avatar.inh().url: ModifyDetail("set", event.guild.avatar, None), }, ), ] - @m.entity(SatoriCapability.event_callback, raw_event="guild-member-updated") - async def guild_member_updated(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="guild-member-updated") + async def guild_member_updated(self, event: GuildMemberEvent): + account = self.account land = Selector().land(account.route["land"]) - if TYPE_CHECKING: - assert isinstance(raw_event, GuildMemberEvent) - guild = land.guild(raw_event.guild.id) - member = guild.member(raw_event.member.user.id if raw_event.member.user else raw_event.user.id) - operator = guild.member(raw_event.operator.id) if raw_event.operator else member + guild = land.guild(event.guild.id) + member = guild.member(event.member.user.id if event.member.user else event.user.id) + operator = guild.member(event.operator.id) if event.operator else member context = Context( account, operator, @@ -80,7 +74,7 @@ async def guild_member_updated(self, raw_event: Event): member, Summary, { - Summary.inh().name: ModifyDetail("set", raw_event.member.nick, None), + Summary.inh().name: ModifyDetail("set", event.member.nick, None), }, ), MetadataModified( @@ -88,8 +82,8 @@ async def guild_member_updated(self, raw_event: Event): member, Nick, { - Nick.inh().name: ModifyDetail("set", raw_event.user.name, None), - Nick.inh().nickname: ModifyDetail("set", raw_event.member.nick, None), + Nick.inh().name: ModifyDetail("set", event.user.name, None), + Nick.inh().nickname: ModifyDetail("set", event.member.nick, None), }, ), MetadataModified( @@ -97,20 +91,18 @@ async def guild_member_updated(self, raw_event: Event): member, Avatar, { - Avatar.inh().url: ModifyDetail("set", raw_event.member.avatar, None), + Avatar.inh().url: ModifyDetail("set", event.member.avatar, None), }, ), ] - @m.entity(SatoriCapability.event_callback, raw_event="guild-role-updated") - async def guild_role_updated(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="guild-role-updated") + async def guild_role_updated(self, event: GuildRoleEvent): + account = self.account land = Selector().land(account.route["land"]) - if TYPE_CHECKING: - assert isinstance(raw_event, GuildRoleEvent) - guild = land.guild(raw_event.guild.id) - role = guild.role(raw_event.role.id) - operator = guild.member(raw_event.operator.id) if raw_event.operator else guild + guild = land.guild(event.guild.id) + role = guild.role(event.role.id) + operator = guild.member(event.operator.id) if event.operator else guild context = Context( account, operator, @@ -124,7 +116,7 @@ async def guild_role_updated(self, raw_event: Event): role, Summary, { - Summary.inh().name: ModifyDetail("set", raw_event.role.name, None), + Summary.inh().name: ModifyDetail("set", event.role.name, None), }, ), MetadataModified( @@ -132,8 +124,8 @@ async def guild_role_updated(self, raw_event: Event): role, Nick, { - Nick.inh().name: ModifyDetail("set", raw_event.role.name, None), - Nick.inh().nickname: ModifyDetail("set", raw_event.role.name, None), + Nick.inh().name: ModifyDetail("set", event.role.name, None), + Nick.inh().nickname: ModifyDetail("set", event.role.name, None), }, ), ] diff --git a/avilla/satori/perform/event/relationship.py b/avilla/satori/perform/event/relationship.py index 8afdcab9..245e44ec 100644 --- a/avilla/satori/perform/event/relationship.py +++ b/avilla/satori/perform/event/relationship.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from flywheel import scoped_collect + from avilla.core.context import Context from avilla.core.event import ( MemberCreated, @@ -9,25 +10,20 @@ SceneDestroyed, ) from avilla.core.selector import Selector +from avilla.satori.bases import InstanceOfAccount from avilla.satori.capability import SatoriCapability -from avilla.satori.collector.connection import ConnectionCollector -from avilla.satori.model import GuildEvent, GuildMemberEvent, GuildRoleEvent from avilla.satori.event import RoleCreated, RoleDestroyed -from satori.model import Event +from avilla.satori.model import GuildEvent, GuildMemberEvent, GuildRoleEvent -class SatoriEventRelationshipPerform((m := ConnectionCollector())._): - m.namespace = "avilla.protocol/satori::event" - m.identify = "relationship" +class SatoriEventRelationshipPerform(m := scoped_collect.globals().target, InstanceOfAccount, static=True): - @m.entity(SatoriCapability.event_callback, raw_event="guild-added") - async def guild_added(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="guild-added") + async def guild_added(self, event: GuildEvent): + account = self.account land = Selector().land(account.route["land"]) - if TYPE_CHECKING: - assert isinstance(raw_event, GuildEvent) - guild = land.guild(raw_event.guild.id) - inviter = guild.member(raw_event.operator.id) if raw_event.operator else guild + guild = land.guild(event.guild.id) + inviter = guild.member(event.operator.id) if event.operator else guild context = Context( account, inviter, @@ -37,14 +33,12 @@ async def guild_added(self, raw_event: Event): ) return SceneCreated(context) - @m.entity(SatoriCapability.event_callback, raw_event="guild-removed") - async def guild_removed(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="guild-removed") + async def guild_removed(self, event: GuildEvent): + account = self.account land = Selector().land(account.route["land"]) - if TYPE_CHECKING: - assert isinstance(raw_event, GuildEvent) - guild: Selector = land.guild(raw_event.guild.id) - operator = guild.member(raw_event.operator.id) if raw_event.operator else guild + guild: Selector = land.guild(event.guild.id) + operator = guild.member(event.operator.id) if event.operator else guild context = Context( account, operator, @@ -54,19 +48,17 @@ async def guild_removed(self, raw_event: Event): ) return SceneDestroyed( context, - active=bool(raw_event.operator) and raw_event.operator.id == account.route["account"], - indirect=not bool(raw_event.operator), + active=bool(event.operator) and event.operator.id == account.route["account"], + indirect=not bool(event.operator), ) - @m.entity(SatoriCapability.event_callback, raw_event="guild-member-added") - async def guild_member_added(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="guild-member-added") + async def guild_member_added(self, event: GuildMemberEvent): + account = self.account land = Selector().land(account.route["land"]) - if TYPE_CHECKING: - assert isinstance(raw_event, GuildMemberEvent) - guild = land.guild(raw_event.guild.id) - member = guild.member(raw_event.member.user.id if raw_event.member.user else raw_event.user.id) - operator = guild.member(raw_event.operator.id) if raw_event.operator else member + guild = land.guild(event.guild.id) + member = guild.member(event.member.user.id if event.member.user else event.user.id) + operator = guild.member(event.operator.id) if event.operator else member context = Context( account, operator, @@ -76,15 +68,13 @@ async def guild_member_added(self, raw_event: Event): ) return MemberCreated(context) - @m.entity(SatoriCapability.event_callback, event_type="guild-member-removed") - async def guild_member_removed(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="guild-member-removed") + async def guild_member_removed(self, event: GuildMemberEvent): + account = self.account land = Selector().land(account.route["land"]) - if TYPE_CHECKING: - assert isinstance(raw_event, GuildMemberEvent) - guild = land.guild(raw_event.guild.id) - member = guild.member(raw_event.member.user.id if raw_event.member.user else raw_event.user.id) - operator = guild.member(raw_event.operator.id) if raw_event.operator else member + guild = land.guild(event.guild.id) + member = guild.member(event.member.user.id if event.member.user else event.user.id) + operator = guild.member(event.operator.id) if event.operator else member context = Context( account, operator, @@ -94,19 +84,17 @@ async def guild_member_removed(self, raw_event: Event): ) return MemberDestroyed( context, - active=bool(raw_event.operator) and raw_event.operator.id == member["member"], - indirect=not bool(raw_event.operator), + active=bool(event.operator) and event.operator.id == member["member"], + indirect=not bool(event.operator), ) - @m.entity(SatoriCapability.event_callback, raw_event="guild-role-created") - async def guild_role_created(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="guild-role-created") + async def guild_role_created(self, event: GuildRoleEvent): + account = self.account land = Selector().land(account.route["land"]) - if TYPE_CHECKING: - assert isinstance(raw_event, GuildRoleEvent) - guild = land.guild(raw_event.guild.id) - role = guild.role(raw_event.role.id) - operator = guild.member(raw_event.operator.id) if raw_event.operator else guild + guild = land.guild(event.guild.id) + role = guild.role(event.role.id) + operator = guild.member(event.operator.id) if event.operator else guild context = Context( account, operator, @@ -116,15 +104,13 @@ async def guild_role_created(self, raw_event: Event): ) return RoleCreated(context) - @m.entity(SatoriCapability.event_callback, event_type="guild-role-deleted") - async def guild_role_deleted(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="guild-role-deleted") + async def guild_role_deleted(self, event: GuildRoleEvent): + account = self.account land = Selector().land(account.route["land"]) - if TYPE_CHECKING: - assert isinstance(raw_event, GuildRoleEvent) - guild = land.guild(raw_event.guild.id) - role = guild.role(raw_event.role.id) - operator = guild.member(raw_event.operator.id) if raw_event.operator else guild + guild = land.guild(event.guild.id) + role = guild.role(event.role.id) + operator = guild.member(event.operator.id) if event.operator else guild context = Context( account, operator, @@ -134,6 +120,6 @@ async def guild_role_deleted(self, raw_event: Event): ) return RoleDestroyed( context, - active=bool(raw_event.operator) and raw_event.operator.id == account.route["account"], - indirect=not bool(raw_event.operator), + active=bool(event.operator) and event.operator.id == account.route["account"], + indirect=not bool(event.operator), ) diff --git a/avilla/satori/perform/event/request.py b/avilla/satori/perform/event/request.py index d13d665b..2d770959 100644 --- a/avilla/satori/perform/event/request.py +++ b/avilla/satori/perform/event/request.py @@ -1,70 +1,63 @@ from __future__ import annotations from datetime import datetime -from typing import TYPE_CHECKING + +from flywheel import scoped_collect from avilla.core.context import Context from avilla.core.request import Request from avilla.core.selector import Selector -from avilla.satori.const import land as LAND +from avilla.satori.bases import InstanceOfAccount from avilla.satori.capability import SatoriCapability -from avilla.satori.collector.connection import ConnectionCollector -from avilla.satori.model import GuildMemberEvent, DirectEvent, GuildEvent - +from avilla.satori.const import land as LAND +from avilla.satori.model import DirectEvent, GuildEvent, GuildMemberEvent from avilla.standard.core.profile.metadata import Nick, Summary from avilla.standard.core.request import RequestReceived -from satori.model import Event -class SatoriEventRequestPerform((m := ConnectionCollector())._): - m.namespace = "avilla.protocol/satori::event" - m.identify = "request" +class SatoriEventRequestPerform(m := scoped_collect.globals().target, InstanceOfAccount, static=True): - @m.entity(SatoriCapability.event_callback, raw_event="guild-member-request") - async def member_join_request(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="guild-member-request") + async def member_join_request(self, event: GuildMemberEvent): + account = self.account land = Selector().land(account.route["land"]) - if TYPE_CHECKING: - assert isinstance(raw_event, GuildMemberEvent) - guild = land.guild(raw_event.guild.id) - sender = land.user(raw_event.user.id) + guild = land.guild(event.guild.id) + sender = land.user(event.user.id) context = Context( account, sender, guild, guild, guild.member(account.route["account"]), - mediums=[guild.member(raw_event.operator.id)] if raw_event.operator else None, + mediums=[guild.member(event.operator.id)] if event.operator else None, ) request = Request( - f"{raw_event.message.id if raw_event.message else raw_event.id}", + f"{event.message.id if event.message else event.id}", LAND(account.route["land"]), guild, sender, account, datetime.now(), request_type="satori::guild-member-request", - message=raw_event.message.content if raw_event.message else None, + message=event.message.content if event.message else None, ) context._collect_metadatas( guild, - Nick(raw_event.guild.name, raw_event.guild.name, None), # type: ignore - Summary(raw_event.guild.name, None), # type: ignore + Nick(event.guild.name, event.guild.name, None), # type: ignore + Summary(event.guild.name, None), # type: ignore ) context._collect_metadatas( sender, - Nick(raw_event.user.name, raw_event.member.nick or raw_event.user.name, None), # type: ignore - Summary(raw_event.user.name, None), # type: ignore + Nick(event.user.name, event.member.nick or event.user.name, None), # type: ignore + Summary(event.user.name, None), # type: ignore ) return RequestReceived(context, request) - @m.entity(SatoriCapability.event_callback, raw_event="friend-request") - async def new_friend_request(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="friend-request") + async def new_friend_request(self, event: DirectEvent): + account = self.account land = Selector().land(account.route["land"]) - if TYPE_CHECKING: - assert isinstance(raw_event, DirectEvent) - sender = land.user(raw_event.user.id) + sender = land.user(event.user.id) context = Context( account, sender, @@ -73,30 +66,28 @@ async def new_friend_request(self, raw_event: Event): account.route, ) request = Request( - f"{raw_event.message.id if raw_event.message else raw_event.id}", + f"{event.message.id if event.message else event.id}", LAND(account.route["land"]), sender, sender, account, datetime.now(), request_type="satori::friend-request", - message=raw_event.message.content if raw_event.message else None, + message=event.message.content if event.message else None, ) context._collect_metadatas( sender, - Nick(raw_event.user.name, raw_event.user.name, None), # type: ignore - Summary(raw_event.user.name, None), # type: ignore + Nick(event.user.name, event.user.name, None), # type: ignore + Summary(event.user.name, None), # type: ignore ) return RequestReceived(context, request) - @m.entity(SatoriCapability.event_callback, raw_event="guild-request") - async def bot_invited_join_group_request(self, raw_event: Event): - account = self.protocol.service._accounts[self.connection.identity] + @m.impl(SatoriCapability.event_callback, raw_event="guild-request") + async def bot_invited_join_group_request(self, event: GuildEvent): + account = self.account land = Selector().land(account.route["land"]) - if TYPE_CHECKING: - assert isinstance(raw_event, GuildEvent) - guild = land.guild(raw_event.guild.id) - operator = guild.member(raw_event.operator.id) if raw_event.operator else guild + guild = land.guild(event.guild.id) + operator = guild.member(event.operator.id) if event.operator else guild context = Context( account, operator, @@ -105,24 +96,24 @@ async def bot_invited_join_group_request(self, raw_event: Event): account.route, ) request = Request( - f"{raw_event.message.id if raw_event.message else raw_event.id}", + f"{event.message.id if event.message else event.id}", LAND(account.route["land"]), operator, operator, account, datetime.now(), request_type="satori::guild-request", - message=raw_event.message.content if raw_event.message else None, + message=event.message.content if event.message else None, ) context._collect_metadatas( guild, - Nick(raw_event.guild.name, raw_event.guild.name, None), # type: ignore - Summary(raw_event.guild.name, None), # type: ignore + Nick(event.guild.name, event.guild.name, None), # type: ignore + Summary(event.guild.name, None), # type: ignore ) - if raw_event.operator: + if event.operator: context._collect_metadatas( operator, - Nick(raw_event.operator.name, raw_event.operator.name, None), # type: ignore - Summary(raw_event.operator.name, None), # type: ignore + Nick(event.operator.name, event.operator.name, None), # type: ignore + Summary(event.operator.name, None), # type: ignore ) return RequestReceived(context, request) diff --git a/avilla/satori/perform/message/deserialize.py b/avilla/satori/perform/message/deserialize.py index 18204e3f..12166049 100644 --- a/avilla/satori/perform/message/deserialize.py +++ b/avilla/satori/perform/message/deserialize.py @@ -1,8 +1,9 @@ from __future__ import annotations +from contextlib import suppress from dataclasses import asdict -from typing import TYPE_CHECKING +from flywheel import global_collect from satori.element import At from satori.element import Audio as SatoriAudio from satori.element import Bold, Br @@ -25,6 +26,7 @@ from satori.element import Underline from satori.element import Video as SatoriVideo +from avilla.core.context import Context from avilla.core.elements import ( Audio, File, @@ -35,7 +37,6 @@ Text, Video, ) -from avilla.core.ryanvk_old.collector.application import ApplicationCollector from avilla.core.selector import Selector from avilla.satori.capability import SatoriCapability from avilla.satori.element import Button @@ -45,117 +46,156 @@ SatoriImageResource, SatoriVideoResource, ) -from graia.ryanvk import OptionalAccess - -if TYPE_CHECKING: - from avilla.core.context import Context - from avilla.satori.account import SatoriAccount - - -class SatoriMessageDeserializePerform((m := ApplicationCollector())._): - m.namespace = "avilla.protocol/satori::message" - m.identify = "deserialize" - - context: OptionalAccess[Context] = OptionalAccess() - account: OptionalAccess[SatoriAccount] = OptionalAccess() - - # LINK: https://github.com/microsoft/pyright/issues/5409 - - @m.entity(SatoriCapability.deserialize_element, raw_element=SatoriText) - async def text(self, raw_element: SatoriText) -> Text: - return Text(raw_element.text) - - @m.entity(SatoriCapability.deserialize_element, raw_element=At) - async def at(self, raw_element: At) -> Notice | NoticeAll: - if raw_element.type in ("all", "here"): - return NoticeAll() - scene = self.context.scene if self.context else Selector().land("satori") - if raw_element.role: - return Notice(scene.role(raw_element.role)) - return Notice(scene.member(raw_element.id)) # type: ignore - - @m.entity(SatoriCapability.deserialize_element, raw_element=Sharp) - async def sharp(self, raw_element: Sharp) -> Notice: - scene = self.context.scene if self.context else Selector().land("satori") - return Notice(scene.into(f"~.channel({raw_element.id})")) # type: ignore - - @m.entity(SatoriCapability.deserialize_element, raw_element=Link) - async def a(self, raw_element: Link) -> Text: - return Text(raw_element.url, style="link") - - @m.entity(SatoriCapability.deserialize_element, raw_element=Image) - async def img(self, raw_element: Image) -> Picture: - scene = self.context.scene if self.context else Selector().land("satori") - res = SatoriImageResource(**asdict(raw_element)) - res.selector = scene.picture(raw_element.src) - return Picture(res) - - @m.entity(SatoriCapability.deserialize_element, raw_element=SatoriVideo) - async def video(self, raw_element: SatoriVideo) -> Video: - scene = self.context.scene if self.context else Selector().land("satori") - res = SatoriVideoResource(**asdict(raw_element)) - res.selector = scene.video(raw_element.src) - return Video(res) - - @m.entity(SatoriCapability.deserialize_element, raw_element=SatoriAudio) - async def audio(self, raw_element: SatoriAudio) -> Audio: - scene = self.context.scene if self.context else Selector().land("satori") - res = SatoriAudioResource(**asdict(raw_element)) - res.selector = scene.video(raw_element.src) - return Audio(res) - - @m.entity(SatoriCapability.deserialize_element, raw_element=SatoriFile) - async def file(self, raw_element: SatoriFile) -> File: - scene = self.context.scene if self.context else Selector().land("satori") - res = SatoriFileResource(**asdict(raw_element)) - res.selector = scene.video(raw_element.src) - return File(res) - - @m.entity(SatoriCapability.deserialize_element, raw_element=Quote) - async def quote(self, raw_element: Quote) -> Reference: - scene = self.context.scene if self.context else Selector().land("satori") - return Reference(scene.message(raw_element.id)) # type: ignore - - @m.entity(SatoriCapability.deserialize_element, raw_element=Bold) - async def bold(self, raw_element: Bold) -> Text: - return Text(raw_element.dumps(True), style="bold") - - @m.entity(SatoriCapability.deserialize_element, raw_element=Italic) - async def italic(self, raw_element: Italic) -> Text: - return Text(raw_element.dumps(True), style="italic") - - @m.entity(SatoriCapability.deserialize_element, raw_element=Strikethrough) - async def strikethrough(self, raw_element: Strikethrough) -> Text: - return Text(raw_element.dumps(True), style="strikethrough") - - @m.entity(SatoriCapability.deserialize_element, raw_element=Underline) - async def underline(self, raw_element: Underline) -> Text: - return Text(raw_element.dumps(True), style="underline") - - @m.entity(SatoriCapability.deserialize_element, raw_element=Spoiler) - async def spoiler(self, raw_element: Spoiler) -> Text: - return Text(raw_element.dumps(True), style="spoiler") - - @m.entity(SatoriCapability.deserialize_element, raw_element=Code) - async def code(self, raw_element: Code) -> Text: - return Text(raw_element.dumps(True), style="code") - - @m.entity(SatoriCapability.deserialize_element, raw_element=Superscript) - async def superscript(self, raw_element: Superscript) -> Text: - return Text(raw_element.dumps(True), style="superscript") - - @m.entity(SatoriCapability.deserialize_element, raw_element=Subscript) - async def subscript(self, raw_element: Subscript) -> Text: - return Text(raw_element.dumps(True), style="subscript") - - @m.entity(SatoriCapability.deserialize_element, raw_element=Br) - async def br(self, raw_element: Br) -> Text: - return Text("\n", style="br") - - @m.entity(SatoriCapability.deserialize_element, raw_element=Paragraph) - async def paragraph(self, raw_element: Paragraph) -> Text: - return Text(raw_element.dumps(True), style="paragraph") - - @m.entity(SatoriCapability.deserialize_element, raw_element=SatoriButton) - async def button(self, raw_element: SatoriButton) -> Button: - return Button(**asdict(raw_element)) + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=SatoriText) +async def text(element: SatoriText) -> Text: + return Text(element.text) + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=At) +async def at(element: At) -> Notice | NoticeAll: + if element.type in ("all", "here"): + return NoticeAll() + scene = Selector().land("satori") + with suppress(LookupError): + scene = Context.current.scene + if element.role: + return Notice(scene.role(element.role)) + return Notice(scene.member(element.id)) # type: ignore + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Sharp) +async def sharp(element: Sharp) -> Notice: + scene = Selector().land("satori") + with suppress(LookupError): + scene = Context.current.scene + return Notice(scene.into(f"~.channel({element.id})")) # type: ignore + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Link) +async def a(element: Link) -> Text: + return Text(element.url, style="link") + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Image) +async def img(element: Image) -> Picture: + scene = Selector().land("satori") + with suppress(LookupError): + scene = Context.current.scene + res = SatoriImageResource(**asdict(element)) + res.selector = scene.picture(element.src) + return Picture(res) + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=SatoriVideo) +async def video(element: SatoriVideo) -> Video: + scene = Selector().land("satori") + with suppress(LookupError): + scene = Context.current.scene + res = SatoriVideoResource(**asdict(element)) + res.selector = scene.video(element.src) + return Video(res) + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=SatoriAudio) +async def audio(element: SatoriAudio) -> Audio: + scene = Selector().land("satori") + with suppress(LookupError): + scene = Context.current.scene + res = SatoriAudioResource(**asdict(element)) + res.selector = scene.video(element.src) + return Audio(res) + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=SatoriFile) +async def file(element: SatoriFile) -> File: + scene = Selector().land("satori") + with suppress(LookupError): + scene = Context.current.scene + res = SatoriFileResource(**asdict(element)) + res.selector = scene.video(element.src) + return File(res) + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Quote) +async def quote(element: Quote) -> Reference: + scene = Selector().land("satori") + with suppress(LookupError): + scene = Context.current.scene + return Reference(scene.message(element.id)) # type: ignore + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Bold) +async def bold(element: Bold) -> Text: + return Text(element.dumps(True), style="bold") + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Italic) +async def italic(element: Italic) -> Text: + return Text(element.dumps(True), style="italic") + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Strikethrough) +async def strikethrough(element: Strikethrough) -> Text: + return Text(element.dumps(True), style="strikethrough") + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Underline) +async def underline(element: Underline) -> Text: + return Text(element.dumps(True), style="underline") + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Spoiler) +async def spoiler(element: Spoiler) -> Text: + return Text(element.dumps(True), style="spoiler") + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Code) +async def code(element: Code) -> Text: + return Text(element.dumps(True), style="code") + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Superscript) +async def superscript(element: Superscript) -> Text: + return Text(element.dumps(True), style="superscript") + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Subscript) +async def subscript(element: Subscript) -> Text: + return Text(element.dumps(True), style="subscript") + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Br) +async def br(element: Br) -> Text: + return Text("\n", style="br") + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=Paragraph) +async def paragraph(element: Paragraph) -> Text: + return Text(element.dumps(True), style="paragraph") + + +@global_collect +@SatoriCapability.deserialize_element.impl(element=SatoriButton) +async def button(element: SatoriButton) -> Button: + return Button(**asdict(element)) diff --git a/avilla/satori/perform/message/serialize.py b/avilla/satori/perform/message/serialize.py index 16842388..d3b9d278 100644 --- a/avilla/satori/perform/message/serialize.py +++ b/avilla/satori/perform/message/serialize.py @@ -1,101 +1,104 @@ from __future__ import annotations -from typing import TYPE_CHECKING - +from flywheel import global_collect from satori.parser import escape from avilla.core.elements import Audio, File, Notice, NoticeAll, Picture, Text, Video -from avilla.core.ryanvk_old.collector.account import AccountCollector from avilla.satori.capability import SatoriCapability from avilla.satori.element import Button from avilla.satori.resource import SatoriResource -if TYPE_CHECKING: - from avilla.satori.account import SatoriAccount # noqa - from avilla.satori.protocol import SatoriProtocol # noqa - - -class SatoriMessageSerializePerform((m := AccountCollector["SatoriProtocol", "SatoriAccount"]())._): - m.namespace = "avilla.protocol/satori::message" - m.identify = "serialize" - - # LINK: https://github.com/microsoft/pyright/issues/5409 - - @m.entity(SatoriCapability.serialize_element, element=Text) - async def text(self, element: Text) -> str: - text = escape(element.text) - text.replace("\n", "
") - if not element.style: - return text - style = element.style - if style in {"a", "link"}: - return f'' - if style == { - "b", - "strong", - "bold", - "i", - "em", - "italic", - "u", - "ins", - "underline", - "s", - "del", - "strike", - "spl", - "spoiler", - "code", - "sup", - "sub", - "superscript", - "subscript", - "p", - "paragraph", - }: - return f"<{style}>{text}" + +@global_collect +@SatoriCapability.serialize_element.impl(element=Text) +async def text(element: Text) -> str: + text = escape(element.text) + text.replace("\n", "
") + if not element.style: return text + style = element.style + if style in {"a", "link"}: + return f'
' + if style == { + "b", + "strong", + "bold", + "i", + "em", + "italic", + "u", + "ins", + "underline", + "s", + "del", + "strike", + "spl", + "spoiler", + "code", + "sup", + "sub", + "superscript", + "subscript", + "p", + "paragraph", + }: + return f"<{style}>{text}" + return text + + +@global_collect +@SatoriCapability.serialize_element.impl(element=Notice) +async def notice(element: Notice) -> str: + if "role" in element.target.pattern: + return f'' + if "channel" in element.target.pattern: + return f'' + return f'' + + +@global_collect +@SatoriCapability.serialize_element.impl(element=NoticeAll) +async def notice_all(element: NoticeAll) -> str: + return '' + + +@global_collect +@SatoriCapability.serialize_element.impl(element=Picture) +async def picture(element: Picture) -> str: + res = element.resource + if not isinstance(res, SatoriResource): + raise NotImplementedError("Only SatoriResource is supported.") + return f'' + + +@global_collect +@SatoriCapability.serialize_element.impl(element=Audio) +async def audio(element: Audio) -> str: + res = element.resource + if not isinstance(res, SatoriResource): + raise NotImplementedError("Only SatoriResource is supported.") + return f'