diff --git a/.github/actions/setup-python/action.yml b/.github/actions/setup-python/action.yml index ebf8f36..db791df 100644 --- a/.github/actions/setup-python/action.yml +++ b/.github/actions/setup-python/action.yml @@ -5,7 +5,7 @@ inputs: python-version: description: Python version required: false - default: "3.10" + default: "3.12" runs: using: "composite" diff --git a/arclet/entari/__init__.py b/arclet/entari/__init__.py index 5ddf75c..f6745f4 100644 --- a/arclet/entari/__init__.py +++ b/arclet/entari/__init__.py @@ -55,6 +55,8 @@ from .event import BaseEvent as BaseEvent from .event import attr as attr from .event import register_internal_event as register_internal_event +from .event.api import SendRequest as SendRequest +from .event.api import SendResponse as SendResponse from .event.base import MessageCreatedEvent as MessageCreatedEvent from .event.base import MessageEvent as MessageEvent from .event.base import Reply as Reply @@ -63,11 +65,10 @@ from .event.lifespan import Cleanup as Cleanup from .event.lifespan import Ready as Ready from .event.lifespan import Startup as Startup -from .event.send import SendRequest as SendRequest -from .event.send import SendResponse as SendResponse from .filter import filter_ as filter_ from .localdata import local_data as local_data from .message import MessageChain as MessageChain +from .plugin import ROOT as ROOT from .plugin import Plugin as Plugin from .plugin import PluginMetadata as PluginMetadata from .plugin import add_service as add_service @@ -80,15 +81,16 @@ from .plugin import load_plugin as load_plugin from .plugin import load_plugins as load_plugins from .plugin import metadata as metadata +from .plugin import niche as niche from .plugin import package as package from .plugin import plugin_config as plugin_config +from .plugin import register_schema as register_schema from .plugin import requires as requires from .plugin import unload_plugin as unload_plugin -from .plugin.model import inject as inject from .session import Session as Session Param = param WS = WebsocketsInfo WH = WebhookInfo -__version__ = "0.18.5" +__version__ = "0.19.0rc3" diff --git a/arclet/entari/builtins/auto_reload.py b/arclet/entari/builtins/auto_reload.py index 7f99b03..752af12 100644 --- a/arclet/entari/builtins/auto_reload.py +++ b/arclet/entari/builtins/auto_reload.py @@ -1,6 +1,11 @@ +import ast import asyncio +import importlib +import sys from dataclasses import asdict from pathlib import Path +from traceback import format_exception_only +from types import ModuleType from arclet.letoderea import post, publish from launart import Launart, Service, any_completed @@ -16,7 +21,18 @@ from arclet.entari.config import BasicConfModel, EntariConfig, model_field from arclet.entari.event.config import ConfigReload from arclet.entari.logger import log -from arclet.entari.plugin import PluginRole, find_plugin, find_plugin_by_file, unload_plugin_async +from arclet.entari.plugin import ( + Plugin, + PluginRole, + find_plugin, + find_plugin_by_file, + plugin_service, + reload_plugin, + reload_subplugin, + unload_plugin_async, +) +from arclet.entari.plugin.swap import classify, swap_functions +from arclet.entari.utils import escape_tag # declare_static() loguru_logger.disable("watchfiles.main") @@ -67,6 +83,29 @@ class Config(BasicConfModel): logger = log.wrapper("[AutoReload]").opt(colors=True) +def module_name_from_path(path: Path) -> str | None: + path = Path(path).resolve() + + for entry in map(Path, sys.path): + try: + relative = path.relative_to(entry.resolve()) + except ValueError: + continue + + if relative.suffix == ".py": + relative = relative.with_suffix("") + + parts = list(relative.parts) + + # __init__.py 对应包本身 + if parts[-1] == "__init__": + parts.pop() + + return ".".join(parts) + + return None + + class Watcher(Service): id = "entari.plugin.auto_reload/watcher" @@ -81,36 +120,130 @@ def stages(self) -> set[Phase]: def __init__(self, config: Config): self.config = config self.fail: dict[str, tuple[str, dict]] = {} + self._locks: dict[str, asyncio.Lock] = {} super().__init__() + def _lock_for(self, plugin_id: str) -> asyncio.Lock: + if plugin_id not in self._locks: + self._locks[plugin_id] = asyncio.Lock() + return self._locks[plugin_id] + + async def _reload(self, pid: str, cfg: dict): + async with self._lock_for(pid): + if pid in plugin_service._subplugined: + return await reload_subplugin(pid, cfg) + return await reload_plugin(pid, cfg) + + async def _reload_upstream(self, module_name: str) -> list[str]: + """刷新非插件上游模块,并重载依赖它的插件 + + 必须先刷新 sys.modules 中的内容, 否则依赖插件重载时 import 链命中 sys.modules 缓存,拿到的仍是旧模块。 + + Returns: + 实际重载的插件 id 列表(供同批次去重)。 + """ + if module_name in plugin_service.plugins or module_name in plugin_service._subplugined: + return [] + mod = sys.modules.get(module_name) + if mod is None or not isinstance(mod, ModuleType): + return [] + dependents = plugin_service.dependents_of(module_name, ensure=True) + if not dependents: + return [] + plugins = ", ".join(sorted(dependents)) + logger.debug(f"Reloading upstream module {module_name!r}, affected plugins: {plugins}") + try: + importlib.reload(mod) + except Exception as e: + logger.error(f"Failed to reload upstream module {module_name!r}: {e!r}") + return [] + reloaded: list[str] = [] + for dep_id in plugin_service.topo_dependents(set(dependents)): + async with self._lock_for(dep_id): + result = await reload_plugin(dep_id) + if result: + reloaded.append(dep_id) + else: + logger.error(f"Failed to reload plugin {dep_id!r} after upstream module reload") + return reloaded + async def watch(self): async for event in awatch( *self.config.watch_dirs, debounce=self.config.debounce, step=self.config.step, watch_filter=PythonFilter() ): + pending: dict[str, tuple[str, Plugin]] = {} + failed: list[str] = [] + upstream: set[str] = set() for change in event: if plugin := find_plugin_by_file(change[1]): if plugin.is_static: logger.info(f"Plugin {plugin.id!r} is static, ignored.") continue - logger.info(f"Detected change in {plugin.id!r}, reloading...") - pid = plugin.id - _conf = plugin.config.copy() - del plugin - await unload_plugin_async(pid) - if plugin := load_plugin(pid, _conf): - logger.info(f"Reloaded {plugin.id!r}") - del plugin - else: - logger.error(f"Failed to reload {pid!r}") - self.fail[change[1]] = (pid, _conf) + pending.setdefault(plugin.id, (change[1], plugin)) elif change[1] in self.fail: - logger.info(f"Detected change in {change[1]!r} which failed to reload, retrying...") - if plugin := load_plugin(*self.fail[change[1]]): - logger.info(f"Reloaded {plugin.id!r}") - del plugin - del self.fail[change[1]] + failed.append(change[1]) + elif module_name := module_name_from_path(Path(change[1])): + upstream.add(module_name) + reloaded: set[str] = set() + for module_name in upstream: + reloaded.update(await self._reload_upstream(module_name)) + for pid, (file_path, plugin) in pending.items(): + if pid in reloaded: + self.fail.pop(file_path, None) + continue + nodes: ast.Module | None = None + if ( + plugin._inspect + and plugin.module.__file__ + and (path := Path(file_path).resolve()) == Path(plugin.module.__file__).resolve() + ): + try: + nodes = ast.parse(path.read_bytes(), filename=path, type_comments=True) + except (OSError, SyntaxError) as e: + trace = escape_tag("".join(format_exception_only(e))) + logger.error(f"Change in {pid!r} occurred exception, skipped:\n{trace}") + continue else: - logger.error(f"Failed to reload {self.fail[change[1]][0]!r}") + if ast.dump(nodes, include_attributes=False) == plugin._inspect.dump: + logger.debug(f"Change in {pid!r} has no semantic difference, skipped.") + self.fail.pop(file_path, None) + continue + logger.info(f"Detected change in {pid!r}, reloading...") + if plugin._inspect and nodes: + changes = classify(plugin._inspect.nodes, nodes) + if changes is not None and swap_functions(plugin, nodes, changes): + if changes: + logger.info( + f"Hot swapped functions in {pid!r}: " + f"{', '.join(f'{change.qualname}' for change in changes)} " + f"successfully." + ) + else: + logger.debug(f"Change in {pid!r} has no function-level diff, skipped.") + self.fail.pop(file_path, None) + continue + logger.debug(f"Hot swap functions in {pid!r} failed, falling back to full reload.") + _conf = plugin.config.copy() + del plugin + if res := await self._reload(pid, _conf): + logger.info(f"Reloaded {pid!r}") + self.fail.pop(file_path, None) + else: + logger.error(f"Failed to reload {pid!r}: {res.error!r}") + self.fail[file_path] = (pid, _conf) + pending.clear() + for file_path in failed: + if file_path not in self.fail: + continue + pid, _conf = self.fail[file_path] + if file_path not in self.fail: + continue + logger.info(f"Detected change in {file_path!r} which failed to reload, retrying...") + if res := await self._reload(pid, _conf): + logger.info(f"Reloaded {pid!r}") + del self.fail[file_path] + else: + logger.error(f"Failed to reload {pid!r}: {res.error!r}") async def watch_config(self): file = EntariConfig.instance.path.resolve() @@ -196,12 +329,11 @@ async def watch_config(self): _conf = plg.config.copy() async def _(): - await unload_plugin_async(pid) - if plg := load_plugin(plugin_name, new_conf): - logger.info(f"Reloaded {plg.id!r}") - del plg + if result := await self._reload(pid, new_conf): + logger.info(f"Reloaded {pid!r}") + self.fail.pop(plugin_file, None) else: - logger.error(f"Failed to reload {plugin_name!r}") + logger.error(f"Failed to reload {plugin_name!r}: {result.error!r}") self.fail[plugin_file] = (pid, _conf) await asyncio.shield(_()) @@ -212,9 +344,8 @@ async def _(): for plugin_name in new: if plugin_name.startswith("$") or plugin_name.startswith("~"): continue - if not (plg := load_plugin(plugin_name)): + if not load_plugin(plugin_name): continue - del plg async def launch(self, manager: Launart): async with self.stage("preparing"): diff --git a/arclet/entari/builtins/control.py b/arclet/entari/builtins/control.py index 4cfe438..da091b8 100644 --- a/arclet/entari/builtins/control.py +++ b/arclet/entari/builtins/control.py @@ -357,6 +357,16 @@ async def function_ctl_disable( session: Session, names: tuple[str, ...], ): + async def do(plg_id: str, func: str): + if session.event.channel: + ch_id = session.event.channel.id + if ch_id not in plugin_disables: + plugin_disables[ch_id] = {} + if plg_id not in plugin_disables[ch_id]: + plugin_disables[ch_id][plg_id] = {} + plugin_disables[ch_id][plg_id][func] = True + await session.send_message(f"已在当前频道禁用功能:{plg_id}:{func}") + plg_ids = set(plugin_functions.keys()) name_to_id = {plg.metadata.name: plg.id for plg in plugin.get_plugins() if plg.metadata and plg.id in plg_ids} for name in names: @@ -366,21 +376,17 @@ async def function_ctl_disable( await session.send_message(f"未找到插件:{target}") continue plg_id = name_to_id.get(target, target) + if func_name not in plugin_functions.get(plg_id, {}): + await session.send_message(f"未找到功能:{func_name} 在插件 {target} 中") + continue + await do(plg_id, func_name) else: for plg_id, functions in plugin_functions.items(): if func_name in functions: - break + await do(plg_id, func_name) else: await session.send_message(f"未找到功能:{func_name}") continue - if session.event.channel: - ch_id = session.event.channel.id - if ch_id not in plugin_disables: - plugin_disables[ch_id] = {} - if plg_id not in plugin_disables[ch_id]: - plugin_disables[ch_id][plg_id] = {} - plugin_disables[ch_id][plg_id][func_name] = True - await session.send_message(f"已在当前频道禁用功能:{name}") @function_ctl_disp.assign("enable") @@ -389,6 +395,13 @@ async def function_ctl_enable( session: Session, names: tuple[str, ...], ): + async def do(plg_id: str, func: str): + if session.event.channel: + ch_id = session.event.channel.id + if ch_id in plugin_disables and plg_id in plugin_disables[ch_id]: + plugin_disables[ch_id][plg_id][func] = False + await session.send_message(f"已在当前频道启用功能:{plg_id}:{func}") + plg_ids = set(plugin_functions.keys()) name_to_id = {plg.metadata.name: plg.id for plg in plugin.get_plugins() if plg.metadata and plg.id in plg_ids} for name in names: @@ -398,18 +411,17 @@ async def function_ctl_enable( await session.send_message(f"未找到插件:{target}") continue plg_id = name_to_id.get(target, target) + if func_name not in plugin_functions.get(plg_id, {}): + await session.send_message(f"未找到功能:{func_name} 在插件 {target} 中") + continue + await do(plg_id, func_name) else: for plg_id, functions in plugin_functions.items(): if func_name in functions: - break + await do(plg_id, func_name) else: await session.send_message(f"未找到功能:{func_name}") continue - if session.event.channel: - ch_id = session.event.channel.id - if ch_id in plugin_disables and plg_id in plugin_disables[ch_id]: - plugin_disables[ch_id][plg_id][func_name] = False - await session.send_message(f"已在当前频道启用功能:{name}") @function_ctl_disp.assign("clear") diff --git a/arclet/entari/command/__init__.py b/arclet/entari/command/__init__.py index bb4494b..871271e 100644 --- a/arclet/entari/command/__init__.py +++ b/arclet/entari/command/__init__.py @@ -23,7 +23,7 @@ from ..event.config import ConfigReload from ..logger import DEBUG_NO, log from ..message import MessageChain -from ..plugin import PluginRole, RootlessPlugin, get_plugin, metadata, plugin_config +from ..plugin import PluginRole, get_plugin, metadata, niche, plugin_config from ..session import Session from .argv import MessageArgv # noqa: F401 from .model import CommandResult, Match, Query @@ -166,7 +166,15 @@ def wrapper(func: Callable[..., TM]) -> Subscriber[TM]: self.subscribers[target.id] = target def _remove(_): - command_manager.delete(get_cmd(_)) + _cmd = get_cmd(_) + # if returned, it means the subscriber is already staged reload. + try: + record = command_manager._resolve(_cmd._hash) + except KeyError: + pass + else: + if id(_cmd) == id(record): + command_manager.delete(_cmd) self.trie[key].remove(target.id) # type: ignore if not self.trie[key]: self.trie.pop(key, None) # type: ignore @@ -203,7 +211,15 @@ def _remove(_): self.trie.setdefault(_key, []).append(target.id) def _remove(_): - command_manager.delete(get_cmd(_)) + _cmd = get_cmd(_) + # if returned, it means the subscriber is already staged reload. + try: + record = command_manager._resolve(_cmd._hash) + except KeyError: + pass + else: + if id(_cmd) == id(record): + command_manager.delete(_cmd) self.subscribers.pop(target.id, None) for _key in keys: self.trie[_key].remove(target.id) # type: ignore @@ -252,7 +268,7 @@ class CommandsConfig(BasicConfModel): ) -@RootlessPlugin.apply("commands", default=True) +@niche("commands") def command_plugin(_): metadata( "Commands Plugin", diff --git a/arclet/entari/command/plugin.py b/arclet/entari/command/plugin.py index 28fd654..155b75c 100644 --- a/arclet/entari/command/plugin.py +++ b/arclet/entari/command/plugin.py @@ -1,5 +1,6 @@ from __future__ import annotations +import asyncio from typing import Any from typing_extensions import TypeVar, deprecated @@ -28,7 +29,7 @@ async def _after_execute(ctx: Contexts, session: Session | None = None): - result = ctx[RESULT] + result: str | MessageChain | _ExitException | None = ctx[RESULT] event = ctx[EVENT] if result is not None: if isinstance(result, _ExitException): @@ -55,7 +56,7 @@ def assign(self, path: str, value: Any = _seminal, or_not: bool = False, priorit class AlconnaPluginDispatcher(PluginDispatcher[T]): def __init__(self, plugin: Plugin, command: Alconna, need_reply_me: bool = False, need_notice_me: bool = False, use_config_prefix: bool = True, block: bool = True, skip_for_unmatch: bool = True): # noqa: E501 plugin._extra.setdefault("commands", []).append((command.prefixes, command.command)) - self.cache = LRU(10) + self.cache: "LRU[str, asyncio.Future]" = LRU(10) # noqa: UP037 self.supplier = AlconnaSuppiler(command, self.cache, block, skip_for_unmatch) super().__init__(plugin, MessageCreatedEvent, command.path) plugin.collect( @@ -68,7 +69,15 @@ def __init__(self, plugin: Plugin, command: Alconna, need_reply_me: bool = False @plugin.collect def dispose(): - command_manager.delete(self.supplier.cmd) + _cmd = self.supplier.cmd + # if returned, it means the subscriber is already staged reload. + try: + record = command_manager._resolve(_cmd._hash) + except KeyError: + pass + else: + if id(_cmd) == id(record): + command_manager.delete(_cmd) del self.supplier.cmd del self.supplier diff --git a/arclet/entari/config/action.py b/arclet/entari/config/action.py index c5480c9..ee3de0d 100644 --- a/arclet/entari/config/action.py +++ b/arclet/entari/config/action.py @@ -135,6 +135,7 @@ def config_model_keys(obj: Any) -> list[str]: def config_model_schema(base: type[C], ref_root: str = "/") -> dict[str, Any]: for b in base.__mro__[-2::-1]: + _ensure_actions(b) if b in _config_model_actions: return _config_model_actions[b].schema(base, ref_root) return {field_.name: field_.type for field_ in fields(base)} # type: ignore diff --git a/arclet/entari/config/file.py b/arclet/entari/config/file.py index c0ca546..6ae4a7e 100644 --- a/arclet/entari/config/file.py +++ b/arclet/entari/config/file.py @@ -15,6 +15,8 @@ from .action import Proxy, config_model_dump, config_model_schema, config_model_validate from .model import BasicConfig +from .schema import apply_schema_fragments +from .schema.plugin import PLUGIN_META_PROPERTIES_EXTRA, plugin_config_schema from .util import GetattrDict try: @@ -336,58 +338,48 @@ def updater(target): def generate_schema(self, plugins: list["Plugin"]): plugins_properties = {} # fmt: off - plugin_meta_properties = {"$disable": {"type": "string", "description": "Expression for whether disable this plugin"}, "$priority": {"type": "integer", "description": "Plugin loading priority, lower value means higher priority (default: 16)"}, "$filter": {"type": "string", "description": "Plugin filter expression, which will be evaluated in the context of the plugin"}} # noqa: E501 # Build a mapping from plugin config key to plugin object for $files schema generation plugin_map: dict[str, "Plugin"] = {} # noqa: UP037 for plug in plugins: - plugin_map[plug._config_key] = plug - if plug.metadata is not None: - if plug.metadata.config: - schema = config_model_schema(plug.metadata.config, ref_root=f"/properties/plugins/properties/{plug._config_key}/") # noqa: E501 - schema["properties"].update(plugin_meta_properties) - plugins_properties[plug._config_key] = schema - else: - plugins_properties[plug._config_key] = {"type": "object", "description": f"{plug.metadata.description or plug.metadata.name}; no configuration required", "additionalProperties": True, "properties": plugin_meta_properties} # noqa: E501 + if plug.id == "$": + schema = plugin_config_schema(plug, ref_root="/properties/plugins") + for key, property_ in schema["properties"].items(): + if key.startswith("$"): + continue + plugins_properties[key] = property_ else: - plugins_properties[plug._config_key] = {"type": "object", "description": "No configuration required", "additionalProperties": True, "properties": plugin_meta_properties} # noqa: E501 + plugin_map[plug._config_key] = plug + plugins_properties[plug._config_key] = plugin_config_schema(plug, ref_root=f"/properties/plugins/properties/{plug._config_key}") # noqa: E501 schemas = { "basic": config_model_schema(BasicConfig, ref_root="/properties/basic/"), "plugins": {"type": "object", "description": "Plugin configurations", "properties": {"$prefix": {"description": "List of prefix config", "items": {"properties": {"key": {"description": "Prefix key", "title": "Key", "type": "string"}, "plugins": {"anyOf": [{"type": "string"}, {"items": {"type": "string", "description": "Plugin name"}, "type": "array", "uniqueItems": True}], "description": "List of plugins under the prefix, or select an item of $files to apply plugins", "title": "Plugins"}}, "required": ["key"], "title": "Prefix Config", "type": "object"}, "type": "array"}, "$prelude": {"type": "array", "items": {"type": "string", "description": "Plugin name"}, "description": "List of prelude plugins to load", "default": [], "uniqueItems": True}, "$files": {"type": "array", "items": {"type": "string", "description": "File path"}, "description": "List of configuration files to load", "default": [], "uniqueItems": True}, **plugins_properties}}, "adapters": {"type": "array", "description": "Adapter configurations", "items": {"type": "object", "description": "Adapter configuration", "properties": {"$path": {"type": "string", "description": "Adapter Module Path"}}, "required": ["$path"], "additionalProperties": True}} # noqa: E501 } with open(f"{self.path.stem}.schema.json", "w", encoding="utf-8") as f: json.dump({"$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": schemas, "additionalProperties": False, "required": ["basic"]}, f, indent=2, ensure_ascii=False) # noqa: E501 + # fmt: on # Generate schema for each file in $files for file in self.plugin_extra_files: path = Path(file) if path.is_file() and not path.name.endswith(".schema.json"): - self._generate_extra_file_schema(path, plugin_map, plugin_meta_properties) + self._generate_extra_file_schema(path, plugin_map) elif path.is_dir(): for _path in path.iterdir(): if _path.is_file() and not _path.name.endswith(".schema.json"): - self._generate_extra_file_schema(_path, plugin_map, plugin_meta_properties) - # fmt: on + self._generate_extra_file_schema(_path, plugin_map) plugin_map.clear() - def _generate_extra_file_schema(self, path: Path, plugin_map: dict[str, "Plugin"], plugin_meta_properties: dict): + def _generate_extra_file_schema(self, path: Path, plugin_map: dict[str, "Plugin"]): """Generate schema for an extra config file from $files.""" - # fmt: off plugin_key = path.stem schema_file = path.with_suffix(".schema.json") - plugin_meta_properties = {**plugin_meta_properties, "$optional": {"type": "boolean", "description": "Whether this plugin is optional"}} # noqa: E501 - + # fmt: off # Check if we have a matching plugin with config if plugin_key in plugin_map: - plug = plugin_map[plugin_key] - if plug.metadata is not None and plug.metadata.config: - plugin_schema = config_model_schema(plug.metadata.config, ref_root="/") - plugin_schema["properties"].update(plugin_meta_properties) - elif plug.metadata is not None: - plugin_schema = {"type": "object", "description": f"{plug.metadata.description or plug.metadata.name}; no configuration required", "additionalProperties": True, "properties": plugin_meta_properties} # noqa: E501 - else: - plugin_schema = {"type": "object", "description": "No configuration required", "additionalProperties": True, "properties": plugin_meta_properties} # noqa: E501 + plugin_schema = plugin_config_schema(plugin_map[plugin_key], use_extra_meta=True) else: # Plugin not found, generate a generic schema - plugin_schema = {"type": "object", "description": f"Configuration for {plugin_key}", "additionalProperties": True, "properties": plugin_meta_properties} # noqa: E501 + plugin_schema = {"type": "object", "description": f"Configuration for {plugin_key}", "additionalProperties": True, "properties": {k: dict(v) for k, v in PLUGIN_META_PROPERTIES_EXTRA.items()}} # noqa: E501 + apply_schema_fragments(plugin_schema, plugin_key, ref_root="/") with open(schema_file, "w", encoding="utf-8") as f: json.dump({"$schema": "https://json-schema.org/draft/2020-12/schema", **plugin_schema}, f, indent=2, ensure_ascii=False) # noqa: E501 diff --git a/arclet/entari/config/models/default.py b/arclet/entari/config/models/default.py index 6e196c0..04f96d6 100644 --- a/arclet/entari/config/models/default.py +++ b/arclet/entari/config/models/default.py @@ -14,7 +14,7 @@ from tarina.signature import merge_cls_and_parent_ns from ..action import ConfigModelAction -from ..dc_schema import SchemaGenerator +from ..schema import SchemaGenerator from ..util import store_field_description _available_dc_attrs = set(Signature.from_callable(dataclass).parameters.keys()) diff --git a/arclet/entari/config/schema/__init__.py b/arclet/entari/config/schema/__init__.py new file mode 100644 index 0000000..77d2893 --- /dev/null +++ b/arclet/entari/config/schema/__init__.py @@ -0,0 +1,6 @@ +from .dataclass import SchemaGenerator as SchemaGenerator +from .plugin import plugin_config_schema as plugin_config_schema +from .registry import add_schema_fragment as add_schema_fragment +from .registry import apply_schema_fragments as apply_schema_fragments +from .registry import has_schema_fragments as has_schema_fragments +from .registry import purge_schema_fragments as purge_schema_fragments diff --git a/arclet/entari/config/dc_schema.py b/arclet/entari/config/schema/dataclass.py similarity index 99% rename from arclet/entari/config/dc_schema.py rename to arclet/entari/config/schema/dataclass.py index 941db37..7d8c0c7 100644 --- a/arclet/entari/config/dc_schema.py +++ b/arclet/entari/config/schema/dataclass.py @@ -39,7 +39,7 @@ from tarina.generic import Unions, is_typed_dict -from .util import store_field_description +from ..util import store_field_description _MISSING = dataclasses.MISSING diff --git a/arclet/entari/config/schema/plugin.py b/arclet/entari/config/schema/plugin.py new file mode 100644 index 0000000..c41a376 --- /dev/null +++ b/arclet/entari/config/schema/plugin.py @@ -0,0 +1,91 @@ +from typing import TYPE_CHECKING, Any + +from ..action import config_model_schema +from .registry import apply_schema_fragments + +if TYPE_CHECKING: + from arclet.entari.plugin import Plugin + + +# fmt: off +PLUGIN_META_PROPERTIES = {"$disable": {"type": "string", "description": "Expression for whether disable this plugin"}, "$priority": {"type": "integer", "description": "Plugin loading priority, lower value means higher priority (default: 16)"}, "$filter": {"type": "string", "description": "Plugin filter expression, which will be evaluated in the context of the plugin"}} # noqa: E501 +PLUGIN_META_PROPERTIES_EXTRA = PLUGIN_META_PROPERTIES | {"$optional": {"type": "boolean", "description": "Whether this plugin is optional"}} # noqa: E501 +# fmt: on + + +def _synthesize_subplugins(plug: "Plugin", schema: dict, ref_root: str): + from arclet.entari.plugin.service import plugin_service + + properties = schema.setdefault("properties", {}) + if not isinstance(properties, dict): + # log.warning("plugin schema 'properties' is not a dict, skip sub-plugin synthesis") + return + for sid in plug.subplugins: + if not sid.startswith(plug.id): + continue + dotted = sid[len(plug.id) :] + if (at := dotted.rfind("@")) != -1: # A.a@uid -> .a(与 module.py:310 加载键一致) + dotted = dotted[:at] + meta = {k: dict(v) for k, v in PLUGIN_META_PROPERTIES.items()} + sub = plugin_service.plugins.get(sid) + if sub is not None and sub.metadata is not None and sub.metadata.config: + sub_ref_root = f"{ref_root}properties/{dotted}/" + sub_schema = config_model_schema(sub.metadata.config, ref_root=sub_ref_root) + sub_props = sub_schema.setdefault("properties", {}) + if not isinstance(sub_props, dict): + sub_props = sub_schema["properties"] = {} + sub_props.update(meta) + if sub.metadata.description or sub.metadata.name: + sub_schema.setdefault("description", sub.metadata.description or sub.metadata.name) + # else: + # if sub is not None and sub.metadata is not None: + # desc = f"{sub.metadata.description or sub.metadata.name}; no configuration required" + # else: + # desc = "No configuration required" + # sub_schema = {"type": "object", "description": desc, "additionalProperties": True, "properties": meta} + properties[dotted] = sub_schema + + +def plugin_config_schema( + plugin: "Plugin", + config_key: str | None = None, + *, + ref_root: str = "/", + expand_subplugins: bool = True, + use_extra_meta: bool = False, +) -> dict[str, Any]: + """获取插件配置模型的 JSON Schema + + Args: + plugin (Plugin): 插件实例。 + config_key (str, optional): 插件实例的配置键。 + ref_root (str, optional): JSON Schema $ref 的根路径,默认为 "/"。 + expand_subplugins (bool, optional): 是否展开子插件的配置模型,默认为 True。 + use_extra_meta (bool, optional): 是否使用额外的元属性($optional),默认为 False。 + """ + meta_properties = { + k: dict(v) for k, v in (PLUGIN_META_PROPERTIES_EXTRA if use_extra_meta else PLUGIN_META_PROPERTIES).items() + } + metadata = plugin.metadata + if metadata and metadata.config is not None: + schema = config_model_schema(metadata.config, ref_root=ref_root) + properties = schema.setdefault("properties", {}) + if not isinstance(properties, dict): + properties = schema["properties"] = {} + properties.update(meta_properties) + elif metadata is not None: + desc = f"{metadata.description or metadata.name}; no configuration required" + schema = {"type": "object", "description": desc, "additionalProperties": True, "properties": meta_properties} + else: + schema = { + "type": "object", + "description": "No configuration required", + "additionalProperties": True, + "properties": meta_properties, + } + if expand_subplugins: + _synthesize_subplugins(plugin, schema, ref_root) + config_key = config_key or getattr(plugin, "_config_key", None) + if config_key: + apply_schema_fragments(schema, config_key, ref_root) + return schema diff --git a/arclet/entari/config/schema/rebase.py b/arclet/entari/config/schema/rebase.py new file mode 100644 index 0000000..6fa412b --- /dev/null +++ b/arclet/entari/config/schema/rebase.py @@ -0,0 +1,156 @@ +import copy +from collections.abc import Callable +from typing import Any, TypeAlias + +from ..action import config_model_schema + +_SENTINEL = object() + +Fragment: TypeAlias = dict[str, Any] | type | Callable[[Any], Any] + + +def _deep_merge(target: dict, source: dict) -> None: + for key, value in source.items(): + if key in target and isinstance(target[key], dict) and isinstance(value, dict): + _deep_merge(target[key], value) + else: + target[key] = copy.deepcopy(value) + + +def _pointer_path(parts: tuple[str, ...]) -> str: + """拼接 JSON Pointer 风格的路径字符串(用于 schema 的 $ref)""" + segments: list[str] = [] + for part in parts: + if part.startswith("."): + segments.append("properties") + segments.append(part) + return "/" + "/".join(segments) if segments else "" + + +def _resolve_target(schema: dict, parts: tuple[str, ...]) -> tuple[dict | None, str | None, dict | None]: + """沿 parts 在 schema 内导航到目标节点,返回 (holder, key, target) + - target: 已存在的 dict 目标节点(可原地深合并); + - target 为 None 且 holder/key 非空:目标缺失或为非 dict,调用方应整体写入 holder[key]; + - holder 为 None:导航中途遇到已存在但非 dict 的节点,已告警,调用方跳过该片段。 + + Raises: + ValueError: 导航中途遇到已存在但非 dict 的节点 + """ + node: Any = schema + holder: dict | None = None + key: str | None = None + last = len(parts) - 1 + if not parts: + return schema, None, schema # 空 path:目标即 schema 根 + for index, part in enumerate(parts): + if part.startswith("."): + if not isinstance(node, dict): + raise ValueError(f"schema fragment path crosses non-dict node {part!r}, skipped") + props = node.setdefault("properties", {}) + if not isinstance(props, dict): + raise ValueError("schema fragment path crosses non-dict 'properties' node, skipped") + holder, key = props, part + value = props.get(part, _SENTINEL) + else: + if not isinstance(node, dict): + raise ValueError(f"schema fragment path crosses non-dict node {part!r}, skipped") + holder, key = node, part + value = node.get(part, _SENTINEL) + if index == last: + if isinstance(value, dict): + return holder, key, value + return holder, key, None + if isinstance(value, dict): + node = value + elif value is _SENTINEL: + node = holder[key] = {"type": "object"} + else: + raise ValueError(f"schema fragment path crosses existing non-dict node {part!r}, skipped") + return holder, key, None + + +def _rewrite_refs(node: Any, renamed: dict[str, str], model_prefix: str, ref_root: str) -> None: + """把片段内 $ref 就地重写为提升后的位置。 + + 匹配两种源形态(其余前缀视为指向片段外,保持原样并告警): + - `#{最终 ref_root}$defs/X`:模型片段生成时带完整前缀; + - `#/$defs/X`:手写 dict 片段相对片段根的形式。 + """ + if isinstance(node, dict): + ref = node.get("$ref") + if isinstance(ref, str): + name: str | None = None + if ref.startswith(model_prefix): + name = ref[len(model_prefix) :] + elif ref.startswith("#/$defs/"): + name = ref[len("#/$defs/") :] + if name is not None: + if name in renamed: + node["$ref"] = f"#{ref_root}$defs/{renamed[name]}" + else: + # log.warning(f"schema fragment $ref {ref!r} points to unknown local $defs {name!r}, kept as-is") + pass + for value in node.values(): + _rewrite_refs(value, renamed, model_prefix, ref_root) + elif isinstance(node, list): + for item in node: + _rewrite_refs(item, renamed, model_prefix, ref_root) + + +def _collect_defs_and_rebase( + content: dict, *, config_key: str, parts: tuple[str, ...], ref_root: str, final_ref_root: str +) -> dict[str, dict]: + """提取片段根级 $defs,唯一化重命名并就地重写 content 内 $ref。 + + 返回 {新名: defs schema 深拷贝}。 + """ + defs = content.pop("$defs", None) + if not isinstance(defs, dict) or not defs: + return {} + path_token = ".".join(parts).strip(".") or "root" + renamed: dict[str, str] = {} + hoisted: dict[str, dict] = {} + for name, def_schema in defs.items(): + new_name = f"{config_key}.{path_token}.{name}" + renamed[name] = new_name + hoisted[new_name] = copy.deepcopy(def_schema) + _rewrite_refs(content, renamed, f"#{final_ref_root}$defs/", ref_root) + return hoisted + + +def update_schema( + schema: dict, config_key: str, ref_root: str, parts: tuple[str, ...], payload: Fragment, replace: bool = False +) -> None: + final_ref_root = f"{ref_root}{_pointer_path(parts)}/" if parts else ref_root + if isinstance(payload, type): + # 模型片段:以最终 ref_root 生成,再走统一的 defs 处理 + try: + payload = config_model_schema(payload, ref_root=final_ref_root) + except Exception as e: + raise RuntimeError(f"failed to resolve config model fragment {payload!r}: {e!r}, skipped") from e + holder, key, target = _resolve_target(schema, parts) + if holder is None: + return + if callable(payload) and not isinstance(payload, type): + try: + current = copy.deepcopy(target) if target is not None else None + payload = payload(current) + except Exception as e: + raise RuntimeError(f"schema fragment callable raised {e!r}, skipped") from e + if not isinstance(payload, dict): + holder[key] = copy.deepcopy(payload) + return + content = copy.deepcopy(payload) # 绝不原地改写注册的片段:热重载后再次应用时内容必须仍是原样 + hoisted = _collect_defs_and_rebase( + content, config_key=config_key, parts=parts, ref_root=ref_root, final_ref_root=final_ref_root + ) # 先剥离 content 的 $defs 并重写 ref,再合并,避免原样 defs 先落入目标 + if target is not None and not replace: + _deep_merge(target, content) + elif target is not None: + target.clear() # replace 空 path:整体替换基础 schema + _deep_merge(target, content) + else: + holder[key] = content + # replace 空 path 会清空 schema 根,不能被 clear 冲掉 + if hoisted: + schema.setdefault("$defs", {}).update(hoisted) diff --git a/arclet/entari/config/schema/registry.py b/arclet/entari/config/schema/registry.py new file mode 100644 index 0000000..54001d3 --- /dev/null +++ b/arclet/entari/config/schema/registry.py @@ -0,0 +1,121 @@ +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from .rebase import Fragment, update_schema + + +@dataclass(frozen=True) +class _FragmentRecord: + """已注册的 schema 片段""" + + path: tuple[str, ...] + """规范化后的路径段(空元组 = 整个配置 schema)""" + fragment: Fragment + """dict / config 模型类型 / callable""" + replace: bool + """True 时目标节点整体替换(跳过深合并)""" + origin: str | None + """注册来源插件 id;None = 无插件上下文(仅 dispose 清除会用它)""" + + +_schema_fragments: dict[str, list[_FragmentRecord]] = {} + + +def _parse_path(path: str | tuple[str, ...]) -> tuple[str, ...]: + """把注册 的 path 参数规范化为非空字符串段元组。 + + 字符串按 `.` 拆段;前导点/连续点/尾随点(空段)一律抛 ValueError + + 子插件键以单元素元组 `(".a.b",)` 传入 + """ + if isinstance(path, str): + if not path: + return () + if path.startswith("."): + raise ValueError("sub-plugin config keys must be passed as a single-element tuple") + parts = tuple(path.split(".")) + if any(not part for part in parts): + raise ValueError(f"invalid schema fragment path {path!r}: empty segment") + return parts + parts = tuple(path) + if any(not isinstance(part, str) or not part for part in parts): + raise ValueError(f"invalid schema fragment path {path!r}: segments must be non-empty strings") + return parts + + +def add_schema_fragment( + config_key: str, + path: str | tuple[str, ...] = "", + fragment: dict | type | Callable[[Any], Any] | None = None, + replace: bool = False, + origin: str | None = None, +) -> None: + """注册一个配置 schema 片段。 + + Args: + config_key (str): 目标插件的配置键。 + path (str | tuple[str, ...]): 点分字符串或段元组;空字符串意味着作用于整个配置 schema。 + 以 `.` 开头的子插件键必须用单元素元组,如 `(".a.b",)`。 + fragment: dict(深合并);config 模型类型(应用时才按最终 ref_root 生成); + 或 callable(接收目标节点当前值,返回 dict 深合并 / 非 dict 整体替换)。 + replace (bool, optional): 目标节点是否整体替换为片段,不做深合并。默认 False。 + origin (str, optional): 注册来源插件 id;空表示无插件上下文(仅 purge 清除会用它)。默认 None。 + Raises: + TypeError: fragment 类型不合法 + """ + + if fragment is None: + raise TypeError("schema_fragment requires a `fragment` (dict / config model type / callable)") + if not isinstance(fragment, dict) and not isinstance(fragment, type) and not callable(fragment): + raise TypeError(f"unsupported fragment type: {type(fragment).__name__}") + parts = _parse_path(path) + records = _schema_fragments.setdefault(config_key, []) + for record in records: + if ( + record.origin == origin + and record.path == parts + and record.fragment is fragment + and record.replace == replace + ): + return + records.append(_FragmentRecord(parts, fragment, replace, origin)) + + +def purge_schema_fragments(plugin_id: str) -> None: + """清除某插件注册的全部 schema 片段""" + for key in [k for k, v in _schema_fragments.items() if any(r.origin == plugin_id for r in v)]: + remained = [r for r in _schema_fragments[key] if r.origin != plugin_id] + if remained: + _schema_fragments[key] = remained + else: + del _schema_fragments[key] + + +def has_schema_fragments(config_key: str) -> bool: + return bool(_schema_fragments.get(config_key)) + + +def apply_schema_fragments(schema: dict, config_key: str, ref_root: str = "/") -> dict: + """按注册顺序把 config_key 的片段应用到 schema(原地合并)并返回 schema。 + + 单轮应用内对等价片段去重:跨 origin 的重复只生效一次。 + """ + records = _schema_fragments.get(config_key) + if not records: + return schema + applied: list[tuple[tuple[str, ...], bool, Fragment]] = [] + for record in records: + mark = (record.path, record.replace, record.fragment) + if any(mark == old for old in applied): + continue + applied.append(mark) + try: + update_schema(schema, config_key, ref_root, record.path, record.fragment, record.replace) + except (RuntimeError, ValueError) as e: + from ...logger import log + + log.plugin.warning(str(e)) + return schema diff --git a/arclet/entari/core.py b/arclet/entari/core.py index ce2cc35..a858bd2 100644 --- a/arclet/entari/core.py +++ b/arclet/entari/core.py @@ -5,14 +5,15 @@ from collections.abc import Iterable, Sequence from importlib.metadata import entry_points from pathlib import Path -from typing import get_args +from typing import Any, Literal, get_args import arclet.letoderea as le from arclet.alconna import Alconna from arclet.alconna import config as alconna_config -from arclet.letoderea import EVENT, Contexts, Param, Provider, ProviderFactory, global_providers +from arclet.letoderea import EVENT, Contexts, Param, Provider, ProviderFactory, UnresolvedRequirement, global_providers from arclet.letoderea.context import shared_suppliers from arclet.letoderea.scope import Scope, configure +from arclet.letoderea.subscriber import SUBSCRIBER, Depend, get_params from creart import it from graia.amnesia.builtins.aiohttp import AiohttpClientService from graia.amnesia.builtins.memcache import MemcacheService @@ -46,15 +47,15 @@ ITEM_SESSION, ITEM_USER, ) +from .event.api import SendResponse from .event.base import MessageCreatedEvent, event_parse from .event.config import ConfigReload -from .event.lifespan import AccountUpdate -from .event.send import SendResponse -from .localdata import local_data +from .event.lifespan import AccountUpdate, Ready +from .localdata import local_data, update_localdata from .logger import apply_log_save, enable_rich_except, log from .message import MessageChain -from .plugin import get_plugins, load_plugin, plugin_config, requires -from .plugin.model import PluginMetadata, PluginRole, RootlessPlugin +from .plugin import Plugin, get_plugins, load_plugin, niche, plugin_config, requires +from .plugin.model import PluginMetadata, PluginRole from .plugin.service import plugin_service from .session import EntariProtocol, Session @@ -254,8 +255,8 @@ class RecordConfig(BasicConfModel): """是否在日志中使用简短的消息内容""" -@RootlessPlugin.apply("record_message", default=True) -def record(plg: RootlessPlugin): +@niche("record_message") +def record(plg: Plugin): plg.metadata = PluginMetadata( "记录消息", PluginRole.UTILITY, @@ -303,10 +304,6 @@ async def log_send(event: SendResponse): class Entari(App): id = "entari.service" - @property - def main_plugin(self): - return RootlessPlugin.apply("main", default=True) - @classmethod def load(cls, path: str | os.PathLike[str] | None = None): return cls.from_config(EntariConfig.load(path)) @@ -356,7 +353,8 @@ def __init__( log.short_level = EntariConfig.instance.basic.log.short_level if rich_error: enable_rich_except() - log.core.info(f"Entari version {__version__}") + if ".localdata" in EntariConfig.instance.plugin: + update_localdata(EntariConfig.instance.plugin[".localdata"]) self._log_save_dispose = lambda: None if EntariConfig.instance.basic.log.save: if EntariConfig.instance.basic.log.save is True: @@ -368,6 +366,7 @@ def __init__( compression=EntariConfig.instance.basic.log.save.compression, colorize=EntariConfig.instance.basic.log.save.colorize, ) + log.core.info(f"Entari version {__version__}") log.ignores.update(EntariConfig.instance.basic.log.ignores) log.core.debug(f"Log level set to {log_level}") log.core.debug(f"Config loaded from {EntariConfig.instance.path}: {EntariConfig.instance.data}") @@ -455,9 +454,7 @@ def ensure_manager(self, manager: Launart): load_plugin(plug, prelude=True) plugins = EntariConfig.instance.plugin_names requires(*plugins) - for apply, slot in plugin_service._apply.items(): - if slot[1] and apply not in EntariConfig.instance.plugin: - plugins.append(apply) + for plug in plugins: load_plugin(plug) @@ -465,6 +462,9 @@ def ensure_manager(self, manager: Launart): if entry.name not in plugins and entry.module not in plugin_service.plugins: load_plugin(entry.module) + le.on(Ready, self._generate_schema, priority=0) + + def _generate_schema(self): if self.gen_schema and EntariConfig.instance.path.exists(): EntariConfig.instance.generate_schema(get_plugins()) @@ -538,27 +538,76 @@ class ServiceProviderFactory(ProviderFactory): priority = 10 class _Provider(Provider[Service]): - def __init__(self, origin: type[Service]): + def __init__(self, origin: type[Service], serv_id: str | None = None, stage: str = "prepared"): super().__init__() self.origin = origin + self.serv_id = serv_id + self.stage = stage def validate(self, param: Param): anno = get_origin(param.annotation) return isinstance(anno, type) and issubclass(anno, self.origin) async def __call__(self, context: Contexts): + launart = it(Launart) + sub = f"{context[SUBSCRIBER].__repr__()[13:-1]}" + serv_id = self.serv_id or self.origin.id try: - return it(Launart).get_component(self.origin) + service = launart.get_component(self.origin) except ValueError: # Service not loaded yet - return + await plugin_service.service_waiter.wait_for(serv_id) + service = launart.get_component(self.origin) + log.plugin.trace(f"{sub} waiting for service {serv_id} to {self.stage!r}") + await service.status.wait_for(self.stage) # type: ignore + return service def validate(self, param: Param): anno = get_origin(param.annotation) + serv_id = None + stage = "prepared" + for m in param.meta: + if isinstance(m, OnStage): + serv_id = m.serv_id + stage = m.stage + elif isinstance(m, str): + if m in ("prepared", "blocking", "cleanup"): + stage = m + else: + serv_id = m if isinstance(anno, type) and issubclass(anno, Service): - return self._Provider(anno) + return self._Provider(anno, serv_id, stage) if is_optional(param.annotation, Service): args = get_args(param.annotation) - return self._Provider(args[0]) + return self._Provider(args[0], serv_id, stage) + + +class OnStage(Depend): + + def __init__(self, serv_id: str | None = None, stage: Literal["preparing", "prepared", "blocking"] = "prepared"): + self.serv_id = serv_id + self.stage = stage + super().__init__(self.__call__, False) + + async def __call__(self, context: Contexts): + params = get_params(context) + if "$depend_cache" not in context: + context["$depend_cache"] = {} + param = next((p for p in params if p.depend is self), None) + if param is None: + raise RuntimeError("Depend execute in a context without corresponding param.") + prov = next((pro for pro in param.providers if isinstance(pro, ServiceProviderFactory._Provider)), None) + if prov is None: + raise UnresolvedRequirement(param.name, param.annotation, param.default, param.providers) + prov.serv_id = self.serv_id or prov.serv_id + prov.stage = self.stage + return await prov(context) + + def fork(self, provider): + return self + + +def on_stage(stage: Literal["preparing", "prepared", "blocking"] = "prepared", id: str | None = None) -> Any: + return OnStage(id, stage) global_providers.extend([EntariProvider(), LaunartProvider(), ServiceProviderFactory()]) # type: ignore diff --git a/arclet/entari/event/api.py b/arclet/entari/event/api.py new file mode 100644 index 0000000..a5c392c --- /dev/null +++ b/arclet/entari/event/api.py @@ -0,0 +1,107 @@ +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +from arclet.letoderea import Contexts, Result, define, provide +from satori import ChannelType +from satori.client import Account +from satori.exception import ActionFailed +from satori.model import Channel, MessageObject + +from ..const import ITEM_ACCOUNT, ITEM_CHANNEL, ITEM_MESSAGE_CONTENT, ITEM_SESSION +from ..message import MessageChain + +if TYPE_CHECKING: + from ..session import Session + + +@dataclass +class SendRequest: + account: Account + channel: str + message: MessageChain + session: "Session | None" = None + + def check_result(self, value) -> Result[bool | MessageChain] | None: + if isinstance(value, bool | MessageChain): + return Result(value) + + +before_send_pub = define(SendRequest, name="entari.event/before_send") + + +@before_send_pub.gather +async def send_req_gather(req: SendRequest, context: Contexts): + context[ITEM_ACCOUNT] = req.account + context[ITEM_MESSAGE_CONTENT] = req.message + if req.session: + context[ITEM_SESSION] = req.session + context[ITEM_CHANNEL] = req.session.channel + else: + try: + context[ITEM_CHANNEL] = await req.account.channel_get(req.channel) + except ActionFailed: + context[ITEM_CHANNEL] = Channel( + req.channel, ChannelType.DIRECT if req.channel.startswith("private:") else ChannelType.TEXT + ) + + +@dataclass +class SendResponse: + account: Account + channel: str + message: MessageChain + result: list[MessageObject] + session: "Session | None" = None + + +send_pub = define(SendResponse, name="entari.event/after_send") +send_pub.providers.append(provide(list[MessageObject], call="$resp_result")) + + +@send_pub.gather +async def send_resp_gather(resp: SendResponse, context: Contexts): + context[ITEM_ACCOUNT] = resp.account + context[ITEM_MESSAGE_CONTENT] = resp.message + context["$resp_result"] = resp.result + if resp.session: + context[ITEM_SESSION] = resp.session + context[ITEM_CHANNEL] = resp.session.channel + else: + try: + context[ITEM_CHANNEL] = await resp.account.channel_get(resp.channel) + except ActionFailed: + context[ITEM_CHANNEL] = Channel( + resp.channel, ChannelType.DIRECT if resp.channel.startswith("private:") else ChannelType.TEXT + ) + + +@dataclass +class APIRequest: + account: Account + name: str + params: dict[str, Any] + + +before_api_pub = define(APIRequest, name="entari.event/before_api_call") + + +@before_api_pub.gather +async def call_req_gather(req: APIRequest, context: Contexts): + context[ITEM_ACCOUNT] = req.account + + +@dataclass +class APIResponse: + account: Account + name: str + params: dict[str, Any] + success: bool + result: Any + + +after_api_pub = define(APIResponse, name="entari.event/after_api_call") + + +@after_api_pub.gather +async def call_resp_gather(resp: APIResponse, context: Contexts): + context[ITEM_ACCOUNT] = resp.account diff --git a/arclet/entari/event/base.py b/arclet/entari/event/base.py index 60261eb..f3234da 100644 --- a/arclet/entari/event/base.py +++ b/arclet/entari/event/base.py @@ -53,7 +53,7 @@ def _is_notice_me(message: MessageChain, account: Account): def _remove_notice_me(message: MessageChain, account: Account): - message = message.copy() + message = message.fork() message.pop(0) if _is_notice_me(message, account): message.pop(0) @@ -316,7 +316,7 @@ def __init__(self, account: Account, origin: OriginEvent): super().__init__(account, origin) self.content = MessageChain(self.message.message) if self.content.has(Quote): - self.quote = self.content.get(Quote, 1)[0] + self.quote = self.content.get_first(Quote) self.content = self.content.exclude(Quote) async def gather(self, context: Contexts): diff --git a/arclet/entari/event/send.py b/arclet/entari/event/send.py index de8991e..ebbb250 100644 --- a/arclet/entari/event/send.py +++ b/arclet/entari/event/send.py @@ -1,75 +1,10 @@ -from dataclasses import dataclass -from typing import TYPE_CHECKING +from warnings import warn -from arclet.letoderea import Contexts, Result, define, provide -from satori import ChannelType -from satori.client import Account -from satori.exception import ActionFailed -from satori.model import Channel, MessageObject +warn( + "arclet.entari.event.send is deprecated, please use arclet.entari.event.api instead", + DeprecationWarning, + stacklevel=2, +) -from ..const import ITEM_ACCOUNT, ITEM_CHANNEL, ITEM_MESSAGE_CONTENT, ITEM_SESSION -from ..message import MessageChain - -if TYPE_CHECKING: - from ..session import Session - - -@dataclass -class SendRequest: - account: Account - channel: str - message: MessageChain - session: "Session | None" = None - - def check_result(self, value) -> Result[bool | MessageChain] | None: - if isinstance(value, bool | MessageChain): - return Result(value) - - -before_send_pub = define(SendRequest, name="entari.event/before_send") - - -@before_send_pub.gather -async def req_gather(req: SendRequest, context: Contexts): - context[ITEM_ACCOUNT] = req.account - context[ITEM_MESSAGE_CONTENT] = req.message - if req.session: - context[ITEM_SESSION] = req.session - context[ITEM_CHANNEL] = req.session.channel - else: - try: - context[ITEM_CHANNEL] = await req.account.channel_get(req.channel) - except ActionFailed: - context[ITEM_CHANNEL] = Channel( - req.channel, ChannelType.DIRECT if req.channel.startswith("private:") else ChannelType.TEXT - ) - - -@dataclass -class SendResponse: - account: Account - channel: str - message: MessageChain - result: list[MessageObject] - session: "Session | None" = None - - -send_pub = define(SendResponse, name="entari.event/after_send") -send_pub.providers.append(provide(list[MessageObject], call="$resp_result")) - - -@send_pub.gather -async def resp_gather(resp: SendResponse, context: Contexts): - context[ITEM_ACCOUNT] = resp.account - context[ITEM_MESSAGE_CONTENT] = resp.message - context["$resp_result"] = resp.result - if resp.session: - context[ITEM_SESSION] = resp.session - context[ITEM_CHANNEL] = resp.session.channel - else: - try: - context[ITEM_CHANNEL] = await resp.account.channel_get(resp.channel) - except ActionFailed: - context[ITEM_CHANNEL] = Channel( - resp.channel, ChannelType.DIRECT if resp.channel.startswith("private:") else ChannelType.TEXT - ) +from .api import SendRequest as SendRequest # noqa: F401 +from .api import SendResponse as SendResponse # noqa: F401 diff --git a/arclet/entari/filter/__init__.py b/arclet/entari/filter/__init__.py index dc683a7..039f493 100644 --- a/arclet/entari/filter/__init__.py +++ b/arclet/entari/filter/__init__.py @@ -1,18 +1,19 @@ -import asyncio import inspect from collections.abc import Awaitable, Callable -from datetime import datetime from typing import Final, TypeAlias from typing_extensions import ParamSpec -from arclet.letoderea import STOP, Propagator, enter_if, propagate -from arclet.letoderea.utils import TCallable +from arclet.letoderea import enter_if from tarina import is_coroutinefunction -from ..config import EntariConfig -from ..message import MessageChain from ..session import Session from . import common +from .limit import interval as interval +from .limit import semaphore as semaphore +from .message import endswith as endswith +from .message import startswith as startswith +from .permission import admins as admins +from .permission import superusers as superusers _SessionFilter: TypeAlias = Callable[[Session], bool] | Callable[[Session], Awaitable[bool]] @@ -62,117 +63,3 @@ async def _(*args, _func=func, **kwargs): filter_: Final[_Filter] = _Filter() F = filter_ - - -class interval(Propagator): - def __init__(self, value: float, limit_prompt: str | MessageChain | None = None, priority: int = 80): - self.success = True - self.value = value - self.priority = priority - self.limit_prompt = limit_prompt - self.last_times: dict[str, datetime] = {} - - async def before(self, session: Session | None = None): - session_id = ( - "$global" if not session else f"{session.account.platform}/{session.account.self_id}/{session.channel.id}" - ) - last_time = self.last_times.get(session_id, None) - if not last_time: - return - self.success = (datetime.now() - last_time).total_seconds() > self.value - if not self.success: - if session and self.limit_prompt: - await session.send(self.limit_prompt) - return STOP - - async def after(self, session: Session | None = None): - session_id = ( - "$global" if not session else f"{session.account.platform}/{session.account.self_id}/{session.channel.id}" - ) - self.last_times[session_id] = datetime.now() - - def compose(self): - yield self.before, True, self.priority - yield self.after, False, self.priority - - def __call__(self, func: TCallable) -> TCallable: - return propagate(self)(func) - - -class semaphore(Propagator): - def __init__(self, count: int, limit_prompt: str | MessageChain | None = None, priority: int = 80): - self.count = count - self.limit_prompt = limit_prompt - self.priority = priority - self.semaphores: dict[str, asyncio.Semaphore] = {} - - async def before(self, session: Session | None = None): - session_id = ( - "$global" if not session else f"{session.account.platform}/{session.account.self_id}/{session.channel.id}" - ) - if session_id not in self.semaphores: - self.semaphores[session_id] = asyncio.Semaphore(self.count) - if not await self.semaphores[session_id].acquire(): - if session and self.limit_prompt: - await session.send(self.limit_prompt) - return STOP - - async def after(self, session: Session | None = None): - session_id = ( - "$global" if not session else f"{session.account.platform}/{session.account.self_id}/{session.channel.id}" - ) - if session_id not in self.semaphores: - self.semaphores[session_id] = asyncio.Semaphore(self.count) - self.semaphores[session_id].release() - - def compose(self): - yield self.before, True, self.priority - yield self.after, False, self.priority - - def __call__(self, func: TCallable) -> TCallable: - return propagate(self)(func) - - -class superusers(Propagator): - - async def check(self, session: Session | None = None): - if not session: - return STOP - config = EntariConfig.instance.basic.superusers - if session.account.platform not in config: - return STOP - if not session.event.user: - return STOP - if session.event.user.id not in config[session.account.platform]: - return STOP - - def compose(self): - yield self.check, True, 50 - - def __call__(self, func: TCallable) -> TCallable: - return propagate(self)(func) - - -class admins(Propagator): - - async def check(self, session: Session | None = None): - if not session: - return STOP - if session.event.member and session.event.member.roles: - for role in session.event.member.roles: - if any(keyword in role.id.lower() for keyword in ("admin", "administrator", "owner")): - return - config = EntariConfig.instance.basic.superusers - if ( - session.account.platform in config - and session.event.user - and session.event.user.id in config[session.account.platform] - ): - return - return STOP - - def compose(self): - yield self.check, True, 50 - - def __call__(self, func: TCallable) -> TCallable: - return propagate(self)(func) diff --git a/arclet/entari/filter/limit.py b/arclet/entari/filter/limit.py new file mode 100644 index 0000000..5df1890 --- /dev/null +++ b/arclet/entari/filter/limit.py @@ -0,0 +1,77 @@ +import asyncio +from datetime import datetime + +from arclet.letoderea import STOP, Propagator, propagate +from arclet.letoderea.utils import TCallable + +from ..message import MessageChain +from ..session import Session + + +class interval(Propagator): + def __init__(self, value: float, limit_prompt: str | MessageChain | None = None, priority: int = 80): + self.success = True + self.value = value + self.priority = priority + self.limit_prompt = limit_prompt + self.last_times: dict[str, datetime] = {} + + async def before(self, session: Session | None = None): + session_id = ( + "$global" if not session else f"{session.account.platform}/{session.account.self_id}/{session.channel.id}" + ) + last_time = self.last_times.get(session_id, None) + if not last_time: + return + self.success = (datetime.now() - last_time).total_seconds() > self.value + if not self.success: + if session and self.limit_prompt: + await session.send(self.limit_prompt) + return STOP + + async def after(self, session: Session | None = None): + session_id = ( + "$global" if not session else f"{session.account.platform}/{session.account.self_id}/{session.channel.id}" + ) + self.last_times[session_id] = datetime.now() + + def compose(self): + yield self.before, True, self.priority + yield self.after, False, self.priority + + def __call__(self, func: TCallable) -> TCallable: + return propagate(self)(func) + + +class semaphore(Propagator): + def __init__(self, count: int, limit_prompt: str | MessageChain | None = None, priority: int = 80): + self.count = count + self.limit_prompt = limit_prompt + self.priority = priority + self.semaphores: dict[str, asyncio.Semaphore] = {} + + async def before(self, session: Session | None = None): + session_id = ( + "$global" if not session else f"{session.account.platform}/{session.account.self_id}/{session.channel.id}" + ) + if session_id not in self.semaphores: + self.semaphores[session_id] = asyncio.Semaphore(self.count) + if not await self.semaphores[session_id].acquire(): + if session and self.limit_prompt: + await session.send(self.limit_prompt) + return STOP + + async def after(self, session: Session | None = None): + session_id = ( + "$global" if not session else f"{session.account.platform}/{session.account.self_id}/{session.channel.id}" + ) + if session_id not in self.semaphores: + self.semaphores[session_id] = asyncio.Semaphore(self.count) + self.semaphores[session_id].release() + + def compose(self): + yield self.before, True, self.priority + yield self.after, False, self.priority + + def __call__(self, func: TCallable) -> TCallable: + return propagate(self)(func) diff --git a/arclet/entari/filter/message.py b/arclet/entari/filter/message.py new file mode 100644 index 0000000..c3f3cda --- /dev/null +++ b/arclet/entari/filter/message.py @@ -0,0 +1,216 @@ +import re +from typing import Any + +from arclet.letoderea import STOP, Contexts, Propagator, deref, propagate, provide +from arclet.letoderea.utils import TCallable +from nepattern import ANY, BasePattern, MatchMode, parser +from satori import Text +from tarina import Empty + +from ..const import ITEM_MESSAGE_CONTENT +from ..message import MessageChain + + +def _prefixed(pat: BasePattern): + if pat.mode not in (MatchMode.REGEX_MATCH, MatchMode.REGEX_CONVERT): + return pat + new_pat = pat.copy() + new_pat.regex_pattern = re.compile(f"^{new_pat.pattern}") + return new_pat + + +def _suffixed(pat: BasePattern): + if pat.mode not in (MatchMode.REGEX_MATCH, MatchMode.REGEX_CONVERT): + return pat + new_pat = pat.copy() + new_pat.regex_pattern = re.compile(f"{new_pat.pattern}$") + return new_pat + + +class startswith(Propagator): + def __init__(self, prefix: Any, include: bool = False, bind: str | None = None, priority: int = 80): + """ + 前缀匹配 + + Args: + prefix: 需要匹配的前缀, 支持格式有 a|b , ['a', At(...)] 等 + include: 指示消息链是否仅返回前缀被匹配的部分, 默认为 False + bind: 指定注入返回值的参数名称,未指定则注入到所有的 MessageChain 参数中 + priority: 优先级 + """ + self.prefix = prefix + self.priority = priority + self.include = include + self.bind = bind + + pattern = BasePattern(prefix, mode=MatchMode.REGEX_MATCH) if isinstance(prefix, str) else parser(prefix) + if pattern in (ANY, Empty): + raise ValueError(prefix) + self.pattern = _prefixed(pattern) + + def providers(self): + if self.bind: + return [provide(MessageChain, self.bind, call=f"$startswith_{self.bind}", priority=4)] + return [] + + async def before(self, ctx: Contexts, message: MessageChain): + message = message.fork() + if message: + elem = message[0] + if isinstance(elem, Text) and (res := self.pattern.validate(elem.text)).success: + if self.include: + message = MessageChain(Text(str(res.value()))) + else: + message[0] = Text(elem.text[len(str(res.value())) :].lstrip()) + elif self.pattern.validate(elem).success: + if self.include: + message = MessageChain(elem) + else: + message.remove(elem) + else: + return STOP + if self.bind: + return {f"$startswith_{self.bind}": message} + if ITEM_MESSAGE_CONTENT in ctx: + return {ITEM_MESSAGE_CONTENT: message} + return {"$message": message} + + def compose(self): + yield self.before, True, self.priority + + def __call__(self, func: TCallable) -> TCallable: + return propagate(self)(func) + + +class endswith(Propagator): + def __init__(self, suffix: Any, include: bool = False, bind: str | None = None, priority: int = 80): + """ + 后缀匹配 + + Args: + suffix: 需要匹配的后缀, 支持格式有 a|b , ['a', At(...)] 等 + include: 指示消息链是否仅返回后缀被匹配的部分, 默认为 False + bind: 指定注入返回值的参数名称,未指定则注入到所有的 MessageChain 参数中 + priority: 优先级 + """ + self.suffix = suffix + self.priority = priority + self.include = include + self.bind = bind + + pattern = BasePattern(suffix, mode=MatchMode.REGEX_MATCH) if isinstance(suffix, str) else parser(suffix) + if pattern in (ANY, Empty): + raise ValueError(suffix) + self.pattern = _suffixed(pattern) + + def providers(self): + if self.bind: + return [provide(MessageChain, self.bind, call=f"$endswith_{self.bind}", priority=4)] + return [] + + async def before(self, ctx: Contexts, message: MessageChain): + message = message.fork() + if message: + elem = message[-1] + if isinstance(elem, Text) and (res := self.pattern.validate(elem.text)).success: + if self.include: + message = MessageChain(Text(str(res.value()))) + else: + message[-1] = Text(elem.text[: elem.text.rfind(str(res.value()))].rstrip()) + elif self.pattern.validate(elem).success: + if self.include: + message = MessageChain(elem) + else: + message.remove(elem) + else: + return STOP + if self.bind: + return {f"$endswith_{self.bind}": message} + if ITEM_MESSAGE_CONTENT in ctx: + return {ITEM_MESSAGE_CONTENT: message} + return {"$message": message} + + def compose(self): + yield self.before, True, self.priority + + def __call__(self, func: TCallable) -> TCallable: + return propagate(self)(func) + + +class fullmatch(Propagator): + def __init__( + self, pattern: str | tuple[str, ...], ignorecase: bool = False, bind: str = "fullmatch", priority: int = 80 + ): + """ + 完全匹配 + + Args: + pattern: 指定消息全匹配字符串元组 + ignorecase: 是否忽略大小写, 默认为 False + bind: 指定注入返回值的参数名称,默认为 "fullmatch" + priority: 优先级 + """ + if isinstance(pattern, str): + pattern = (pattern,) + self.pattern = tuple(map(str.casefold, pattern)) if ignorecase else pattern + self.ignorecase = ignorecase + self.priority = priority + self.bind = bind + + def providers(self): + if self.bind: + return [provide(str, self.bind, call=f"$fullmatch_{self.bind}", priority=4)] + return [] + + async def before(self, ctx: Contexts, message: MessageChain): + text = message.extract_plain_text() + if not text: + return STOP + text = text.casefold() if self.ignorecase else text + if text in self.pattern: + return {f"$fullmatch_{self.bind}": text} + return STOP + + def compose(self): + yield self.before, True, self.priority + + def __call__(self, func: TCallable) -> TCallable: + return propagate(self)(func) + + +class regexmatch(Propagator): + def __init__(self, pattern: str, flags: int | re.RegexFlag = 0, priority: int = 80): + """ + 正则匹配,注意正则表达式匹配使用 search 而非 match,如需从头匹配请使用 `r"^xxx"` 来确保匹配开头 + + Args: + pattern: 需要匹配的正则表达式 + flags: 正则匹配标志, 默认为 0 + priority: 优先级 + """ + self.pattern = re.compile(pattern, flags) + self.priority = priority + + def providers(self): + return [provide(re.Match, call="$regexmatch", priority=4)] + + async def before(self, ctx: Contexts, message: MessageChain): + text = message.extract_plain_text() + if not text: + return STOP + if matched := self.pattern.search(text): + return {"$regexmatch": matched} + return STOP + + def compose(self): + yield self.before, True, self.priority + + def __call__(self, func: TCallable) -> TCallable: + return propagate(self)(func) + + +def regex_origin(): + return deref(re.Match) + + +__all__ = ["startswith", "endswith", "fullmatch", "regexmatch", "regex_origin"] diff --git a/arclet/entari/filter/permission.py b/arclet/entari/filter/permission.py new file mode 100644 index 0000000..d337b74 --- /dev/null +++ b/arclet/entari/filter/permission.py @@ -0,0 +1,50 @@ +from arclet.letoderea import STOP, Propagator, propagate +from arclet.letoderea.utils import TCallable + +from ..config import EntariConfig +from ..session import Session + + +class superusers(Propagator): + + async def check(self, session: Session | None = None): + if not session: + return STOP + config = EntariConfig.instance.basic.superusers + if session.account.platform not in config: + return STOP + if not session.event.user: + return STOP + if session.event.user.id not in config[session.account.platform]: + return STOP + + def compose(self): + yield self.check, True, 50 + + def __call__(self, func: TCallable) -> TCallable: + return propagate(self)(func) + + +class admins(Propagator): + + async def check(self, session: Session | None = None): + if not session: + return STOP + if session.event.member and session.event.member.roles: + for role in session.event.member.roles: + if any(keyword in role.id.lower() for keyword in ("admin", "administrator", "owner")): + return + config = EntariConfig.instance.basic.superusers + if ( + session.account.platform in config + and session.event.user + and session.event.user.id in config[session.account.platform] + ): + return + return STOP + + def compose(self): + yield self.check, True, 50 + + def __call__(self, func: TCallable) -> TCallable: + return propagate(self)(func) diff --git a/arclet/entari/localdata.py b/arclet/entari/localdata.py index 3bacd52..f00b0e7 100644 --- a/arclet/entari/localdata.py +++ b/arclet/entari/localdata.py @@ -10,7 +10,7 @@ from .config import BasicConfModel, config_model_validate, model_field from .event.config import ConfigReload -from .plugin import PluginRole, RootlessPlugin, metadata, plugin_config +from .plugin import PluginRole, metadata, niche, plugin_config P = ParamSpec("P") @@ -108,8 +108,17 @@ class Config(BasicConfModel): base_dir: str | None = model_field(default=None, description="基础目录,默认为空,表示使用 `app_name` 作为目录名,") -@RootlessPlugin.apply("localdata", default=True) -def localdata_apply(plg: RootlessPlugin): +def update_localdata(value: dict): + conf1 = config_model_validate(Config, value) + local_data.global_path = conf1.use_global + local_data.app_name = conf1.app_name.lower() + local_data._temp_dir.cleanup() + local_data._temp_dir = TemporaryDirectory(prefix=f"{local_data.app_name}_") + local_data.base_dir = conf1.base_dir + + +@niche("localdata") +def localdata_apply(plg): metadata( "Data store interface with local directories", PluginRole.LIBRARY, @@ -131,10 +140,5 @@ def reload_config(event: ConfigReload): return if event.key != ".localdata": return - conf1 = config_model_validate(Config, event.value) - local_data.global_path = conf1.use_global - local_data.app_name = conf1.app_name.lower() - local_data._temp_dir.cleanup() - local_data._temp_dir = TemporaryDirectory(prefix=f"{local_data.app_name}_") - local_data.base_dir = conf1.base_dir + update_localdata(event.value) return True diff --git a/arclet/entari/logger.py b/arclet/entari/logger.py index 7d38460..68f6cbb 100644 --- a/arclet/entari/logger.py +++ b/arclet/entari/logger.py @@ -5,6 +5,7 @@ import logging import sys import traceback +from enum import Enum from pathlib import Path from types import TracebackType from typing import TYPE_CHECKING, cast @@ -183,15 +184,43 @@ def _hidden_upsteam(record: Record): logger.configure(patcher=_hidden_upsteam) -def loguru_exc_callback(cls: type[BaseException], val: BaseException, tb: TracebackType | None, *_, **__): - """loguru 异常回调 +class _Sentinel(Enum): + SENTINEL = "SENTINEL" - Args: - cls (Type[Exception]): 异常类 - val (Exception): 异常的实际值 - tb (TracebackType): 回溯消息 - """ - log.error.opt(exception=(cls, val, tb)).error("Exception:") + +def _parse_value_tb(exc, value, tb) -> tuple[BaseException, TracebackType | None] | tuple[None, None]: + if (value is _Sentinel.SENTINEL) != (tb is _Sentinel.SENTINEL): + raise ValueError("Both or neither of value and tb must be given") + if value is tb is _Sentinel.SENTINEL: + if exc is not None: + if isinstance(exc, BaseException): + return exc, exc.__traceback__ + raise TypeError(f"Exception expected for value, {type(exc).__name__} found") + else: + return None, None + return value, tb + + +_print_exception = traceback.print_exception + + +def loguru_print_exception( + exc: type[BaseException] | BaseException | None, + /, + value: BaseException | None = _Sentinel.SENTINEL, # type: ignore + tb: TracebackType | None = _Sentinel.SENTINEL, # type: ignore + *args, + **kwargs, +): + colorize = kwargs.pop("colorize", True) + value, tb = _parse_value_tb(exc, value, tb) + if colorize: + exc_type = type(value) if value is not None else Exception + log.error.opt(exception=(exc_type, value, tb), colors=colorize).error("Exception:") + elif isinstance(exc, BaseException): + _print_exception(exc, **kwargs) + else: + _print_exception(exc, value, tb, *args, **kwargs) def loguru_print_trace(te: Trace): @@ -216,9 +245,20 @@ class FakeTraceback: log.error.opt(exception=(te.exc_type, te.exc_value, cast(TracebackType, FakeTraceback()))).error("Exception:") -traceback.print_exception = loguru_exc_callback +def loguru_excepthook(cls: type[BaseException], val: BaseException, tb: TracebackType | None): + """loguru 异常回调 + + Args: + cls (Type[Exception]): 异常类 + val (Exception): 异常的实际值 + tb (TracebackType): 回溯消息 + """ + log.error.opt(exception=(cls, val, tb)).error("Exception:") + + +traceback.print_exception = loguru_print_exception ExceptionHandler.print_trace = loguru_print_trace -sys.excepthook = loguru_exc_callback +sys.excepthook = loguru_excepthook def apply_log_save( diff --git a/arclet/entari/message.py b/arclet/entari/message.py index 3b3c02e..3733068 100644 --- a/arclet/entari/message.py +++ b/arclet/entari/message.py @@ -1,10 +1,10 @@ from __future__ import annotations -from collections.abc import Awaitable, Callable, Iterable, Sequence +from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSequence, Sequence from copy import deepcopy from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar, Union, overload -from typing_extensions import Self, SupportsIndex +from typing import TYPE_CHECKING, Any, Self, TypeAlias, TypeVar, Union, overload +from typing_extensions import SupportsIndex from satori import select as satori_select from satori.element import At, Custom, Element, Emoji, Link, Quote, Raw, Resource, Sharp, Style, Text, transform @@ -27,33 +27,20 @@ MessageContainer = Union[str, Element, Sequence["MessageContainer"], "MessageChain[Element]"] -class MessageChain(list[TE]): - """消息序列 - - Args: - message: 消息内容 - """ +class MessageChain(MutableSequence[TE]): + """消息链, 被用于承载整个消息内容的数据结构, 包含有一有序列表, 包含有继承了 Element 的各式类实例.""" @overload def __init__(self): ... @overload - def __init__(self: MessageChain[Text], message: str): ... - - @overload - def __init__(self, message: TE): ... - - @overload - def __init__(self: MessageChain[TE1], message: TE1): ... + def __init__(self, message: TE | Iterable[TE] | Sequence[TE]): ... @overload - def __init__(self, message: Iterable[TE]): ... + def __init__(self: MessageChain[TE1], message: TE1 | Iterable[TE1] | Sequence[TE1]): ... @overload - def __init__(self: MessageChain[TE1], message: Iterable[TE1]): ... - - @overload - def __init__(self: MessageChain[Text], message: Iterable[str]): ... + def __init__(self: MessageChain[Text], message: str | Sequence[str]): ... @overload def __init__(self: MessageChain[Text | TE1], message: Iterable[str | TE1]): ... @@ -62,7 +49,13 @@ def __init__( self: MessageChain[Element], message: Iterable[str | TE] | str | TE | None = None, ): - super().__init__() + """从传入的序列(可以是元组 tuple, 也可以是列表 list) 创建消息链. + Args: + message (Iterable[str | TE] | str | TE): 包含且仅包含消息元素和字符串的序列 + Returns: + MessageChain: 以传入的序列作为所承载消息的消息链 + """ + self.content: list[TE] = [] if message: if isinstance(message, (str, Element)): self.__iadd__(message) @@ -71,32 +64,48 @@ def __init__( self.__iadd__(i) def __str__(self) -> str: - return "".join(str(elem) for elem in self) + """获取以字符串形式表示的消息链, 且趋于通常你见到的样子. + Returns: + str: 以字符串形式表示的消息链 + """ + return "".join(str(elem) for elem in self.content) def __repr__(self) -> str: - return "[" + ", ".join(repr(elem) for elem in self) + "]" + """获取以字符串形式表示的消息链的详细信息. + Returns: + str: 以字符串形式表示的消息链的详细信息 + """ + return "[" + ", ".join(repr(elem) for elem in self.content) + "]" @overload def __add__(self, other: str) -> MessageChain[TE | Text]: ... @overload - def __add__(self, other: TE | Iterable[TE]) -> MessageChain[TE]: ... + def __add__(self, other: TE | Iterable[TE] | Sequence[TE]) -> MessageChain[TE]: ... @overload - def __add__(self, other: TE1 | Iterable[TE1]) -> MessageChain[TE | TE1]: ... + def __add__(self, other: TE1 | Iterable[TE1] | Sequence[TE1]) -> MessageChain[TE | TE1]: ... def __add__(self, other: str | TE | TE1 | Iterable[TE | TE1]) -> MessageChain: - result: MessageChain = self.fork() + """将另一个消息段或消息链添加到当前消息链. + + Args: + other: 要添加的消息段或消息链 + + Returns: + 添加后的消息链 + """ + result: MessageChain[Element] = self.fork() # type: ignore if isinstance(other, str): - if result and isinstance(text := result[-1], Text): - result[-1] = Text(text.text + other) + if result.content and isinstance(text := result[-1], Text): + result.content[-1] = Text(text.text + other) else: - result.append(Text(other)) + result.content.append(Text(other)) elif isinstance(other, Element): - if result and isinstance(result[-1], Text) and isinstance(other, Text): - result[-1] = Text(result[-1].text + other.text) + if result.content and isinstance(text := result[-1], Text) and isinstance(other, Text): + result.content[-1] = Text(text.text + other.text) else: - result.append(other) + result.content.append(other) elif isinstance(other, Iterable): for elem in other: result += elem @@ -108,10 +117,10 @@ def __add__(self, other: str | TE | TE1 | Iterable[TE | TE1]) -> MessageChain: def __radd__(self, other: str) -> MessageChain[Text | TE]: ... @overload - def __radd__(self, other: TE | Iterable[TE]) -> MessageChain[TE]: ... + def __radd__(self, other: TE | Iterable[TE] | Sequence[TE]) -> MessageChain[TE]: ... @overload - def __radd__(self, other: TE1 | Iterable[TE1]) -> MessageChain[TE1 | TE]: ... + def __radd__(self, other: TE1 | Iterable[TE1] | Sequence[TE1]) -> MessageChain[TE1 | TE]: ... def __radd__(self, other: str | TE1 | Iterable[TE1]) -> MessageChain: result = MessageChain(other) @@ -119,15 +128,15 @@ def __radd__(self, other: str | TE1 | Iterable[TE1]) -> MessageChain: def __iadd__(self, other: str | TE | Iterable[TE]) -> Self: if isinstance(other, str): - if self and isinstance(text := self[-1], Text): - list.__setitem__(self, -1, Text(text.text + other)) + if self.content and isinstance(text := self[-1], Text): + self.content[-1] = Text(text.text + other) # type: ignore else: - self.append(Text(other)) # type: ignore + self.content.append(Text(other)) # type: ignore elif isinstance(other, Element): - if self and (isinstance(text := self[-1], Text) and isinstance(other, Text)): - list.__setitem__(self, -1, Text(text.text + other.text)) + if self.content and (isinstance(text := self[-1], Text) and isinstance(other, Text)): + self.content[-1] = Text(text.text + other.text) # type: ignore else: - self.append(other) + self.content.append(other) elif other: for elem in other: self.__iadd__(elem) @@ -136,7 +145,7 @@ def __iadd__(self, other: str | TE | Iterable[TE]) -> Self: return self @overload - def __getitem__(self, args: type[TE1]) -> MessageChain[TE1]: + def __getitem__(self, args: type[TE1], /) -> MessageChain[TE1]: """获取仅包含指定消息段类型的消息 Args: @@ -147,7 +156,7 @@ def __getitem__(self, args: type[TE1]) -> MessageChain[TE1]: """ @overload - def __getitem__(self, args: tuple[type[TE1], int]) -> TE1: + def __getitem__(self, args: tuple[type[TE1], int], /) -> TE1: """索引指定类型的消息段 Args: @@ -158,7 +167,7 @@ def __getitem__(self, args: tuple[type[TE1], int]) -> TE1: """ @overload - def __getitem__(self, args: tuple[type[TE1], slice]) -> MessageChain[TE1]: + def __getitem__(self, args: tuple[type[TE1], slice], /) -> MessageChain[TE1]: """切片指定类型的消息段 Args: @@ -169,7 +178,7 @@ def __getitem__(self, args: tuple[type[TE1], slice]) -> MessageChain[TE1]: """ @overload - def __getitem__(self, args: int) -> TE: + def __getitem__(self, args: int, /) -> TE: """索引消息段 Args: @@ -180,7 +189,7 @@ def __getitem__(self, args: int) -> TE: """ @overload - def __getitem__(self, args: slice) -> Self: + def __getitem__(self, args: slice, /) -> Self: """切片消息段 Args: @@ -196,35 +205,72 @@ def __getitem__( ) -> TE | TE1 | MessageChain[TE1] | Self: arg1, arg2 = args if isinstance(args, tuple) else (args, None) if isinstance(arg1, int) and arg2 is None: - return super().__getitem__(arg1) + return self.content[arg1] if isinstance(arg1, slice) and arg2 is None: - return MessageChain(super().__getitem__(arg1)) # type: ignore + return MessageChain(self.content[arg1]) # type: ignore if TYPE_CHECKING: assert not isinstance(arg1, slice | int) if issubclass(arg1, Element) and arg2 is None: - return MessageChain(elem for elem in self if isinstance(elem, arg1)) # type: ignore + return MessageChain(elem for elem in self.content if isinstance(elem, arg1)) # type: ignore if issubclass(arg1, Element) and isinstance(arg2, int): - return [elem for elem in self if isinstance(elem, arg1)][arg2] + return [elem for elem in self.content if isinstance(elem, arg1)][arg2] if issubclass(arg1, Element) and isinstance(arg2, slice): - return MessageChain([elem for elem in self if isinstance(elem, arg1)][arg2]) # type: ignore + return MessageChain([elem for elem in self.content if isinstance(elem, arg1)][arg2]) # type: ignore raise ValueError("Incorrect arguments to slice") # pragma: no cover - def __contains__(self, value: str | Element | type[Element]) -> bool: - """检查消息段是否存在 + def __setitem__(self, index: int, value: TE | str, /) -> None: + if isinstance(value, str): + value = Text(value) # type: ignore + self.content[index] = value # type: ignore + + def __delitem__(self, index: int, /) -> None: + del self.content[index] + + def __contains__(self, item: str | Element | type[Element] | Self | Sequence[str | Element]) -> bool: + """判断消息链中是否含有特定的内容. Args: - value: 消息段或消息段类型 + item (str | Element | type[Element] | Self | Sequence[str | Element]): 需判断内容. Returns: 消息内是否存在给定消息段或给定类型的消息段 """ - if isinstance(value, type): - return not not next((elem for elem in self if isinstance(elem, value)), None) - if isinstance(value, str): - value = Text(value) - return super().__contains__(value) + if isinstance(item, type): + return not not next((elem for elem in self.content if isinstance(elem, item)), None) + if isinstance(item, Element): + return item in self.merge().content + if isinstance(item, (MessageChain, Sequence)): + return not not self.index_sub(item) + + raise ValueError(f"{item} is not an acceptable argument!") + + def merge(self, *, copy: bool = True) -> Self: + """合并相邻的 Text 项, 选择返回一个新的消息链实例 + + Returns: + MessageChain: 得到的新的消息链实例, 里面不应存在有任何的相邻的 Text 元素. + """ + + result = [] + + texts = [] + for i in self.content: + if not isinstance(i, Text): + if texts: + result.append(Text("".join(texts))) + texts.clear() # 清空缓存 + result.append(i) + else: + texts.append(i.text) + if texts: + result.append(Text("".join(texts))) + texts.clear() # 清空缓存 + if copy: + return self.__class__(result) + self.content.clear() + self.content.extend(result) + return self - def has(self, value: str | Element | type[Element]) -> bool: - return value in self + has = __contains__ def index(self, value: str | Element | type[Element], *args: SupportsIndex) -> int: """索引消息段 @@ -243,114 +289,177 @@ def index(self, value: str | Element | type[Element], *args: SupportsIndex) -> i first_elemment = next((elem for elem in self if isinstance(elem, value)), None) if first_elemment is None: raise ValueError(f"Element with type {value!r} is not in message") - return super().index(first_elemment, *args) + return self.content.index(first_elemment, *args) # type: ignore if isinstance(value, str): value = Text(value) - return super().index(value, *args) # type: ignore + return self.content.index(value, *args) # type: ignore - def get(self, type_: type[TE], count: int | None = None) -> MessageChain[TE]: - """获取指定类型的消息段 + def index_sub(self, sub: MessageChain | Sequence[str | Element]) -> list[int]: + """判断消息链是否含有子链. 使用 KMP 算法. Args: - type_: 消息段类型 - count: 获取个数 + sub (MessageChain | Sequence[str | Element]): 要判断的子链. Returns: - 构建的新消息 + List[int]: 所有找到的下标. + """ + + def unzip(seq: Sequence[str | Element]) -> list[str | Element]: + res: list[str | Element] = [] + for e in seq: + if isinstance(e, Text): + res.extend(e.text) + elif isinstance(e, str): + res.extend(e) + else: + res.append(e) + return res + + pattern: list[str | Element] = unzip(sub.content) if isinstance(sub, MessageChain) else unzip(sub) + + match_target: list[str | Element] = unzip(self.content) + + if len(match_target) < len(pattern): + return [] + + fallback: list[int] = [0 for _ in pattern] + current_fb: int = 0 # current fallback index + for i in range(1, len(pattern)): + while current_fb and pattern[i] != pattern[current_fb]: + current_fb = fallback[current_fb - 1] + if pattern[i] == pattern[current_fb]: + current_fb += 1 + fallback[i] = current_fb + + match_index: list[int] = [] + ptr = 0 + for i, e in enumerate(match_target): + while ptr and e != pattern[ptr]: + ptr = fallback[ptr - 1] + if e == pattern[ptr]: + ptr += 1 + if ptr == len(pattern): + match_index.append(i - ptr + 1) + ptr = fallback[ptr - 1] + return match_index + + def get(self, element_class: type[TE1], count: int | None = None) -> MessageChain[TE1]: + """ + 获取消息链中所有特定类型的消息元素 + + Args: + element_class (type[E]): 指定的消息元素的类型, 例如 "Text", "At", "Image" 等. + count (int, optional): 至多获取的元素个数 + + Returns: + MessageChain[E]: 获取到的符合要求的所有消息元素; 另: 可能是空列表([]). """ if count is None: - return self[type_] + return self[element_class] - iterator, filtered = (elem for elem in self if isinstance(elem, type_)), MessageChain() - for _ in range(count): - elem = next(iterator, None) - if elem is None: - break - filtered.append(elem) - return filtered # type: ignore + return MessageChain(elem for elem in self.content if isinstance(elem, element_class))[:count] # type: ignore + + def get_one(self, element_class: type[TE1], index: int) -> TE1: + """获取消息链中第 index + 1 个特定类型的消息元素 + Args: + element_class (type[Element]): 指定的消息元素的类型, 例如 "Text", "At", "Image" 等. + index (int): 索引, 从 0 开始数 + Returns: + T: 消息链第 index + 1 个特定类型的消息元素 + """ + return self.get(element_class)[index] + + def get_first(self, element_class: type[TE1]) -> TE1: + """获取消息链中第 1 个特定类型的消息元素 + Args: + element_class (type[Element]): 指定的消息元素的类型, 例如 "Text", "At", "Image" 等. + Returns: + T: 消息链第 1 个特定类型的消息元素 + """ + return self.get(element_class)[0] + + def join(self, *chains: Self | Iterable[Self]) -> Self: + """将多个消息链连接起来, 并在其中插入自身. + + Args: + *chains (Iterable[MessageChain]): 要连接的消息链. + + Returns: + MessageChain: 连接后的消息链, 已对文本进行合并. + """ + result: list[TE] = [] + list_chains: list[MessageChain] = [] + for chain in chains: + if isinstance(chain, MessageChain): + list_chains.append(chain) + else: + list_chains.extend(chain) + + for chain in list_chains: + if chain is not list_chains[0]: + result.extend(deepcopy(self.content)) + result.extend(deepcopy(chain.content)) + return self.__class__(result).merge() def count(self, value: type[Element] | str | Element) -> int: - """计算指定消息段的个数 + """计算指定消息元素的个数 Args: - value: 消息段或消息段类型 + value (str | Element | type[Element]): 消息元素或消息元素类型 Returns: - 个数 + int: 消息元素的个数 """ if isinstance(value, str): value = Text(value) return ( len(self[value]) # type: ignore if isinstance(value, type) - else super().count(value) # type: ignore + else self.content.count(value) # type: ignore ) def only(self, value: type[Element] | str | Element) -> bool: - """检查消息中是否仅包含指定消息段 + """检查消息中是否仅包含指定消息元素 Args: - value: 指定消息段或消息段类型 + value: 指定消息元素或消息元素类型 Returns: - 是否仅包含指定消息段 + bool: 是否仅包含指定消息元素 """ if isinstance(value, type): - return all(isinstance(elem, value) for elem in self) + return all(isinstance(elem, value) for elem in self.content) if isinstance(value, str): value = Text(value) - return all(elem == value for elem in self) - - def join(self, iterable: Iterable[TE1 | MessageChain[TE1]]) -> MessageChain[TE | TE1]: - """将多个消息连接并将自身作为分割 - - Args: - iterable: 要连接的消息 - - Returns: - 连接后的消息 - """ - ret = MessageChain() - for index, msg in enumerate(iterable): - if index != 0: - ret.extend(self) - if isinstance(msg, Element): - ret.append(msg) - else: - ret.extend(msg.copy()) - return ret # type: ignore + return all(elem == value for elem in self.content) - def copy(self) -> MessageChain[TE]: + def copy(self) -> Self: """深拷贝消息""" return deepcopy(self) - def fork(self) -> MessageChain[TE]: + def fork(self) -> Self: """浅拷贝消息""" new = self.__class__() - list.extend(new, self) + new.content = self.content[:] return new - def include(self, *types: type[Element]) -> MessageChain: - """过滤消息 - + def exclude(self, *types: type[Element]) -> Self: + """将除了在给出的消息元素类型中符合的消息元素重新包装为一个新的消息链 Args: - types: 包含的消息段类型 - + *types (type[Element]): 将排除在外的消息元素类型 Returns: - 新构造的消息 + MessageChain: 返回的消息链中不包含参数中给出的消息元素类型 """ - return MessageChain(elem for elem in self if elem.__class__ in types) - - def exclude(self, *types: type[Element]) -> MessageChain: - """过滤消息 + return self.__class__([i for i in self.content if not isinstance(i, types)]) + def include(self, *types: type[Element]) -> Self: + """将只在给出的消息元素类型中符合的消息元素重新包装为一个新的消息链 Args: - types: 不包含的消息段类型 - + *types (type[Element]): 将只包含在内的消息元素类型 Returns: - 新构造的消息 + MessageChain: 返回的消息链中只包含参数中给出的消息元素类型 """ - return MessageChain(elem for elem in self if elem.__class__ not in types) + return self.__class__([i for i in self.content if isinstance(i, types)]) def extract_plain_text(self) -> str: """提取消息内纯文本消息""" @@ -363,7 +472,13 @@ def filter(self, predicate: Callable[[TE], bool]) -> MessageChain[TE]: Args: predicate: 过滤函数 """ - return MessageChain(elem for elem in self if predicate(elem)) + return MessageChain(elem for elem in self.content if predicate(elem)) + + def __iter__(self) -> Iterator[TE]: + yield from self.content + + def __len__(self) -> int: + return len(self.content) @overload def map(self, func: Callable[[TE], TE1]) -> MessageChain[TE1]: ... @@ -374,7 +489,7 @@ def map(self, func: Callable[[TE], T]) -> list[T]: ... def map(self, func: Callable[[TE], TE1] | Callable[[TE], T]) -> MessageChain[TE1] | list[T]: result1 = [] result2 = [] - for elem in self: + for elem in self.content: result = func(elem) if isinstance(result, Element): result1.append(result) @@ -418,7 +533,7 @@ def transform(self, rules: SyncVisitor[S], session: S = None) -> MessageChain: 转换后的消息 """ output = MessageChain() - for elem in self: + for elem in self.content: result = self._visit_sync(elem, rules, session) if result is True: children = MessageChain(elem.children) @@ -428,7 +543,7 @@ def transform(self, rules: SyncVisitor[S], session: S = None) -> MessageChain: if isinstance(result, str | Element): output += result else: - output.extend(result) + output.content.extend(result) return output async def transform_async(self, rules: AsyncVisitor[S], session: S = None) -> MessageChain: @@ -442,7 +557,7 @@ async def transform_async(self, rules: AsyncVisitor[S], session: S = None) -> Me 转换后的消息 """ output = MessageChain() - for elem in self: + for elem in self.content: result = await self._visit_async(elem, rules, session) if result is True: children = MessageChain(elem.children) @@ -467,7 +582,7 @@ def split(self, pattern: str = " ") -> list[Self]: result: list[Self] = [] tmp = [] - for seg in self: + for seg in self.content: if isinstance(seg, Text): split_result = seg.text.split(pattern) for index, split_text in enumerate(split_result): @@ -483,11 +598,7 @@ def split(self, pattern: str = " ") -> list[Self]: tmp = [] return result - def replace( - self, - old: str, - new: str, - ) -> Self: + def replace(self, old: str, new: str) -> Self: """替换消息中有关的文本 Args: @@ -498,7 +609,7 @@ def replace( UniMessage: 修改后的消息链, 若未替换则原样返回. """ result_list: list[TE] = [] - for seg in self: + for seg in self.content: if isinstance(seg, Text): result_list.append(seg.__class__(seg.text.replace(old, new))) else: @@ -515,9 +626,9 @@ def startswith(self, string: str) -> bool: bool: 是否以给出的字符串开头 """ - if not self or not isinstance(self[0], Text): + if not self.content or not isinstance(text := self.content[0], Text): return False - return list.__getitem__(self, 0).text.startswith(string) + return text.text.startswith(string) def endswith(self, string: str) -> bool: """判断消息链是否以给出的字符串结尾 @@ -529,102 +640,236 @@ def endswith(self, string: str) -> bool: bool: 是否以给出的字符串结尾 """ - if not self or not isinstance(self[-1], Text): + if not self.content or not isinstance(text := self.content[-1], Text): return False - return list.__getitem__(self, -1).text.endswith(string) + return text.text.endswith(string) + + def append(self, element: TE | str) -> None: + """ + 向消息链最后追加单个元素 + + Args: + element (Element): 要添加的元素 + + Returns: + None + """ + self.content.append(Text(element) if isinstance(element, str) else element) # type: ignore + + def insert(self, index: int, value: Element | str, /) -> None: + if isinstance(value, str): + value = Text(value) + self.content.insert(index, value) # type: ignore + + def extend(self, values: Iterable[Self | TE | Sequence[TE | str]]) -> None: + """ + 向消息链最后添加元素/元素列表/消息链 + + Args: + *values (MessageChain | Element | list[Element | str]): 要添加的元素/元素容器. + + Returns: + MessageChain: copy = True 时返回副本, 否则返回自己的引用. + """ + result = [] + for i in values: + if isinstance(i, Element): + result.append(i) + elif isinstance(i, str): + result.append(Text(i)) + elif isinstance(i, MessageChain): + result.extend(i.content) + else: + for e in i: + if isinstance(e, str): + result.append(Text(e)) + else: + result.append(e) + self.content.extend(result) + + def empty(self) -> bool: + """ + 判断消息链是否为空,包括判断是否仅包含空字符串。 - def removeprefix(self, prefix: str) -> Self: + Returns: + bool: 判断结果。 + """ + + return not bool(self.content and str(self)) + + def pop(self, index: int = -1, /) -> TE: + """移除并返回指定位置的元素,默认移除最后一个元素。 + + Args: + index (int, optional): 要移除的元素的索引,默认为 -1(最后一个元素)。 + + Returns: + TE: 被移除的元素。 + """ + return self.content.pop(index) # type: ignore + + def removeprefix(self, prefix: str, *, copy: bool = True) -> Self: """移除消息链前缀. Args: prefix (str): 要移除的前缀. + copy (bool, optional): 是否在副本上修改, 默认为 True. Returns: - UniMessage: 修改后的消息链. + MessageChain: 修改后的消息链, 若未移除则原样返回. """ - copy = list.copy(self) - if not copy: - return self.__class__(copy) - seg = copy[0] - if not isinstance(seg, Text): - return self.__class__(copy) - if seg.text.startswith(prefix): - seg = seg.__class__(seg.text[len(prefix) :]) - if not seg.text: - copy.pop(0) - else: - copy[0] = seg - return self.__class__(copy) + elements = deepcopy(self.content) if copy else self.content + if not elements: + return self.copy() if copy else self + elem = elements[0] + if not isinstance(elem, Text): + return self.copy() if copy else self + if elem.text.startswith(prefix): + elem.text = elem.text[len(prefix) :] + if not elem.text: + elements.pop(0) + if copy: + return self.__class__(elements) + self.content.clear() + self.content.extend(elements) + return self - def removesuffix(self, suffix: str) -> Self: + def removesuffix(self, suffix: str, *, copy: bool = True) -> Self: """移除消息链后缀. Args: suffix (str): 要移除的后缀. + copy (bool, optional): 是否在副本上修改, 默认为 True. Returns: - UniMessage: 修改后的消息链. + MessageChain: 修改后的消息链, 若未移除则原样返回. """ - copy = list.copy(self) - if not copy: - return self.__class__(copy) - seg = copy[-1] - if not isinstance(seg, Text): - return self.__class__(copy) - if seg.text.endswith(suffix): - seg = seg.__class__(seg.text[: -len(suffix)]) - if not seg.text: - copy.pop(-1) - else: - copy[-1] = seg - return self.__class__(copy) - - def strip(self, *segments: str | Element | type[Element]) -> Self: - return self.lstrip(*segments).rstrip(*segments) - - def lstrip(self, *segments: str | Element | type[Element]) -> Self: - types = [i for i in segments if not isinstance(i, str)] or [] - chars = "".join([i for i in segments if isinstance(i, str)]) or None - copy = list.copy(self) - if not copy: - return self.__class__(copy) - while copy: - seg = copy[0] - if seg in types or seg.__class__ in types: - copy.pop(0) - elif isinstance(seg, Text): - seg = seg.__class__(seg.text.lstrip(chars)) - if not seg.text: - copy.pop(0) + elements = deepcopy(self.content) if copy else self.content + if not elements: + return self.copy() if copy else self + elem = elements[-1] + if not isinstance(elem, Text): + return self.copy() if copy else self + if elem.text.endswith(suffix): + elem.text = elem.text[: -len(suffix)] + if not elem.text: + elements.pop(-1) + if copy: + return self.__class__(elements) + self.content.clear() + self.content.extend(elements) + return self + + def strip(self, *elements: str | type[Element] | Element, copy: bool = True) -> Self: + return self.lstrip(*elements, copy=copy).rstrip(*elements, copy=copy) + + def lstrip(self, *elements: str | type[Element] | Element, copy: bool = True) -> Self: + types = [i for i in elements if not isinstance(i, str)] or [] + chars = "".join([i for i in elements if isinstance(i, str)]) or None + content = deepcopy(self.content) if copy else self.content + if not content: + return self.copy() if copy else self + while content: + elem = content[0] + if elem in types or elem.__class__ in types: + content.pop(0) + elif isinstance(elem, Text): + text = elem.text.lstrip(chars) + if not text: + content.pop(0) continue - else: - copy[0] = seg + elem.text = text break else: break - return self.__class__(copy) - - def rstrip(self, *segments: str | Element | type[Element]) -> Self: - types = [i for i in segments if not isinstance(i, str)] or [] - chars = "".join([i for i in segments if isinstance(i, str)]) or None - copy = list.copy(self) - if not copy: - return self.__class__(copy) - while copy: - seg = copy[-1] - if seg in types or seg.__class__ in types: - copy.pop(-1) - elif isinstance(seg, Text): - seg = seg.__class__(seg.text.rstrip(chars)) - if not seg.text: - copy.pop(-1) + if copy: + return self.__class__(content) + self.content.clear() + self.content.extend(content) + return self + + def rstrip(self, *elements: str | type[Element] | Element, copy: bool = True) -> Self: + types = [i for i in elements if not isinstance(i, str)] or [] + chars = "".join([i for i in elements if isinstance(i, str)]) or None + content = deepcopy(self.content) if copy else self.content + if not content: + return self.copy() if copy else self + while content: + elem = content[-1] + if elem in types or elem.__class__ in types: + content.pop(-1) + elif isinstance(elem, Text): + text = elem.text.rstrip(chars) + if not text: + content.pop(-1) continue - else: - copy[-1] = seg + elem.text = text break else: break - return self.__class__(copy) + if copy: + return self.__class__(content) + self.content.clear() + self.content.extend(content) + return self + + def replace_chain(self, old: Sequence[Element], new: Sequence[Element]) -> Self: + """替换消息链中的一部分. (在副本上操作) + + Args: + old (MessageChain): 要替换的消息链. + new (MessageChain): 替换后的消息链. + + Returns: + MessageChain: 修改后的消息链, 若未替换则原样返回. + """ + if not isinstance(old, MessageChain): + old = MessageChain(old) + if not isinstance(new, MessageChain): + new = MessageChain(new) + index_list: list[int] = self.index_sub(old) + + def unzip(chain: MessageChain) -> list[str | Element]: + unzipped: list[str | Element] = [] + for e in chain.content: + if isinstance(e, Text): + unzipped.extend(e.text) + else: + unzipped.append(e) + return unzipped + + unzipped_new: list[str | Element] = unzip(new) + unzipped_old: list[str | Element] = unzip(old) + unzipped_self: list[str | Element] = unzip(self) + unzipped_result: list[str | Element] = [] + last_end: int = 0 + for start in index_list: + unzipped_result.extend(unzipped_self[last_end:start]) + last_end = start + len(unzipped_old) + unzipped_result.extend(unzipped_new) + unzipped_result.extend(unzipped_self[last_end:]) + + # Merge result + result_list: list[TE] = [] + char_stk: list[str] = [] + for v in unzipped_result: + if isinstance(v, str): + char_stk.append(v) + else: + result_list.append(Text("".join(char_stk))) # type: ignore + char_stk = [] + result_list.append(v) # type: ignore + if char_stk: + result_list.append(Text("".join(char_stk))) # type: ignore + return self.__class__(result_list) + + def __bool__(self): + return bool(self.content and str(self)) + + def __eq__(self, value: object, /): + if not isinstance(value, MessageChain): + return False + return value.content == self.content def display(self): texts = [] diff --git a/arclet/entari/plugin/__init__.py b/arclet/entari/plugin/__init__.py index d0cb445..95aa85e 100644 --- a/arclet/entari/plugin/__init__.py +++ b/arclet/entari/plugin/__init__.py @@ -3,11 +3,12 @@ import itertools import os import sys -from collections.abc import Awaitable +from collections.abc import Awaitable, Callable from pathlib import Path +from types import ModuleType from typing import TYPE_CHECKING, Any, Literal, TypeVar, overload -from arclet.letoderea import Subscriber, on, publish +from arclet.letoderea import Subscriber, on from arclet.letoderea.effect import AsyncDisposable, Disposable from arclet.letoderea.utils import Resultable from tarina import init_spec @@ -15,19 +16,18 @@ from ..config import EntariConfig, config_model_keys, config_model_validate from ..event.config import ConfigReload -from ..event.lifespan import Ready -from ..event.plugin import PluginLoadedFailed -from ..exceptions import RegisterNotInPluginError, ReusablePluginError, StaticPluginDispatchError -from ..logger import log +from ..exceptions import StaticPluginDispatchError from ..message import Fragment, MessageChain, Render from ..session import COMPONENTS, Session, component_transform -from ..utils import escape_tag +from .loader import find_plugin as find_plugin +from .loader import load_plugin as load_plugin +from .loader import reload_plugin as reload_plugin +from .loader import reload_subplugin as reload_subplugin +from .loader import unload_plugin as unload_plugin from .model import TS, Plugin, PluginDispatcher, current_plugin from .model import PluginMetadata as PluginMetadata from .model import PluginRole as PluginRole -from .model import RootlessPlugin as RootlessPlugin from .model import keeping as keeping -from .module import import_plugin from .module import package as package from .module import requires as requires from .service import plugin_service @@ -169,26 +169,6 @@ def get_plugin_commands(plug: Plugin | str | None = None) -> list[tuple[list[str return plg._extra.get("commands", []) -def get_plugin_depend_services(plug: Plugin | str | None = None) -> list[str]: - """获取指定插件依赖的所有服务的 ID - - Args: - plug (Plugin | str | None, optional): 插件对象或插件名称. Defaults to None. - """ - if isinstance(plug, Plugin): - plg = plug - elif plug in plugin_service.plugins: - plg = plugin_service.plugins[plug] - else: - plg = get_plugin(1) - injected_services = plg._extra.get("injected_services", []) - local_injected_services = plg._extra.get("local_injected_services", []) - services = injected_services[:] - for slot in local_injected_services: - services.extend(slot[1]) - return list(set(services)) - - @overload def dispatch(event: type[Resultable[T]], name: str | None = None) -> PluginDispatcher[T]: ... @overload @@ -200,108 +180,6 @@ def dispatch(event: type, name: str | None = None) -> PluginDispatcher: return get_plugin(1).dispatch(event, name=name) -def load_plugin( - path: str, config: dict | None = None, recursive_guard: set[str] | None = None, prelude: bool = False -) -> Plugin | None: - """ - 以导入路径方式加载模块 - - Args: - path (str): 模块路径 - config (dict): 模块配置 - recursive_guard (set[str]): 递归保护 - prelude (bool): 是否为前置插件 - """ - if config is not None: - config["$path"] = path - else: - for k, names in EntariConfig.instance._plugin_names.items(): - if path in names: - config = EntariConfig.instance.plugin.get(k, {}) - config["$path"] = k - break - else: - config = {"$path": path} - if prelude: - config["$static"] = True - if recursive_guard is None: - recursive_guard = set() - path = path.replace("::", "arclet.entari.builtins.") - while path in plugin_service._subplugined: - path = plugin_service._subplugined[path] - if path in plugin_service._apply: - if path in plugin_service.plugins: - return plugin_service.plugins[path] - log.plugin.trace(f"loaded rootless plugin {path!r}") - return plugin_service._apply[path][0](config) - if plug := find_plugin(path): - plugin_service._direct_plugins.add(plug.path) - return plug - try: - mod = import_plugin(path, config=config) - if not mod: - mod = next( - (import_plugin(_path, config=config) for _path in EntariConfig.instance._plugin_names.get(path, [])), - None, - ) - if not mod: - log.plugin.error(f"cannot found plugin {path!r}") - publish(PluginLoadedFailed(path)) - return - plugin_service._direct_plugins.add(mod.__name__) - if mod.__name__ in plugin_service.referents and plugin_service.referents[mod.__name__]: - referents = plugin_service.referents[mod.__name__].copy() - # plugin_service.referents[mod.__name__].clear() - for referent in referents: - if referent in recursive_guard: - continue - if referent.startswith(mod.__name__): - continue - if referent in plugin_service._subplugined and mod.__name__.startswith( - plugin_service._subplugined[referent] - ): - continue - if referent in plugin_service.plugins: - plugin_service.referents[mod.__name__].discard(referent) - log.plugin.debug(f"reloading {escape_tag(mod.__name__)}'s referent {referent!r}") - unload_plugin(referent) - if not (plug := load_plugin(referent)): - plugin_service.referents[mod.__name__].add(referent) - else: - publish(Ready(), plug._scope) - recursive_guard.add(referent) - - return mod.__plugin__ - except (ImportError, RegisterNotInPluginError, ReusablePluginError, StaticPluginDispatchError): - return - except Exception as e: - log.plugin.exception(f"failed to load plugin {path!r}: {e}", exc_info=e) - publish(PluginLoadedFailed(path)) - return - - -def load_plugins(dir_: str | os.PathLike | Path): - """加载指定目录下的所有插件""" - path = dir_ if isinstance(dir_, Path) else Path(dir_) - if not path.is_dir(): - raise NotADirectoryError(f"{path} is not a directory") - path: Path = path.resolve() # .relative_to(Path.cwd()) - syspaths = [Path(p).resolve() for p in sys.path if p] - prefixes = [p for p in syspaths if path.is_relative_to(p)] - if prefixes: - prefix = max(prefixes, key=lambda p: len(p.parts)) - else: - prefix = Path.cwd() - for p in path.iterdir(): - if p.suffix in (".py", "") and p.stem not in {"__init__", "__pycache__"}: - p = p.resolve().relative_to(prefix) - if len(p.parts) > 1: - plg = ".".join(p.parts[:-1:1]) + "." + p.stem - else: - plg = p.stem - load_plugin(plg) - - if TYPE_CHECKING: @init_spec(PluginMetadata) @@ -370,6 +248,60 @@ def _reload(event: ConfigReload): get_config = plugin_config +def register_schema( + path: str | tuple[str, ...] = "", + fragment: dict | type | Callable[[dict], dict] | None = None, + *, + replace: bool = False, +) -> None: + plugin = get_plugin(1) + plugin.register_schema(path, fragment, replace=replace) + + +def get_plugin_schema() -> dict: + plugin = get_plugin(1) + return plugin.config_schema() + + +def load_plugins(dir_: str | os.PathLike | Path): + """加载指定目录下的所有插件""" + path = dir_ if isinstance(dir_, Path) else Path(dir_) + if not path.is_dir(): + raise NotADirectoryError(f"{path} is not a directory") + path: Path = path.resolve() # .relative_to(Path.cwd()) + syspaths = [Path(p).resolve() for p in sys.path if p] + prefixes = [p for p in syspaths if path.is_relative_to(p)] + if prefixes: + prefix = max(prefixes, key=lambda p: len(p.parts)) + else: + prefix = Path.cwd() + + def _(_path: Path): + _path = _path.resolve().relative_to(prefix) + if len(_path.parts) > 1: + plg = ".".join(_path.parts[:-1:1]) + "." + _path.stem + else: + plg = _path.stem + return load_plugin(plg) + + return [_(p) for p in path.iterdir() if p.suffix in (".py", "") and p.stem not in {"__init__", "__pycache__"}] + + +def find_plugin_by_file(file: str) -> Plugin | None: + path = Path(file).resolve() + for plugin in plugin_service.plugins.values(): + if plugin.module.__file__ == str(path): + return plugin + if plugin.module.__file__ and Path(plugin.module.__file__).parent == path: + return plugin + path1 = Path(path) + while path1.parent != path1: + if str(path1) == plugin.module.__file__: + return plugin + path1 = path1.parent + return None + + def declare_static(): """声明当前插件为静态插件""" _plugin = get_plugin(1) @@ -400,43 +332,6 @@ def restore(): return get_plugin(1).restore() -def find_plugin(name: str) -> Plugin | None: - """根据插件名称查找插件""" - if name in plugin_service.plugins: - return plugin_service.plugins[name] - if name in EntariConfig.instance.plugin_prefixes: - for prefix in EntariConfig.instance.plugin_prefixes[name]: - if f"{prefix}{name}" in plugin_service.plugins: - return plugin_service.plugins[f"{prefix}{name}"] - if not name.count(".") and f"entari_plugin_{name}" in plugin_service.plugins: - return plugin_service.plugins[f"entari_plugin_{name}"] - - -def find_plugin_by_file(file: str) -> Plugin | None: - path = Path(file).resolve() - for plugin in plugin_service.plugins.values(): - if plugin.module.__file__ == str(path): - return plugin - if plugin.module.__file__ and Path(plugin.module.__file__).parent == path: - return plugin - path1 = Path(path) - while path1.parent != path1: - if str(path1) == plugin.module.__file__: - return plugin - path1 = path1.parent - return None - - -def unload_plugin(plugin: str): - plugin = plugin.replace("::", "arclet.entari.builtins.") - while plugin in plugin_service._subplugined: - plugin = plugin_service._subplugined[plugin] - if not (_plugin := find_plugin(plugin)): - return False - _plugin.dispose() - return True - - async def unload_plugin_async(plugin: str): plugin = plugin.replace("::", "arclet.entari.builtins.") while plugin in plugin_service._subplugined: @@ -491,3 +386,16 @@ def _(): return func return wrapper + + +def niche(label: str, config: dict[str, Any] | None = None): + """创建一个隔离的插件上下文, 用于在当前插件中创建一个子插件""" + try: + _plugin = get_plugin(1) + except LookupError: + _plugin = ROOT + return _plugin.niche(label, config) + + +ROOT = plugin_service.root = Plugin("$", ModuleType("$"), config={"$path": "$"}) +ROOT.module.__file__ = "__main__" diff --git a/arclet/entari/plugin/loader.py b/arclet/entari/plugin/loader.py new file mode 100644 index 0000000..d5bd5cc --- /dev/null +++ b/arclet/entari/plugin/loader.py @@ -0,0 +1,482 @@ +import ast +import asyncio +import sys +from typing import Any, overload +from weakref import ref + +from arclet.letoderea import publish + +from ..config import EntariConfig +from ..event.lifespan import Ready +from ..event.plugin import PluginLoadedFailed +from ..exceptions import RegisterNotInPluginError, ReusablePluginError, StaticPluginDispatchError +from ..logger import log +from .model import Plugin, current_plugin +from .module import import_plugin +from .service import plugin_service + + +def collect_module_level_names(nodes: ast.Module) -> set[str]: + """收集模块层表达式引用的名字(函数体除外;含装饰器/默认值/注解/基类/类级语句)""" + used: set[str] = set() + + def visit_expr(expr: ast.expr): + if isinstance(expr, ast.Name) and isinstance(expr.ctx, ast.Load): + used.add(expr.id) + return + for child in ast.iter_child_nodes(expr): + if isinstance(child, ast.expr): + visit_expr(child) + elif isinstance(child, ast.keyword) and child.value is not None: + visit_expr(child.value) + + def visit_stmt(stmt: ast.stmt): + if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)): + for dec in stmt.decorator_list: + visit_expr(dec) + for default in [*stmt.args.defaults, *[d for d in stmt.args.kw_defaults if d]]: + visit_expr(default) + for arg in stmt.args.posonlyargs + stmt.args.args + stmt.args.kwonlyargs: + if arg.annotation: + visit_expr(arg.annotation) + if stmt.args.vararg and stmt.args.vararg.annotation: + visit_expr(stmt.args.vararg.annotation) + if stmt.args.kwarg and stmt.args.kwarg.annotation: + visit_expr(stmt.args.kwarg.annotation) + return + if isinstance(stmt, ast.ClassDef): + for dec in stmt.decorator_list: + visit_expr(dec) + for base in stmt.bases: + visit_expr(base) + for kw in stmt.keywords: + visit_expr(kw.value) + for s in stmt.body: + visit_stmt(s) + return + for child in ast.iter_child_nodes(stmt): + if isinstance(child, ast.expr): + visit_expr(child) + elif isinstance(child, ast.stmt): + visit_stmt(child) + elif isinstance(child, ast.keyword) and child.value is not None: + visit_expr(child.value) + + for stmt in nodes.body: + visit_stmt(stmt) + return used + + +def _uses_module_level(plugin: Plugin, path: str) -> bool: + """检测插件是否在模块层使用 path 的绑定名(基类/模块级装饰器/顶层实例化等)""" + if not plugin._inspect: + return True + bound = { + name + for name, (target, _) in plugin_service.last_bindings(plugin.id).items() + if target == path or target.startswith(path + ".") + } + if not bound: + return False + return bool(collect_module_level_names(plugin._inspect.nodes) & bound) + + +_MISSING = object() + + +def _rebind_imports(plugin: Plugin, path: str) -> bool: + """将插件对 path(或其子树)的 import 绑定改写为新对象;False 表示有绑定无法满足(升级全量) + + 模块绑定(attr 为 None)仅在名字等同于目标模块或其末组件时写回:`import a.b` 风格的名字是顶层包, + 其子模块链由 promote 的父属性写回维护,不在此处理。 + 绑定目标为 path 的父包、且父包绑定记录将该名字解析到 path 时, + 经父模块 getattr 取新值(父包在 sorted 依赖序中先于其子模块处理,故已重绑完成)。 + """ + module = plugin.module + parent_pkg = path.rpartition(".")[0] + for name, (target, attr) in plugin_service.last_bindings(plugin.id).items(): + if target == path or target.startswith(path + "."): + if target not in plugin_service.plugins: + continue + new_module = plugin_service.plugins[target].module + if attr is None: + if name != target and name != target.rpartition(".")[-1]: + continue + value: Any = new_module + else: + value = getattr(new_module, attr, _MISSING) + if value is _MISSING: + return False + module.__dict__[name] = value + elif ( + parent_pkg + and attr is not None + and target == parent_pkg + and parent_pkg in plugin_service.bindings + and parent_pkg in plugin_service.plugins + ): + parent_chain = plugin_service.last_bindings(parent_pkg) + if parent_chain.get(attr or name, (None, None))[0] == path: + value = getattr(plugin_service.plugins[parent_pkg].module, attr or name, _MISSING) + if value is _MISSING: + return False + module.__dict__[name] = value + return True + + +def public_fingerprint(plugin: Plugin) -> str | None: + """插件公开面指纹:排序后的导出名 + 各名字的定义节点的dump(导入名仅记录类型) + + 无 `__all__` 时导出名 = 非下划线开头的模块 dict 名字;定义节点 dump 覆盖函数体与类体,任何行为差异都反映为指纹差异。 + """ + if not plugin._inspect: + return None + module = plugin.module + names = getattr(module, "__all__", None) + if names is None: + names = sorted(n for n in module.__dict__ if not n.startswith("_")) + else: + names = sorted(names) + node_map: dict[str, ast.stmt] = {} + for stmt in plugin._inspect.nodes.body: + if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + node_map[stmt.name] = stmt + elif isinstance(stmt, ast.Assign): + for target in stmt.targets: + if isinstance(target, ast.Name): + node_map[target.id] = stmt + elif isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name): + node_map[stmt.target.id] = stmt + parts: list[str] = [] + for name in names: + if node := node_map.get(name): + parts.append(f"{name}:{ast.dump(node, include_attributes=False)}") + else: + value = module.__dict__.get(name, None) + parts.append(f"{name}:imported:{type(value).__name__}") + return "\n".join(parts) + + +def _fingerprint_changed(plugin: Plugin) -> bool: + """比较插件公开面指纹(存储的旧指纹 vs 现算新指纹),并更新存储 + + 旧指纹缺失(未存储过)时视为已变化:无法证明公开面未变 → 保守级联。 + """ + path = plugin.path + old = plugin_service.fingerprints.get(path) + new = public_fingerprint(plugin) + plugin_service.fingerprints[path] = new or "" + return old is None or old != new + + +def _rebind_dep(dep: Plugin, path: str): + """重绑依赖方对 path 的 import 绑定并恢复其可用状态""" + if _rebind_imports(dep, path): + log.plugin.debug(f"rebound {dep.id!r}'s imports on {path}") + dep.enable() + else: + log.plugin.warning(f"cannot satisfy {dep.id!r}'s imports on {path}, falling back to full reload") + _cascade_dep(dep.id, plugin_service.referents.get(path, set()), set()) + + +def _cascade_dep(dep_id: str, referent_set: set[str], recursive_guard: set[str]): + """对依赖方执行全量级联(unload + load + Ready),失败时恢复 referent 边""" + referent_set.discard(dep_id) + log.plugin.debug(f"reloading {dep_id!r}") + _unload_for_reload(dep_id) + if not (result := load_plugin(dep_id)): + referent_set.add(dep_id) + else: + publish(Ready(), result.plugin._scope) + recursive_guard.add(dep_id) + + +def _handle_dependents(plugin: Plugin, recursive_guard: set[str] | None = None): + """插件完成(重)加载后处理依赖方:公开面未变 → 全部重绑;否则按模块层使用细粒度重绑或级联 + + 依赖方 = referents 图与绑定索引的并集;公开面指纹未变时新对象与旧对象行为等价,模块层绑定无需重执行; + 指纹变化时仅模块层使用方升级全量,惰性依赖方重绑即可。 + """ + if recursive_guard is None: + recursive_guard = set() + path = plugin.path + dependents = set(plugin_service.referents.get(path, ())) | set(plugin_service.dependents_of(path, ensure=False)) + if not dependents: + return + surface_changed = _fingerprint_changed(plugin) + referent_set = plugin_service.referents.setdefault(path, set()) + current = current_plugin.get(None) + for dep_id in plugin_service.topo_dependents(dependents): + if dep_id in recursive_guard: + continue + if current is not None and (dep_id == current.id or dep_id.startswith(current.id + ".")): + # 如果依赖方是当前正在加载的插件或其子插件,说明它们在同一 load_plugins 调用链中被导入, + # 且已在当前上下文中处理过依赖关系。 + continue + if dep_id == path or dep_id.startswith(path + "."): + continue + dep = plugin_service.plugins.get(dep_id) + if dep is None: + if not (result := load_plugin(dep_id)): + referent_set.add(dep_id) + continue + publish(Ready(), result.plugin._scope) + recursive_guard.add(dep_id) + continue + if not surface_changed: + _rebind_dep(dep, path) + continue + if _uses_module_level(dep, path): + _cascade_dep(dep_id, referent_set, recursive_guard) + else: + _rebind_dep(dep, path) + + +class _LoadResult: + __slots__ = ("_plugin", "_error", "_desc") + + @overload + def __init__(self, *, plugin: Plugin): ... + + @overload + def __init__(self, *, error: Exception): ... + + def __init__(self, plugin: Plugin | None = None, error: Exception | None = None): + if plugin is None and error is None: + raise ValueError("either plugin or error must be provided") + if plugin is not None and error is not None: + raise ValueError("only one of plugin or error can be provided") + self._plugin = ref(plugin) if plugin is not None else None + self._desc = repr(plugin) if plugin is not None else "None" + self._error = error + + @property + def plugin(self) -> Plugin: + if self._plugin is None: + raise RuntimeError("plugin not loaded") + if plug := self._plugin(): + return plug + raise RuntimeError("plugin has been garbage collected") + + @property + def error(self) -> Exception: + if self._error is None: + raise RuntimeError("plugin loaded successfully, no error") + return self._error + + def __bool__(self): + return self._plugin is not None + + def __repr__(self) -> str: + return f"" + + +def load_plugin( + path: str, + config: dict | None = None, + recursive_guard: set[str] | None = None, + prelude: bool = False, + staged: bool = False, +) -> _LoadResult: + """ + 以导入路径方式加载模块 + + Args: + path (str): 模块路径 + config (dict): 模块配置 + recursive_guard (set[str]): 递归保护 + prelude (bool): 是否为前置插件 + staged (bool): 是否为暂存加载 + """ + if config is not None: + config["$path"] = path + else: + for k, names in EntariConfig.instance._plugin_names.items(): + if path in names: + config = EntariConfig.instance.plugin.get(k, {}) + config["$path"] = k + break + else: + config = {"$path": path} + if prelude: + config["$static"] = True + if recursive_guard is None: + recursive_guard = set() + path = path.replace("::", "arclet.entari.builtins.") + if path.startswith(".") and f"${path}" in plugin_service.root.subplugins: + rootless = plugin_service.plugins[f"${path}"] + rootless.config = config + return _LoadResult(plugin=rootless) + while path in plugin_service._subplugined: + path = plugin_service._subplugined[path] + if not staged and (plug := find_plugin(path)): + plugin_service._direct_plugins.add(plug.path) + return _LoadResult(plugin=plug) + try: + mod = import_plugin(path, config=config, staged=staged) + if not mod: + mod = next( + ( + import_plugin(_path, config=config, staged=staged) + for _path in EntariConfig.instance._plugin_names.get(path, []) + ), + None, + ) + if not mod: + log.plugin.error(f"cannot found plugin {path!r}") + publish(PluginLoadedFailed(path)) + return _LoadResult(error=RuntimeError("plugin not found")) + plugin_service._direct_plugins.add(mod.__name__) + if not staged: + _handle_dependents(mod.__plugin__, recursive_guard) + return _LoadResult(plugin=mod.__plugin__) + except (ImportError, RegisterNotInPluginError, ReusablePluginError, StaticPluginDispatchError) as e: + return _LoadResult(error=e) + except Exception as e: + log.plugin.exception(f"failed to load plugin {path!r}: {e}", exc_info=e) + publish(PluginLoadedFailed(path)) + return _LoadResult(error=e) + + +def find_plugin(name: str) -> Plugin | None: + """根据插件名称查找插件""" + if name in plugin_service.plugins: + return plugin_service.plugins[name] + if name in EntariConfig.instance.plugin_prefixes: + for prefix in EntariConfig.instance.plugin_prefixes[name]: + if f"{prefix}{name}" in plugin_service.plugins: + return plugin_service.plugins[f"{prefix}{name}"] + if not name.count(".") and f"entari_plugin_{name}" in plugin_service.plugins: + return plugin_service.plugins[f"entari_plugin_{name}"] + + +def unload_plugin(plugin: str): + """卸载插件及其子插件""" + plugin = plugin.replace("::", "arclet.entari.builtins.") + while plugin in plugin_service._subplugined: + plugin = plugin_service._subplugined[plugin] + if not (_plugin := find_plugin(plugin)): + return False + _plugin.dispose() + return True + + +def _unload_for_reload(plugin_id: str) -> bool: + """级联重载中的卸载步骤:以 replacing=True 拆卸,与 reload_plugin 的语义一致 + + 不清理 bindings 等会被随后 load 重新记录的状态,也不走永久卸载的引用/依赖收尾。 + """ + if not (_plugin := find_plugin(plugin_id)): + return False + _plugin.dispose(replacing=True) + return True + + +def promote_staged(plugin: Plugin): + """将暂存插件及其子插件正式注册进 plugin_service,并恢复启用状态与父绑定 + + 子插件按加载顺序已记入父插件 subplugins 列表; + 父属性写回(插件导入链)使模块中的属性指向新模块对象。 + """ + for sid in [plugin.id, *plugin.subplugins]: + if staged := plugin_service._staged.pop(sid, None): + plugin_service.plugins[sid] = staged + if sid != plugin.id: + plugin_service._subplugined[sid] = plugin.id + staged.check_disable() + if plugin_service.status.blocking: + publish(Ready(), staged._scope) + plugin_service._unloaded.discard(plugin.id) + for sid in plugin.subplugins: + if sid not in plugin_service.plugins or sid not in plugin_service._subplugined: + continue + parent = plugin_service.plugins.get(plugin_service._subplugined[sid]) + if parent is not None: + parent.module.__dict__[sid.rpartition(".")[-1]] = plugin_service.plugins[sid].module + + +def _collect_subtree(plugin: Plugin) -> list[str]: + """DFS 收集插件树的全部子插件 id(父在前,去重)""" + ids: list[str] = [] + seen: set[str] = set() + + def walk(plug: Plugin): + for sid in plug.subplugins: + if sid in seen: + continue + seen.add(sid) + ids.append(sid) + if sub := plugin_service.plugins.get(sid): + walk(sub) + + walk(plugin) + return ids + + +async def reload_plugin(path: str, conf: dict | None = None) -> _LoadResult: + """原子重载:导入失败时旧插件继续运行;成功后处理依赖方""" + path = path.replace("::", "arclet.entari.builtins.") + while path in plugin_service._subplugined: + path = plugin_service._subplugined[path] + if not (plugin := find_plugin(path)): + return _LoadResult(error=RuntimeError(f"plugin {path!r} not found")) + if plugin.is_static: + return _LoadResult(error=RuntimeError(f"plugin {path!r} is static and cannot be reloaded")) + _conf = conf if conf is not None else plugin.config.copy() + old_subplugins = _collect_subtree(plugin) + for name in old_subplugins: + sys.modules.pop(name, None) + log.plugin.debug(f"staged loading {path!r}, old plugin keeps running until swap") + if not (result := load_plugin(path, _conf, staged=True)): + log.plugin.error(f"failed to load staged plugin {path!r}, old plugin keeps running") + return result + if tasks := plugin.dispose(replacing=True): + await asyncio.wait(tasks) + new_plugin = result.plugin + promote_staged(new_plugin) + # 恢复未随 staged exec 重新导入的子插件(load_plugins/config 方式加载的模块在 + # 暂存加载期间 load_plugin 上溯命中旧插件早退,且不记入新插件 subplugins) + for sub_id in old_subplugins: + if sub_id in new_plugin.subplugins or sub_id in plugin_service.plugins: + continue + try: + mod = import_plugin(sub_id) + except Exception as e: + log.plugin.error(f"failed to restore sub-plugin {sub_id!r}: {e!r}") + continue + if mod is None: + log.plugin.error(f"cannot restore sub-plugin {sub_id!r}: module not found") + continue + log.plugin.debug(f"restored sub-plugin {sub_id!r} not re-imported by staged reload") + _handle_dependents(new_plugin) + return result + + +async def reload_subplugin(path: str, conf: dict | None = None) -> _LoadResult: + """子插件粒度重载:仅替换子插件自身,父插件与兄弟经绑定索引重绑 + + 父插件在模块层使用该子插件名字(基类/模块级装饰器/顶层实例化)→ 回退整树重载; + 子插件自身经暂存机制原子替换(失败时旧子插件继续运行)。 + """ + path = path.replace("::", "arclet.entari.builtins.") + if path not in plugin_service.plugins or path not in plugin_service._subplugined: + return _LoadResult(error=RuntimeError(f"sub-plugin {path!r} not found")) + plugin = plugin_service.plugins[path] + parent_id = plugin_service._subplugined[path] + parent = plugin_service.plugins.get(parent_id) + if parent and _uses_module_level(parent, path): + log.plugin.debug(f"parent {parent_id!r} uses {path!r} at module level, full tree reload") + return await reload_plugin(parent_id, parent.config.copy()) + _conf = conf if conf is not None else plugin.config.copy() + log.plugin.debug(f"staged loading sub-plugin {path!r}, old sub-plugin keeps running until swap") + if not (mod := import_plugin(path, config=_conf, staged=True)): + log.plugin.error(f"failed to load staged sub-plugin {path!r}, old sub-plugin keeps running") + return _LoadResult( + error=RuntimeError(f"failed to load staged sub-plugin {path!r}, old sub-plugin keeps running") + ) + if tasks := plugin.dispose(replacing=True): + await asyncio.wait(tasks) + new_plugin = mod.__plugin__ # type: ignore + promote_staged(new_plugin) + _handle_dependents(new_plugin) + return _LoadResult(plugin=new_plugin) diff --git a/arclet/entari/plugin/model.py b/arclet/entari/plugin/model.py index c66e495..ab87750 100644 --- a/arclet/entari/plugin/model.py +++ b/arclet/entari/plugin/model.py @@ -1,5 +1,6 @@ from __future__ import annotations +import ast import asyncio import inspect import re @@ -14,30 +15,26 @@ from weakref import finalize, proxy from arclet.letoderea import ( - SUBSCRIBER, - Contexts, ExitState, Propagator, Scope, Subscriber, define, - enter_if, on, - propagate, publish, ) from arclet.letoderea.breakpoint import StepOut, step_out from arclet.letoderea.effect import AsyncDisposable, Disposable from arclet.letoderea.provider import Provider, ProviderFactory, TProviders from arclet.letoderea.publisher import Publisher, _publishers, filter_publisher -from arclet.letoderea.scope import RegisterWrapper +from arclet.letoderea.scope import RegisterWrapper, scope_ctx from arclet.letoderea.utils import DisposableList, add_task from creart import it from launart import Launart, Service from tarina import ContextModel -from tarina.tools import TCallable, run_sync +from tarina.tools import run_sync -from ..config import config_model_schema +from ..config.schema import add_schema_fragment, plugin_config_schema, purge_schema_fragments from ..event.config import ConfigReload from ..event.plugin import PluginLoadedFailed, PluginLoadedSuccess, PluginUnloaded from ..exceptions import RegisterNotInPluginError, ReusablePluginError, StaticPluginDispatchError @@ -168,52 +165,14 @@ class PluginMetadata: """插件分类""" requirements: list[str] = field(default_factory=list) """插件依赖""" - depend_services: list[type[Service] | str | dict] = field(default_factory=list) - """插件依赖的服务""" config: Any | None = None """插件配置模型""" - def get_config_schema(self) -> dict[str, Any]: - """获取插件配置模型的 JSON Schema""" - if self.config is None: - return {} - return config_model_schema(self.config) - -def inject(*services: type[Service] | str | dict, _is_global: bool = False): - - async def checker(launart: Launart, ctx: Contexts): - for service in services: - if isinstance(service, dict): - serv_id = service["id"] if isinstance(service["id"], str) else service["id"].id - stage = service.get("stage", "prepared") - else: - serv_id = service if isinstance(service, str) else service.id - stage = "prepared" - sub = f"{ctx[SUBSCRIBER].__repr__()[13:-1]}" - log.plugin.trace(f"{sub} waiting for service {serv_id} to be {stage!r}") - await plugin_service.service_waiter.wait_for(serv_id) - serv = launart.components[serv_id] - if not serv.status.prepared: - await serv.status.wait_for(stage) - return True - - if _is_global: - return enter_if(checker) - - def wrapper(func: TCallable) -> TCallable: - try: - plg = current_plugin.get() - except LookupError: - plg = func.__globals__.get("__plugin__", None) - if not plg or not isinstance(plg, Plugin): - raise LookupError("no plugin context found") - plg._extra.setdefault("local_injected_services", []).append( - (func.__qualname__, [s.id if isinstance(s, type) else s for s in services]) - ) - return propagate(enter_if(checker))(func) - - return wrapper +@dataclass(slots=True) +class PluginInspect: + nodes: ast.Module + dump: str @dataclass @@ -226,13 +185,15 @@ class Plugin: is_static: bool = False path: str = field(init=False) uid: str | None = None + _inspect: PluginInspect | None = field(default=None, repr=False) _metadata: PluginMetadata | None = None _is_disposed: bool = False _services: dict[str, Service] = field(init=False, default_factory=dict) _config_key: str = field(init=False) - # _scope: Scope = field(init=False) _extra: dict[str, Any] = field(default_factory=dict, init=False) # extra metadata for inspection - _apply: Callable[[Plugin], Any] | None = field(default=None, init=False) + _apply: Callable[[Plugin], Any] | None = field(default=None, init=False, repr=False) + _sys_entry: Any = field(default=None, init=False, repr=False) + """本插件在 sys.modules 中登记的条目(代理对象),用于卸载时仅移除自身登记""" @property def reusable(self) -> bool: @@ -256,9 +217,6 @@ def metadata(self) -> PluginMetadata | None: @metadata.setter def metadata(self, value: PluginMetadata): self._metadata = value - if value and value.depend_services: - self._scope.propagators.append(inject(*value.depend_services, _is_global=True)) # type: ignore - self._extra["injected_services"] = [s.id if isinstance(s, type) else s for s in value.depend_services] if value.readme and re.fullmatch(r"(?i:readme)\.(?i:md|markdown)", value.readme.strip()): readme_path = Path(self.module.__file__ or "").parent / value.readme.strip() try: @@ -271,14 +229,75 @@ def metadata(self, value: PluginMetadata): ): value.config.__doc__ = value.description or value.name + def __post_init__(self): + if not hasattr(self.module, "__plugin__"): + setattr(self.module, "__plugin__", self) + uid_index = self.id.rfind("@") + self.path = self.id[:uid_index] if uid_index != -1 else self.id + self.uid = self.id[uid_index + 1 :] if uid_index != -1 else None + if self.id in plugin_service.plugins and not self.id.startswith("$"): + # 原子重载暂存:id 冲突时注册进 _staged,scope 用唯一 id 并置 disabled + self._scope = _make_scope(self).of(f"{self.id}@staging") + self._scope.disable() + plugin_service._staged[self.id] = self # type: ignore + else: + self._scope = _make_scope(self).of(self.id) + plugin_service.plugins[self.id] = self # type: ignore + self.effect = self._scope.effect + self._config_key = self.config.pop("$path", self.id.lstrip("$")) + if filter_expr := self.config.get("$filter", ""): + self._scope.propagators.append(FilterPropagator(filter_expr)) + if "$disable" in self.config and isinstance(self.config["$disable"], str): + + async def _check_reload(event: ConfigReload): + if event.scope == "basic": + self.check_disable() + + sub = on(ConfigReload, _check_reload) + self.collect(sub.dispose) + + self.is_static = self.config.pop("$static", False) + if self.id not in plugin_service._keep_values: + plugin_service._keep_values[self.id] = {} + if self.path not in plugin_service.referents: + plugin_service.referents[self.path] = set() + if self.path not in plugin_service.references: + plugin_service.references[self.path] = set() + plugin_service._unloaded.discard(self.id) + finalize(self, self.dispose, is_cleanup=True) + + def register_schema( + self, + path: str | tuple[str, ...] = "", + fragment: dict | type | Callable[[dict], dict] | None = None, + *, + replace: bool = False, + ) -> None: + """注册作用于本插件配置的 schema 片段""" + add_schema_fragment(self._config_key, path, fragment, replace, self.id) + + def config_schema(self, config_key: str | None = None, ref_root: str = "/") -> dict[str, Any]: + """获取插件配置模型的 JSON Schema + + Args: + config_key (str, optional): 本插件的配置键。 + ref_root (str, optional): JSON Schema $ref 的根路径,默认为 "/"。 + """ + return plugin_config_schema(self, config_key, ref_root=ref_root) # type: ignore + def exec_apply(self): if not self._apply: return log.plugin.trace(f"applying plugin {self.id!r}") token = current_plugin.set(self) + if not self.is_static: + token1 = scope_ctx.set(self._scope) try: self._apply(self) - log.plugin.success(f"plugin {self.id!r} fully applied") + if self.id in plugin_service._subplugined: + log.plugin.trace(f"sub-plugin {self.id!r} fully applied") + else: + log.plugin.success(f"plugin {self.id!r} fully applied") publish(PluginLoadedSuccess(self.id)) except (ImportError, RegisterNotInPluginError, StaticPluginDispatchError, ReusablePluginError) as e: log.plugin.error(f"failed to load plugin {self.id!r}: {e.args[0]}") @@ -292,6 +311,8 @@ def exec_apply(self): raise finally: current_plugin.reset(token) + if not self.is_static or "token1" in locals(): + scope_ctx.reset(token1) # type: ignore @property def is_available(self) -> bool: @@ -345,9 +366,9 @@ def disable(self): continue plugin_service.plugins[ret].disable() tasks = set() - t = self._clean_service() - t.add_done_callback(tasks.discard) - tasks.add(t) + if (t := self._clean_service()) is not None: + t.add_done_callback(tasks.discard) + tasks.add(t) self._scope.disable() if "$disable" not in self.config or isinstance(self.config["$disable"], bool): self.config["$disable"] = True @@ -388,40 +409,6 @@ def restore(self): """回收所有副作用""" return self._scope._effect_manager.dispose() - def __post_init__(self): - uid_index = self.id.rfind("@") - self.path = self.id[:uid_index] if uid_index != -1 else self.id - self.uid = self.id[uid_index + 1 :] if uid_index != -1 else None - self._scope = _make_scope(self).of(self.id) - self.effect = self._scope.effect - plugin_service.plugins[self.id] = self # type: ignore - self._config_key = self.config.pop("$path", self.id) - if filter_expr := self.config.get("$filter", ""): - self._scope.propagators.append(FilterPropagator(filter_expr)) - # if self._metadata and self._metadata.depend_services: - # self._scope.propagators.append(inject(*self._metadata.depend_services, _is_global=True)) # type: ignore - # self._extra["injected_services"] = [ - # s.id if isinstance(s, type) else s for s in self._metadata.depend_services - # ] - if "$disable" in self.config and isinstance(self.config["$disable"], str): - - async def _check_reload(event: ConfigReload): - if event.scope == "basic": - self.check_disable() - - sub = on(ConfigReload, _check_reload) - self.collect(sub.dispose) - - self.is_static = self.config.pop("$static", False) - if self.id not in plugin_service._keep_values: - plugin_service._keep_values[self.id] = {} - if self.path not in plugin_service.referents: - plugin_service.referents[self.path] = set() - if self.path not in plugin_service.references: - plugin_service.references[self.path] = set() - plugin_service._unloaded.discard(self.id) - finalize(self, self.dispose, is_cleanup=True) - def _clean_service(self): manager = it(Launart) @@ -432,41 +419,56 @@ def _gen(service: Service): yield service _services = [s for serv in self._services.values() for s in _gen(serv)] + if not _services: + return + + async def _clean_one(service: Service): + if not manager.task_group: + return + plugin_service.service_waiter.clear(service.id) + if service.id not in manager.task_group.sideload_trackers: + return + try: + tracker = manager.task_group.sideload_trackers[service.id] + manager.remove_component(service) + await asyncio.wait([tracker, add_task(service.status.wait_for("finished"))]) + except (ValueError, KeyError): + pass async def _clean(services: list[Service]): if not manager.task_group: return - for serv in services: - plugin_service.service_waiter.clear(serv.id) - if serv.id not in manager.task_group.sideload_trackers: - continue - try: - tracker = manager.task_group.sideload_trackers[serv.id] - manager.remove_component(serv) - await asyncio.wait([tracker, add_task(serv.status.wait_for("finished"))]) - except (ValueError, KeyError): - pass + await asyncio.gather(*(_clean_one(serv) for serv in services)) return add_task(_clean(_services)) - def dispose(self, *, is_cleanup: bool = False): + def dispose(self, *, is_cleanup: bool = False, replacing: bool = False): + """拆卸插件 + + replacing=True 表示是重载插件下的卸载(reload_plugin/reload_subplugin) + """ if not is_cleanup and self.is_static: return # static plugin can only be disposed in cleanup phase - plugin_service._unloaded.add(self.id) + _was_staged = self.id in plugin_service._staged + # staged plugin shares position with same-id live plugin + # do not changed the unloaded flag of live plugin + if not _was_staged: + plugin_service._unloaded.add(self.id) if self._is_disposed: return - if not self.id.startswith(".") and self.id not in plugin_service._subplugined: + purge_schema_fragments(self.id) + if not self.id.startswith("$") and self.id not in plugin_service._subplugined: log.plugin.debug(f"disposing plugin {self.id}") self._is_disposed = True tasks = set() - t = self._clean_service() - t.add_done_callback(tasks.discard) - tasks.add(t) + if (t := self._clean_service()) is not None: + t.add_done_callback(tasks.discard) + tasks.add(t) self._services.clear() - if self.module.__spec__ and self.module.__spec__.cached: - Path(self.module.__spec__.cached).unlink(missing_ok=True) - sys.modules.pop(self.module.__name__, None) - tasks.update(self.restore()) + # only remove if entry in sys.modules is self._sys_entry + _entry = sys.modules.get(self.module.__name__) + if self._sys_entry is None or _entry is self._sys_entry: + sys.modules.pop(self.module.__name__, None) delattr(self.module, "__plugin__") if self.subplugins: subplugs = [i.removeprefix(self.id)[1:] for i in self.subplugins] @@ -474,17 +476,23 @@ def dispose(self, *, is_cleanup: bool = False): log.plugin.trace(f"disposing sub-plugin {', '.join(subplugs)} of {self.id}") for subplug in self.subplugins: if subplug not in plugin_service.plugins: - plugin_service._subplugined.pop(subplug, None) + if subplug in plugin_service._staged: + tasks.update( + plugin_service._staged[subplug].dispose(is_cleanup=is_cleanup, replacing=replacing) + ) + else: + plugin_service._subplugined.pop(subplug, None) continue try: - tasks.update(plugin_service.plugins[subplug].dispose(is_cleanup=is_cleanup)) + tasks.update(plugin_service.plugins[subplug].dispose(is_cleanup=is_cleanup, replacing=replacing)) plugin_service._subplugined.pop(subplug, None) except Exception as e: log.plugin.error(f"failed to dispose sub-plugin {subplug} caused by {e!r}") plugin_service.plugins.pop(subplug, None) self.subplugins.clear() - if not is_cleanup: + if not is_cleanup and not _was_staged: publish(PluginUnloaded(self.id)) + if not is_cleanup and not _was_staged and not replacing: for ref in plugin_service.references.pop(self.path): if ref not in plugin_service.plugins: continue @@ -505,7 +513,10 @@ def dispose(self, *, is_cleanup: bool = False): except Exception as e: log.plugin.error(f"failed to dispose referent plugin {ref} caused by {e!r}") plugin_service.plugins.pop(ref, None) - for ret in plugin_service.referents[self.path].copy(): + # bindings-only 依赖方在卸载时同样需要停用,否则 A 永久卸载后其持有僵尸绑定继续运行 + _dependents = set(plugin_service.referents[self.path]) + _dependents.update(plugin_service.dependents_of(self.path, ensure=True)) + for ret in _dependents: if ret not in plugin_service.plugins: continue if ( @@ -515,10 +526,20 @@ def dispose(self, *, is_cleanup: bool = False): ): continue tasks.update(plugin_service.plugins[ret].disable()) - self._scope.dispose() + tasks.update(self._scope.dispose()) self._scope.propagators.clear() - del plugin_service.plugins[self.id] + # clean bindings when cleanup; replacing or stage will clean by new module + if not replacing and not _was_staged: + plugin_service.bindings.pop(self.id, None) + # 暂存插件的 id 与存活插件相同,只能从 _staged 移除,不可删除 plugins 中的存活插件 + if _was_staged: + plugin_service._staged.pop(self.id, None) + elif self.id in plugin_service.plugins: + del plugin_service.plugins[self.id] + else: + plugin_service._staged.pop(self.id, None) del self.module + del self._inspect return tasks def dispatch(self, event, name: str | None = None): @@ -575,49 +596,36 @@ def service(self, serv: TS | type[TS]) -> TS: plugin_service.service_waiter.assign(serv.id) return serv + def restore_kept_state(self): + """重载后将保持的模块级可变对象重新绑定到模块 dict""" + for kept in plugin_service._keep_values.get(self.id, {}).values(): + if kept.module_attr: + self.module.__dict__[kept.module_attr] = kept.obj + + def niche(self, label: str, config: dict[str, Any] | None = None): + """创建一个隔离的子插件,子插件的生命周期与父插件绑定""" + sub_id = f"{self.id}.{label}" + if sub_id in plugin_service.plugins: + raise ValueError(f"sub-plugin {sub_id} already exists") + + def wrapper(func: Callable[[Plugin], Any], /): + subplug = Plugin(sub_id, ModuleType(sub_id), config=self.config.copy() if config is None else config) + setattr(subplug.module, "__plugin__", subplug) + setattr(func, "__plugin__", subplug) + setattr(subplug.module, "__file__", func.__code__.co_filename) + subplug._apply = func + self.subplugins.append(sub_id) + plugin_service._subplugined[sub_id] = self.id + return subplug -class RootlessPlugin(Plugin): - # fmt: off - @classmethod - def apply(cls: type[RootlessPlugin], id: str, func: Callable[[RootlessPlugin], Any] | None = None, *, default: bool = False) -> Any: # noqa: E501 - # fmt: on - if not id.startswith("."): - id = f".{id}" - - def dispose(): - if id in plugin_service.plugins: - plugin_service.plugins[id].dispose() - else: - plugin_service._apply.pop(id, None) - - def wrapper(func: Callable[[RootlessPlugin], Any]): - plugin_service._apply[id] = (lambda config: cls(id, func, config), default) # type: ignore - return dispose - - if func: - return wrapper(func) return wrapper - def __init__(self, id: str, func: Callable[[RootlessPlugin], Any], config: dict): - super().__init__(id, ModuleType(id), config=config) - setattr(self.module, "__plugin__", self) - setattr(self.module, "__file__", func.__code__.co_filename) - self.func = func - setattr(self.func, "__plugin__", self) - token = current_plugin.set(self) - try: - func(self) - finally: - current_plugin.reset(token) - - def validate(self, func): - pass - class KeepingVariable(Generic[T]): - def __init__(self, obj: T, dispose: Callable[[T], None] | Callable[[T], Awaitable[None]] | None = None): + def __init__(self, obj: T, dispose=None, module_attr=None): self.obj = obj self._dispose = None + self.module_attr = module_attr if hasattr(self.obj, "dispose"): _dispose = self.obj.dispose.__func__ # type: ignore if _is_awaitable(_dispose): @@ -641,7 +649,7 @@ async def dispose(self): # fmt: off -def keeping(id_: str, obj: T | None = None, obj_factory: Callable[[], T] | None = None, dispose: Callable[[T], None] | Callable[[T], Awaitable[None]] | None = None) -> T: # noqa: E501 +def keeping(id_: str, obj: T | None = None, obj_factory: Callable[[], T] | None = None, dispose: Callable[[T], None] | Callable[[T], Awaitable[None]] | None = None, module_attr: str | None = None) -> T: # noqa: E501 # fmt: on if not (plug := current_plugin.get(None)): raise LookupError("no plugin context found") @@ -650,5 +658,5 @@ def keeping(id_: str, obj: T | None = None, obj_factory: Callable[[], T] | None raise ValueError("Either `obj` or `obj_factory` must be provided") _obj = obj_factory() if obj_factory else obj plug._extra.setdefault("kept_variables", []).append(id_) - plugin_service._keep_values[plug.id][id_] = KeepingVariable(cast(T, _obj), dispose) # type: ignore + plugin_service._keep_values[plug.id][id_] = KeepingVariable(cast(T, _obj), dispose, module_attr) # type: ignore return plugin_service._keep_values[plug.id][id_].obj # type: ignore diff --git a/arclet/entari/plugin/model.pyi b/arclet/entari/plugin/model.pyi index 8935786..625950a 100644 --- a/arclet/entari/plugin/model.pyi +++ b/arclet/entari/plugin/model.pyi @@ -1,19 +1,19 @@ +import ast import asyncio from enum import Enum from collections.abc import Callable from dataclasses import dataclass, field from types import ModuleType -from typing import Any, AsyncGenerator, Awaitable, Generator, Generic, Literal, TypedDict, TypeVar, overload +from typing import Any, AsyncGenerator, Awaitable, Generator, Generic, TypedDict, TypeVar, overload from typing_extensions import NotRequired, Self from arclet.letoderea import ExitState, Propagator, Provider, ProviderFactory, Scope, Subscriber from arclet.letoderea.breakpoint import StepOut -from arclet.letoderea.decorate import Check from arclet.letoderea.effect import Disposable, AsyncDisposable, SyncEffect, AsyncEffect from arclet.letoderea.provider import TProviders from arclet.letoderea.publisher import Publisher from arclet.letoderea.scope import RegisterWrapper -from arclet.letoderea.utils import DisposableList, Resultable, TCallable +from arclet.letoderea.utils import DisposableList, Resultable from launart import Service from tarina import ContextModel @@ -162,12 +162,6 @@ class Author(TypedDict): email: NotRequired[str] """作者邮箱""" -class DependService(TypedDict): - id: str | type[Service] - """服务名称""" - stage: NotRequired[Literal["preparing", "prepared", "blocking"]] - """服务阶段""" - class PluginRole(Enum): NORMAL = "normal" UTILITY = "utility" @@ -187,15 +181,15 @@ class PluginMetadata: readme: str | None = ... classifier: list[str] = ... requirements: list[str] = ... - depend_services: list[type[Service] | str | DependService] = ... config: Any | None = ... - def get_config_schema(self) -> dict[str, Any]: ... + def get_config_schema(self, config_key: str | None = None) -> dict[str, Any]: ... + +@dataclass(slots=True) +class PluginInspect: + nodes: ast.Module + dump: str -@overload -def inject(*services: type[Service] | str | DependService) -> Callable[[TCallable], TCallable]: ... -@overload -def inject(*services: type[Service] | str | DependService, _is_global: Literal[True]) -> Check: ... @dataclass class Plugin: id: str @@ -206,6 +200,7 @@ class Plugin: is_static: bool = ... path: str = ... uid: str | None = ... + _inspect: PluginInspect | None = ... _metadata: PluginMetadata | None = ... _is_disposed: bool = ... _services: dict[str, Service] = field(init=False, default_factory=dict) @@ -213,6 +208,7 @@ class Plugin: _scope: PluginScope = field(init=False) _extra: dict[str, Any] = field(default_factory=dict, init=False) # extra metadata for inspection _apply: Callable[[Plugin], Any] | None = field(default=None, init=False) + _sys_entry: Any = field(default=None, init=False, repr=False) @property def reusable(self) -> bool: ... @@ -224,6 +220,14 @@ class Plugin: def metadata(self) -> PluginMetadata | None: ... @metadata.setter def metadata(self, value: PluginMetadata): ... + def register_schema( + self, + path: str | tuple[str, ...] = "", + fragment: dict | type | Callable[[dict], dict] | None = None, + *, + replace: bool = False, + ) -> None: ... + def config_schema(self, config_key: str | None = None) -> dict[str, Any]: ... def exec_apply(self) -> None: ... @property def is_available(self) -> bool: ... @@ -240,8 +244,8 @@ class Plugin: def effect(self, execute: Callable[[], AsyncEffect], label: str = "") -> AsyncDisposable[Awaitable[None]]: ... def collect(self, *disposes: Disposable | AsyncDisposable) -> Self: ... def restore(self) -> set[asyncio.Task]: ... - def _clean_service(self) -> asyncio.Task: ... - def dispose(self, *, is_cleanup: bool = False) -> set[asyncio.Task]: ... + def _clean_service(self) -> asyncio.Task | None: ... + def dispose(self, *, is_cleanup: bool = False, replacing: bool = False) -> set[asyncio.Task]: ... @overload def dispatch(self, event: type[Resultable[T]], name: str | None = None) -> PluginDispatcher[T]: ... @overload @@ -346,30 +350,36 @@ class Plugin: def proxy(self) -> ModuleType: ... def subproxy(self, sub_id: str) -> ModuleType: ... def service(self, serv: TS | type[TS]) -> TS: ... - -class RootlessPlugin(Plugin): - @classmethod - @overload - def apply( - cls, id: str, *, default: bool = False - ) -> Callable[[Callable[[RootlessPlugin], Any]], Callable[[], None]]: ... - @classmethod - @overload - def apply(cls, id: str, func: Callable[[RootlessPlugin], Any], *, default: bool = False) -> Callable[[], None]: ... - def __init__(self, id: str, func: Callable[[RootlessPlugin], Any], config: dict): ... + def restore_kept_state(self) -> None: ... + def niche( + self, label: str, config: dict[str, Any] | None = None + ) -> Callable[[Callable[[Plugin], Any]], Plugin]: ... class KeepingVariable(Generic[T]): obj: T _dispose: Callable[[T], Awaitable[None]] | None - def __init__(self, obj: T, dispose: Callable[[T], None] | Callable[[T], Awaitable[None]] | None = None): ... + module_attr: str | None + def __init__( + self, + obj: T, + dispose: Callable[[T], None] | Callable[[T], Awaitable[None]] | None = None, + module_attr: str | None = None, + ): ... async def dispose(self): ... @overload -def keeping(id_: str, obj: T, *, dispose: Callable[[T], None] | Callable[[T], Awaitable[None]] | None = None) -> T: ... +def keeping( + id_: str, + obj: T, + *, + dispose: Callable[[T], None] | Callable[[T], Awaitable[None]] | None = None, + module_attr: str | None = None, +) -> T: ... @overload def keeping( id_: str, *, obj_factory: Callable[[], T], dispose: Callable[[T], None] | Callable[[T], Awaitable[None]] | None = None, + module_attr: str | None = None, ) -> T: ... diff --git a/arclet/entari/plugin/module.py b/arclet/entari/plugin/module.py index 2e5b8eb..af9f123 100644 --- a/arclet/entari/plugin/module.py +++ b/arclet/entari/plugin/module.py @@ -5,11 +5,11 @@ import tokenize from collections.abc import Sequence from importlib import _bootstrap, _bootstrap_external # type: ignore -from importlib.abc import MetaPathFinder from importlib.machinery import ExtensionFileLoader, ModuleSpec, PathFinder, SourceFileLoader from importlib.metadata import Distribution, PackageNotFoundError, distribution, distributions from importlib.util import module_from_spec, resolve_name from io import BytesIO +from os import PathLike from pathlib import Path from types import ModuleType from typing import Any @@ -22,8 +22,8 @@ from ..event.plugin import PluginLoadedFailed, PluginLoadedSuccess from ..exceptions import RegisterNotInPluginError, ReusablePluginError, StaticPluginDispatchError from ..logger import log -from .model import Plugin, PluginMetadata, current_plugin -from .service import plugin_service +from .model import Plugin, PluginInspect, PluginMetadata, current_plugin +from .service import Binding, plugin_service _SUBMODULE_WAITLIST: dict[str, set[str]] = {} _ENSURE_IS_PLUGIN: set[str] = set() @@ -75,12 +75,40 @@ def _ensure_plugin(names: list[str], sub: bool, pid: str, pname: str, prefix="") _IMPORTING.add(f"{prefix}{name}") +def _resolve_from_target(node: ast.ImportFrom, pname: str, is_init: bool) -> str | None: + """from-import 的目标模块全限定名(相对导入按当前模块解析)""" + if node.level == 0: + return node.module + parts = pname.split(".") + pkg = parts if is_init else parts[:-1] + if node.level > len(pkg) + 1: + return None + base = pkg if node.level == 1 else pkg[: 1 - node.level] + if node.module: + return ".".join([*base, node.module]) + return ".".join(base) if base else None + + +def _record_binding(pid: str, name: str, target: str, attr: str | None): + """记录名字级 import 绑定(name → (target, attr)),供重载侧查询与改写""" + if not target: + return + # 同一条边(名字+目标+属性)重复记录时去重;同名的其他边取消 last 标记,本次记录为最后一次 + entries = [ + b._replace(last=False) if b.name == name else b + for b in plugin_service.bindings.get(pid, []) + if not (b.name == name and b.target == target and b.attr == attr) + ] + entries.append(Binding(name, target, attr, True)) + plugin_service.bindings[pid] = entries + + # fmt: off class _Visitor(ast.NodeVisitor): - def __init__(self, pid: str, pname: str, path: str, plg_lineno: list[int], sub_lineno: list[int], ns_lineno: list[int]): # noqa: E501 + def __init__(self, pid: str, pname: str, path: bytes | str | PathLike[str], plg_lineno: list[int], sub_lineno: list[int], ns_lineno: list[int]): # noqa: E501 self.pid = pid self.pname = pname - self.path = path + self.path = path.decode() if isinstance(path, bytes) else f"{Path(path)}" self.signed_plugin_lineno = plg_lineno self.signed_subplugin_lineno = sub_lineno self.signed_namespace_lineno = ns_lineno @@ -96,6 +124,8 @@ def visit_Import(self, node: ast.Import): if self._in_type_checking(): return + for alias in node.names: + _record_binding(self.pid, alias.asname or alias.name.split(".")[0], alias.name, None) if node.lineno in self.signed_plugin_lineno or all(x.name in _ENSURE_IS_PLUGIN for x in node.names): _ensure_plugin([alias.name for alias in node.names], False, self.pid, self.pname) elif node.lineno in self.signed_subplugin_lineno or all(x.name in _SUBMODULE_WAITLIST.get(self.pname, ()) for x in node.names): # noqa: E501 @@ -105,6 +135,15 @@ def visit_ImportFrom(self, node: ast.ImportFrom): name = self.pname if self._in_type_checking(): return + target = _resolve_from_target(node, name, self.path.endswith("__init__.py")) + if target: + for alias in node.names: + if alias.name == "*": + continue + if node.level == 1 and node.module is None: + _record_binding(self.pid, alias.asname or alias.name, f"{target}.{alias.name}", None) + else: + _record_binding(self.pid, alias.asname or alias.name, target, alias.name) if node.module is None: # from . import xxx _ensure_plugin([alias.name for alias in node.names], node.lineno not in self.signed_plugin_lineno, self.pid, name, f"{name}.") # noqa: E501 elif node.level == 0: # from xxx import xxx @@ -194,6 +233,8 @@ def __init__(self, fullname: str, path: str, plugin_id: str, parent_plugin_id: s self.loaded = False self.plugin_id = plugin_id self.parent_plugin_id = parent_plugin_id + self._inspect: PluginInspect = None # type: ignore + self.staged = False super().__init__(fullname, path) def get_code(self, fullname): @@ -204,13 +245,12 @@ def get_code(self, fullname): """ source_path = self.get_filename(fullname) - source_bytes = None - if source_bytes is None: - source_bytes = self.get_data(source_path) + # --- SourceFileLoader's cache handler removed --- + source_bytes = self.get_data(source_path) code_object = self.source_to_code(source_bytes, source_path) return code_object - def source_to_code(self, data, path=""): + def source_to_code(self, data, path="", *, _optimize: int = -1): """Return the code object compiled from source. The 'data' argument can be any object type that compile() supports. @@ -236,33 +276,39 @@ def source_to_code(self, data, path=""): nodes = ast.parse(data, type_comments=True) except SyntaxError: return _bootstrap._call_with_frames_removed( # type: ignore - compile, data, path, "exec", dont_inherit=True, optimize=-1 + compile, data, path, "exec", dont_inherit=True, optimize=_optimize ) visitor = _Visitor(self.plugin_id, name, path, plg_lineno, sub_lineno, ns_lineno) visitor.visit(nodes) - + self._inspect = PluginInspect(nodes, ast.dump(nodes, include_attributes=False)) return _bootstrap._call_with_frames_removed( # type: ignore - compile, nodes, path, "exec", dont_inherit=True, optimize=-1 + compile, nodes, path, "exec", dont_inherit=True, optimize=_optimize ) def create_module(self, spec) -> ModuleType | None: + if self.staged: + return super().create_module(spec) if self.name in plugin_service.plugins: self.loaded = True return plugin_service.plugins[self.name].proxy() if self.name in plugin_service._subplugined: self.loaded = True return plugin_service.plugins[plugin_service._subplugined[self.name]].subproxy(self.name) - if ( - any((k.startswith(self.name) and k.rfind("@") != -1) for k in plugin_service.plugins) - and self.plugin_id.rfind("@") == -1 - ): - raise ReusablePluginError(f"reusable plugin {self.name!r} cannot be imported directly") + _check_reusable(self.name, self.plugin_id) return super().create_module(spec) def exec_module(self, module: ModuleType, config: dict[str, Any] | None = None) -> None: is_sub = False - if plugin := plugin_service.plugins.get(self.parent_plugin_id) if self.parent_plugin_id else None: - plugin.subplugins.append(self.plugin_id) + plugin = ( + # 暂存期间 plugins 中仍是旧顶插件,须优先取 _staged 中的新插件, + # 否则新子插件的父链接指向旧插件 + (plugin_service._staged.get(self.parent_plugin_id) or plugin_service.plugins.get(self.parent_plugin_id)) + if self.parent_plugin_id + else None + ) + if plugin: + if self.plugin_id not in plugin.subplugins: + plugin.subplugins.append(self.plugin_id) plugin_service._subplugined[self.plugin_id] = plugin.id is_sub = True if config is None or not {k: v for k, v in config.items() if k not in ("$path", "$static")}: @@ -279,33 +325,47 @@ def exec_module(self, module: ModuleType, config: dict[str, Any] | None = None) if config is None or (not is_sub and not {k: v for k, v in config.items() if k not in ("$path", "$static")}): key = module.__name__ - if key.startswith("arclet.entari.builtins.") and f"::{key[23:]}" in EntariConfig.instance.plugin: - key = f"::{key[23:]}" - config = EntariConfig.instance.plugin.get(key) - if config is not None: - config["$path"] = key - else: - for k, names in EntariConfig.instance._plugin_names.items(): - if key in names: - config = EntariConfig.instance.plugin.get(k, {}) - config["$path"] = k - break + if hasattr(EntariConfig, "instance"): + if key.startswith("arclet.entari.builtins.") and f"::{key[23:]}" in EntariConfig.instance.plugin: + key = f"::{key[23:]}" + config = EntariConfig.instance.plugin.get(key) + if config is not None: + config["$path"] = key else: - config = {"$path": key} - if key in EntariConfig.instance.prelude_plugin: - config["$static"] = True # type: ignore + for k, names in EntariConfig.instance._plugin_names.items(): + if key in names: + config = EntariConfig.instance.plugin.get(k, {}) + config["$path"] = k + break + else: + config = {"$path": key} + if key in EntariConfig.instance.prelude_plugin: + config["$static"] = True # type: ignore + else: + config = {"$path": key} # create plugin before executing plugin = Plugin(self.plugin_id, module, config=config) + # record the origin entry for recover if stage failed + prev_entry = sys.modules.get(module.__name__) # for `dataclasses` module sys.modules[module.__name__] = plugin.proxy() # type: ignore + plugin._sys_entry = sys.modules[module.__name__] setattr(module, "__plugin__", plugin) + def _abort(plug: Plugin): + plug.dispose(replacing=self.staged) + if self.staged and prev_entry is not None: + sys.modules[module.__name__] = prev_entry + # enter plugin context token = current_plugin.set(plugin) if not plugin.is_static: token1 = scope_ctx.set(plugin._scope) try: - super().exec_module(module) + code = self.get_code(module.__name__) + if code is None: + raise ImportError(f"cannot load module {module.__name__r} when get_code() returns None") + _bootstrap._call_with_frames_removed(exec, code, module.__dict__) # type: ignore except RegisterNotInPluginError as e: deleted = [] for frame in reversed(inspect.trace()): @@ -322,7 +382,10 @@ def exec_module(self, module: ModuleType, config: dict[str, Any] | None = None) _ensure_plugin(deleted[-1:], False, self.plugin_id, self.name) _ENSURE_IS_PLUGIN.update(deleted[:-1]) try: - super().exec_module(module) + code = self.get_code(module.__name__) + if code is None: + raise ImportError(f"cannot load module {module.__name__r} when get_code() returns None") + _bootstrap._call_with_frames_removed(exec, code, module.__dict__) # type: ignore except Exception as e1: if isinstance(e1, RegisterNotInPluginError): log.plugin.error(f"failed to load plugin {self.plugin_id!r}:\n{e1.msg}") @@ -330,20 +393,20 @@ def exec_module(self, module: ModuleType, config: dict[str, Any] | None = None) log.plugin.exception( f"failed to load plugin {self.plugin_id!r} caused by {e!r}", exc_info=e ) - plugin.dispose() + _abort(plugin) publish(PluginLoadedFailed(self.plugin_id, e1)) if isinstance(e, (ImportError, StaticPluginDispatchError, ReusablePluginError)): raise e1 from None else: - raise ImportError(f"{e1!r} in {self.name!r}", name=self.name, path=self.path) from None + raise ImportError(f"{e1!r} in {self.name!r}", name=self.name, path=self.path) except Exception as e: log.plugin.exception(f"failed to load plugin {self.plugin_id!r} caused by {e!r}", exc_info=e) - plugin.dispose() + _abort(plugin) publish(PluginLoadedFailed(self.plugin_id, e)) if isinstance(e, (ImportError, StaticPluginDispatchError, ReusablePluginError)): raise else: - raise ImportError(f"{e!r} in {self.name!r}", name=self.name, path=self.path) from None + raise ImportError(f"{e!r} in {self.name!r}", name=self.name, path=self.path) finally: # leave plugin context delattr(module, "__cached__") @@ -356,16 +419,20 @@ def exec_module(self, module: ModuleType, config: dict[str, Any] | None = None) if metadata and not plugin.metadata: plugin.metadata = metadata plugin._apply = getattr(module, "__plugin_apply__", None) + plugin._inspect = self._inspect + plugin.restore_kept_state() + del self._inspect + staged = "staged " if self.staged else "" if not is_sub: if plugin._apply: - log.plugin.success(f"loaded plugin {self.plugin_id!r} partially applied") + log.plugin.success(f"{staged}loaded plugin {self.plugin_id!r} partially applied") else: - log.plugin.success(f"loaded plugin {self.plugin_id!r}") + log.plugin.success(f"{staged}loaded plugin {self.plugin_id!r}") else: - log.plugin.trace(f"loaded sub-plugin {plugin.id!r} of {self.parent_plugin_id!r}") + log.plugin.trace(f"{staged}loaded sub-plugin {plugin.id!r} of {self.parent_plugin_id!r}") if not plugin._apply: publish(PluginLoadedSuccess(self.plugin_id)) - if plugin_service.status.blocking: + if plugin_service.status.blocking and not self.staged: if plugin._apply: plugin.exec_apply() plugin.check_disable() @@ -405,20 +472,46 @@ def _path_find_spec(fullname, path=None, target=None) -> ModuleSpec | None: return spec -class _PluginFinder(MetaPathFinder): +def _as_plugin( + module_spec: ModuleSpec, fullname: str, module_origin: str, plugin_id: str, staged: bool = False +) -> ModuleSpec: + loader = PluginLoader(fullname, module_origin, plugin_id) + loader.staged = staged + module_spec.loader = loader + return module_spec + + +def _as_submodule( + module_spec: ModuleSpec, fullname: str, module_origin: str, plugin_id: str, parent: str, staged: bool = False +) -> ModuleSpec: + loader = PluginLoader(fullname, module_origin, plugin_id, parent) + loader.staged = staged + module_spec.loader = loader + return module_spec + + +def _check_reusable(name: str, plugin_id: str) -> None: + if any(k.startswith(name) and k.rfind("@") != -1 for k in plugin_service.plugins) and plugin_id.rfind("@") == -1: + raise ReusablePluginError(f"reusable plugin {name!r} cannot be imported directly") + + +class _PluginFinder(PathFinder): @classmethod def find_spec( cls, fullname: str, - path: Sequence[str] | None, + path: Sequence[str] | None = None, target: ModuleType | None = None, origin_id_: str | None = None, - ): + force: bool = False, + staged: bool = False, + ) -> ModuleSpec | None: # get the module spec using the default path-finder module_spec = _path_find_spec(fullname, path, target) if not module_spec: return module_origin = module_spec.origin + plugin_id = origin_id_ or fullname # if the module has no origin, it might be a namespace package or a built-in module. # We only care about namespace packages here, as built-in modules should not be treated as plugins. # For namespace packages, we can still return the spec without modification, @@ -433,30 +526,46 @@ def find_spec( return # current import statement is within a plugin. if plug := current_plugin.get(None): + # only record outside import, as inside import are already recorded by the plugin's loader. + if module_spec.name != plug.module.__name__ and not module_spec.name.startswith(plug.module.__name__ + "."): + _record_binding(plug.id, module_spec.name.split(".")[0], module_spec.name, None) # if the module being imported is the same as the plugin's module, # return the plugin's module spec directly to avoid infinite recursion. - if plug.module.__spec__ and plug.module.__spec__.origin == module_spec.origin: + if plug.module.__spec__ and plug.module.__spec__.origin == module_origin: return plug.module.__spec__ # get the top-level plugin id (the parent) of the current plugin - plugin_id = plug.id - while plugin_id in plugin_service._subplugined: - plugin_id = plugin_service._subplugined[plugin_id] + parent_id = plug.id + while parent_id in plugin_service._subplugined: + parent_id = plugin_service._subplugined[parent_id] # if the module being imported is a submodule of the top-level plugin, - if module_spec.name.startswith(plugin_service.plugins[plugin_id].module.__name__ + "."): - module_spec.loader = PluginLoader(fullname, module_origin, origin_id_ or fullname, plugin_id) - return module_spec - # if the module being imported is in the waitlist of the top-level plugin, + # or if the module being imported is in the waitlist of the top-level plugin, # it means it is marked as a submodule by the plugin author. - if module_spec.name in _SUBMODULE_WAITLIST.get(plugin_id, ()): - module_spec.loader = PluginLoader(fullname, module_origin, origin_id_ or fullname, plugin_id) - # plugin_service.referents.setdefault(module_spec.name, set()).add(plug.id) - # _SUBMODULE_WAITLIST[plug.module.__name__].remove(module_spec.name) - return module_spec + if module_spec.name.startswith( + plugin_service.plugins[parent_id].module.__name__ + "." + ) or module_spec.name in _SUBMODULE_WAITLIST.get( # noqa: E501 + parent_id, () + ): + return _as_submodule( + module_spec, + fullname, + module_origin, + plugin_id, + parent_id, + staged=staged or plug.id in plugin_service._staged, + ) # noqa: E501 # in the following cases, the module is imported directly (probably from Entari App) # 1. the module is already a plugin. if module_spec.name in plugin_service.plugins: - module_spec.loader = PluginLoader(fullname, module_origin, origin_id_ or fullname) - return module_spec + if module_spec.name in plugin_service._subplugined: + return _as_submodule( + module_spec, + fullname, + module_origin, + plugin_id, + plugin_service._subplugined[module_spec.name], + staged=staged, + ) # noqa: E501 + return _as_plugin(module_spec, fullname, module_origin, plugin_id, staged=staged) # 2. the module is marked as a plugin by the plugin author, or followed the naming convention for plugins. marked = ( module_spec.name in _ENSURE_IS_PLUGIN @@ -487,7 +596,7 @@ def find_spec( except (KeyError, ValueError): pass if marked: - module_spec.loader = PluginLoader(fullname, module_origin, origin_id_ or fullname) + _as_plugin(module_spec, fullname, module_origin, plugin_id, staged=staged) # if there already exists a plugin that is importing this module, # we should add the plugin as a referent of this module if plug: @@ -495,44 +604,93 @@ def find_spec( return module_spec # 3. the module is marked as a submodule by other plugin, or it is a submodule of a plugin. if module_spec.name in plugin_service._subplugined: - module_spec.loader = PluginLoader( - fullname, module_origin, origin_id_ or fullname, plugin_service._subplugined[module_spec.name] - ) - return module_spec + return _as_submodule( + module_spec, + fullname, + module_origin, + plugin_id, + plugin_service._subplugined[module_spec.name], + staged=staged, + ) # noqa: E501 # 4. if the module is already a plugin, but it is assigned an unique id (usage of reusable plugin), # it cannot be imported directly, otherwise it will break the uniqueness of the plugin instance. - if ( - any(k.startswith(module_spec.name) and k.rfind("@") != -1 for k in plugin_service.plugins) - and (origin_id_ or fullname).rfind("@") == -1 - ): - raise ReusablePluginError(f"reusable plugin {module_spec.name!r} cannot be imported directly") + _check_reusable(module_spec.name, plugin_id) # 5. the module is a submodule of a plugin, but it is not marked as a submodule by the plugin author, - # we should still treat it as a submodule of the plugin to avoid breaking existing plugins + # we should still treat it as a submodule of the plugin to avoid breaking existing plugins. + # notice: cannot merge two conditions below, because some spec with submodule_search_locations (Namespace), + # their parent is the spec itself, not the parent module name. if module_spec.parent and module_spec.parent in plugin_service.plugins: - module_spec.loader = PluginLoader(fullname, module_origin, origin_id_ or fullname, module_spec.parent) - return module_spec - # 6. the module is a submodule of a plugin, but it is not marked as a submodule by the plugin author, - # we should still treat it as a submodule of the plugin to avoid breaking existing plugins - if module_spec.name.rpartition(".")[0] in plugin_service.plugins: - module_spec.loader = PluginLoader( - fullname, module_origin, origin_id_ or fullname, module_spec.name.rpartition(".")[0] - ) - return module_spec + return _as_submodule(module_spec, fullname, module_origin, plugin_id, module_spec.parent, staged=staged) + if (parent_name := module_spec.name.rpartition(".")[0]) and parent_name in plugin_service.plugins: + return _as_submodule(module_spec, fullname, module_origin, plugin_id, parent_name, staged=staged) + # 6. force-wrap as a plugin when explicitly requested by import_plugin. + if force: + return _as_plugin(module_spec, fullname, module_origin, plugin_id, staged=staged) return -def find_spec(id_, package=None) -> ModuleSpec | None: +def _load_module(spec: ModuleSpec, config: dict | None = None): + mod = module_from_spec(spec) + spec._initializing = True # type: ignore + try: + sys.modules[spec.name] = mod + try: + if spec.loader is None: + if spec.submodule_search_locations is None: + raise ImportError("missing loader", name=spec.name) + # A namespace package so do nothing. + else: + if isinstance(spec.loader, PluginLoader): + spec.loader.exec_module(mod, config=config) + else: + spec.loader.exec_module(mod) + except: + sys.modules.pop(spec.name, None) + raise + if isinstance(spec.loader, PluginLoader): + protected_modules = set() + module_name = mod.__name__ + if module_name: + prefix = [] + for part in module_name.split("."): + prefix.append(part) + protected_modules.add(".".join(prefix)) + sys.modules.pop(module_name, None) + for _imported in _IMPORTING: + if _imported in protected_modules or _imported in plugin_service.plugins: + continue + sys.modules.pop(_imported, None) + _IMPORTING.clear() + else: + # Move the module to the end of sys.modules. + # We don't ensure that the import-related module attributes get + # set in the sys.modules replacement case. Such modules are on + # their own. + mod = sys.modules.pop(spec.name) + sys.modules[mod.__name__] = mod + finally: + spec._initializing = False # type: ignore + return mod + + +def import_plugin(id_, package=None, config: dict | None = None, staged: bool = False) -> ModuleType | None: uid_index = id_.rfind("@") name = id_ if uid_index == -1 else id_[:uid_index] fullname = resolve_name(name, package) if name.startswith(".") else name parent_name = fullname.rpartition(".")[0] + child = fullname.rpartition(".")[2] + parent_path: Sequence[str] | None = None + parent_spec: ModuleSpec | None = None if parent_name: - parent: ModuleType | None + parent: ModuleType parts = parent_name.split(".") _current = parts[0] if _current in plugin_service.plugins: parent = plugin_service.plugins[_current].module enter_plugin = True + elif _current in sys.modules: + parent = sys.modules[_current] + enter_plugin = hasattr(parent, "__plugin__") else: parent = __import__(_current, fromlist=["__path__"]) enter_plugin = False @@ -542,70 +700,45 @@ def find_spec(id_, package=None) -> ModuleSpec | None: if _current in plugin_service.plugins: parent = plugin_service.plugins[_current].module enter_plugin = True - _current += "." - continue - if _current in _ENSURE_IS_PLUGIN: - parent = import_plugin(_current) - if parent: + elif _current in sys.modules: + parent = sys.modules[_current] + enter_plugin = hasattr(parent, "__plugin__") + elif _current in _ENSURE_IS_PLUGIN or enter_plugin: + tmp = import_plugin(_current) + if tmp is not None: + parent = tmp enter_plugin = True + del tmp else: parent = __import__(_current, fromlist=["__path__"]) - _current += "." - continue - if enter_plugin and (parent := import_plugin(_current)): - pass + enter_plugin = False else: - enter_plugin = False parent = __import__(_current, fromlist=["__path__"]) _current += "." - if parent is None: + try: + parent_path = parent.__path__ + except AttributeError: raise ModuleNotFoundError( - f"parent module {parent_name!r} does not have __path__ attribute " f"while trying to find {fullname!r}", + f"parent module {parent_name!r} does not have __path__ attribute while trying to find {fullname!r}", name=fullname, ) - parent_path = parent.__path__ - else: - parent_path = None - if isinstance(parent_path, _bootstrap_external._NamespacePath): # type: ignore - parent_path = _NamespacePath(parent_path._name, parent_path._path, PathFinder._get_spec) # type: ignore - if spec := _PluginFinder.find_spec(fullname, parent_path, origin_id_=id_): - return spec - module_spec = _path_find_spec(fullname, parent_path, None) - if not module_spec: - return - module_origin = module_spec.origin - if not module_origin: - return - if isinstance(module_spec.loader, ExtensionFileLoader): + parent_spec = parent.__spec__ + # _find_spec + spec = _PluginFinder.find_spec(fullname, parent_path, origin_id_=id_, force=True, staged=staged) + if not spec: return - module_spec.loader = PluginLoader(fullname, module_origin, id_) - return module_spec - - -def import_plugin(id_, package=None, config: dict | None = None): - spec = find_spec(id_, package) - if spec: - mod = module_from_spec(spec) - if spec.loader: - if isinstance(spec.loader, PluginLoader): - spec.loader.exec_module(mod, config=config) - protected_modules = set() - module_name = mod.__name__ - if module_name: - prefix = [] - for part in module_name.split("."): - prefix.append(part) - protected_modules.add(".".join(prefix)) - sys.modules.pop(module_name, None) - for _imported in _IMPORTING: - if _imported in protected_modules or _imported in plugin_service.plugins: - continue - sys.modules.pop(_imported, None) - _IMPORTING.clear() - else: - spec.loader.exec_module(mod) - return mod - return + if parent_spec: + # Temporarily add child we are currently importing to parent's + # _uninitialized_submodules for circular import tracking. + parent_spec._uninitialized_submodules.append(child) # type: ignore + try: + mod = _load_module(spec, config=config) + finally: + if parent_spec: + parent_spec._uninitialized_submodules.pop() # type: ignore + if parent_name: + setattr(parent, child, mod.__plugin__.proxy()) + return mod sys.meta_path.insert(0, _PluginFinder()) diff --git a/arclet/entari/plugin/service.py b/arclet/entari/plugin/service.py index 680f8ab..0814b9a 100644 --- a/arclet/entari/plugin/service.py +++ b/arclet/entari/plugin/service.py @@ -1,8 +1,7 @@ from __future__ import annotations import asyncio -from collections.abc import Callable -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, NamedTuple from arclet.letoderea import es from launart import Launart, Service @@ -14,7 +13,17 @@ from ..logger import log if TYPE_CHECKING: - from .model import KeepingVariable, Plugin, RootlessPlugin + from .model import KeepingVariable, Plugin + + +class Binding(NamedTuple): + """一条名字级 import 绑定""" + + name: str + target: str + attr: str | None + last: bool + """是否为该名字的最后一次绑定(即当前在模块中生效的绑定)""" class ServiceWaiters: @@ -42,13 +51,27 @@ class PluginManagerService(Service): id = "entari.plugin.manager" plugins: dict[str, Plugin] - _keep_values: dict[str, dict[str, KeepingVariable]] + """插件字典,键为插件ID,值为插件对象""" referents: dict[str, set[str]] + """插件引用字典,键为插件ID,值为引用该插件的其他插件ID集合""" references: dict[str, set[str]] + """插件被引用字典,键为插件ID,值为该插件引用的其他插件ID集合""" + bindings: dict[str, list[Binding]] + """插件导入绑定字典,键为插件ID,值为该插件全部导入边。同名多次绑定时均保留,仅 last=True 的为当前生效绑定""" + fingerprints: dict[str, str] + """插件指纹字典,键为插件ID,值为该插件的指纹字符串""" + service_waiter: ServiceWaiters + _keep_values: dict[str, dict[str, KeepingVariable]] _direct_plugins: set[str] + """直接插件集合,存储所有直接加载(反过来即只由插件导入的插件)的插件ID""" _unloaded: set[str] + """卸载插件集合,存储所有已卸载的插件ID""" _subplugined: dict[str, str] - _apply: dict[str, tuple[Callable[[dict[str, Any]], RootlessPlugin], bool]] + """子插件字典,键为子插件ID,值为父插件ID""" + _staged: dict[str, Plugin] + """插件暂存""" + root: Plugin + """根插件对象,作为基础插件的父插件""" def __init__(self): super().__init__() @@ -59,7 +82,9 @@ def __init__(self): self.references = {} self._unloaded = set() self._subplugined = {} - self._apply = {} + self.bindings = {} + self.fingerprints = {} + self._staged = {} self.service_waiter = ServiceWaiters() @property @@ -70,6 +95,63 @@ def required(self) -> set[str]: def stages(self) -> set[Phase]: return {"preparing", "cleanup", "blocking"} + def last_bindings(self, pid: str) -> dict[str, tuple[str, str | None]]: + """插件当前生效的绑定视图 {名字: (目标模块, 属性)}(每个名字仅取最后一次绑定)""" + return {b.name: (b.target, b.attr) for b in self.bindings.get(pid, ()) if b.last} + + def dependents_of(self, path: str, ensure: bool = True) -> list[str]: + """path(及其子树)的依赖方插件:直接导入者 + 经父包再导出链一层 + + 子树匹配使整树重载时,依赖子插件的下游插件同样被处理(子插件随树重建,绑定需重绑/级联)。 + 插件(或其子模块)对自身子树的绑定属内部边,不构成依赖方,直接跳过。 + + Args: + path (str): 插件ID或其子模块路径 + ensure (bool, optional): 是否确保返回的插件ID存在于已加载插件中. Defaults to True. + + Returns: + list[str]: 依赖方插件ID列表 + """ + parent_pkg = path.rpartition(".")[0] + result: list[str] = [] + for plug_id, edges in self.bindings.items(): + if plug_id == path or plug_id.startswith(path + "."): + continue + for name, target, attr, _ in edges: + if target == path or target.startswith(path + "."): + result.append(plug_id) + break + if parent_pkg and attr is not None and target == parent_pkg and parent_pkg in self.bindings: + parent_chain = self.last_bindings(parent_pkg) + if parent_chain.get(attr or name, (None, None))[0] == path: + result.append(plug_id) + break + if ensure: + result = [r for r in result if r in self.plugins] + return result + + def topo_dependents(self, dependents: set[str]) -> list[str]: + """依赖方按 references 图拓扑排序:上游(被依赖者)优先于下游(依赖者) + + 依赖者 C 若在 B 之前级联,会绑定旧 B,随后 B 重载时, C 已在 recursive_guard 中被跳过 → 静默。 + 拓扑序保证 B 先重载。 + """ + ordered: list[str] = [] + visited: set[str] = set() + + def visit(dep_id: str): + if dep_id in visited: + return + visited.add(dep_id) + for ref in self.references.get(dep_id, ()): + if ref in dependents: + visit(ref) + ordered.append(dep_id) + + for dep_id in sorted(dependents): + visit(dep_id) + return ordered + async def launch(self, manager: Launart): servs = [] diff --git a/arclet/entari/plugin/swap.py b/arclet/entari/plugin/swap.py new file mode 100644 index 0000000..4b22d99 --- /dev/null +++ b/arclet/entari/plugin/swap.py @@ -0,0 +1,596 @@ +import ast +import builtins +import copy +import inspect +import sys +import types +from collections.abc import Iterator, Sequence +from dataclasses import dataclass +from types import ModuleType +from typing import Any + +from arclet.letoderea.scope import scope_ctx + +from ..logger import log +from .model import Plugin, PluginInspect, current_plugin + + +@dataclass(slots=True) +class FunctionChange: + qualname: str + ordinal: int + count: int + node: ast.FunctionDef | ast.AsyncFunctionDef + signature_changed: bool + append: bool = False + + +def list_dump(nodes: Sequence[ast.AST]) -> str: + """Copy from ast.py line 167-170""" + return f"[{', '.join(ast.dump(x, include_attributes=False) for x in nodes)}]" + + +def iter_functions(body: list[ast.stmt]) -> Iterator[tuple[str, int, ast.FunctionDef | ast.AsyncFunctionDef]]: + """按 qualname 收集模块顶层函数与类方法(含嵌套类)""" + + def _walk(body: list[ast.stmt], prefix: str) -> Iterator[tuple[str, int, ast.FunctionDef | ast.AsyncFunctionDef]]: + seen: dict[str, int] = {} + for stmt in body: + if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)): + qualname = f"{prefix}{stmt.name}" + ordinal = seen.get(stmt.name, 0) + seen[stmt.name] = ordinal + 1 + yield qualname, ordinal, stmt + elif isinstance(stmt, ast.ClassDef): + yield from _walk(stmt.body, f"{prefix}{stmt.name}.") + + yield from _walk(body, "") + + +def _signature_names(node: ast.FunctionDef | ast.AsyncFunctionDef | ast.Lambda) -> set[str]: + names = {arg.arg for arg in node.args.posonlyargs + node.args.args + node.args.kwonlyargs} + if node.args.vararg: + names.add(node.args.vararg.arg) + if node.args.kwarg: + names.add(node.args.kwarg.arg) + return names + + +def _comp_targets(gen: ast.comprehension) -> set[str]: + targets: set[str] = set() + stack: list[ast.AST] = [gen.target] + while stack: + node = stack.pop() + if isinstance(node, ast.Name): + targets.add(node.id) + elif isinstance(node, (ast.Tuple, ast.List)): + stack.extend(node.elts) + elif isinstance(node, ast.Starred): + stack.append(node.value) + return targets + + +def _structurally_same(o: ast.stmt, n: ast.stmt) -> bool: + """结构比较:函数/方法体跳过(由 qualname 配对分析),其余须 dump 相等""" + if isinstance(o, (ast.FunctionDef, ast.AsyncFunctionDef)) and isinstance( + n, (ast.FunctionDef, ast.AsyncFunctionDef) + ): + return True + + if isinstance(o, ast.ClassDef) and isinstance(n, ast.ClassDef): + if list_dump(o.decorator_list) != list_dump(n.decorator_list): + return False + if list_dump(o.bases) != list_dump(n.bases): + return False + if list_dump(o.keywords) != list_dump(n.keywords): + return False + if len(o.body) != len(n.body): + return False + return all(_structurally_same(x, y) for x, y in zip(o.body, n.body)) + return ast.dump(o, include_attributes=False) == ast.dump(n, include_attributes=False) + + +def _import_names(stmt: ast.stmt) -> dict[str, str]: + """import 语句绑定的 {名字: 目标模块}""" + if isinstance(stmt, ast.Import): + return {alias.asname or alias.name.split(".")[0]: alias.name for alias in stmt.names} + if isinstance(stmt, ast.ImportFrom): + module = "." * (stmt.level or 0) + (stmt.module or "") + return {alias.asname or alias.name: f"{module}.{alias.name}" for alias in stmt.names if alias.name != "*"} + return {} + + +def _align_prefix( + old_body: list[ast.stmt], new_body: list[ast.stmt] +) -> tuple[dict[str, str], dict[str, str], list[tuple[ast.stmt, ast.stmt]], list[ast.stmt]] | None: + """对齐新旧模块前缀:允许 import 语句就地变化/增删,其余语句须结构相同 + + 返回 (旧 import 绑定, 新 import 绑定, 对齐的非 import 语句对, 新尾部)。 + None 表示结构无法匹配(需全量重载)。 + """ + old_imports: dict[str, str] = {} + new_imports: dict[str, str] = {} + pairs: list[tuple[ast.stmt, ast.stmt]] = [] + i = j = 0 + while i < len(old_body) and j < len(new_body): + o, n = old_body[i], new_body[j] + if isinstance(o, (ast.Import, ast.ImportFrom)) and isinstance(n, (ast.Import, ast.ImportFrom)): + old_imports.update(_import_names(o)) + new_imports.update(_import_names(n)) + i += 1 + j += 1 + elif isinstance(o, (ast.Import, ast.ImportFrom)): + old_imports.update(_import_names(o)) + i += 1 + elif isinstance(n, (ast.Import, ast.ImportFrom)): + new_imports.update(_import_names(n)) + j += 1 + elif _structurally_same(o, n): + pairs.append((o, n)) + i += 1 + j += 1 + else: + return None + if i != len(old_body): + return None + return old_imports, new_imports, pairs, new_body[j:] + + +def _changed_import_names(old_imports: dict[str, str], new_imports: dict[str, str]) -> set[str]: + """新旧 import 绑定的变更名集合(新增/移除/改绑)""" + common = set(old_imports) & set(new_imports) + return (set(old_imports) ^ set(new_imports)) | {n for n in common if old_imports[n] != new_imports[n]} + + +class _GlobalNameCollector: + """作用域感知的名字收集器:收集会在模块全局或 builtins 解析的名字 + + 规则:Name(Load) 在自身与所有外层函数作用域均未绑定 → 全局名。 + """ + + __slots__ = ("used", "stack") + + def __init__(self): + self.used: set[str] = set() + self.stack: list[set[str]] = [set()] + + def _bound(self, name: str) -> bool: + return any(name in scope for scope in self.stack) + + def _bind(self, name: str): + self.stack[-1].add(name) + + def collect(self, stmts: list[ast.stmt], initial: set[str] | None = None) -> set[str]: + self.used = set() + self.stack = [initial or set()] + for stmt in stmts: + self.visit_stmt(stmt) + return self.used + + def visit_expr(self, expr: ast.expr): + if isinstance(expr, ast.Name): + if isinstance(expr.ctx, ast.Load) and not self._bound(expr.id): + self.used.add(expr.id) + elif isinstance(expr.ctx, ast.Store): + self._bind(expr.id) + elif isinstance(expr, ast.Lambda): + self.stack.append(_signature_names(expr)) + self.visit_expr(expr.body) + self.stack.pop() + elif isinstance(expr, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)): + comps = expr.generators + if comps: + self.visit_expr(comps[0].iter) + self.stack.append({name for gen in comps for name in _comp_targets(gen)}) + for gen in comps: + if gen is not comps[0]: + self.visit_expr(gen.iter) + for cond in gen.ifs: + self.visit_expr(cond) + if isinstance(expr, ast.DictComp): + self.visit_expr(expr.key) + self.visit_expr(expr.value) + else: + self.visit_expr(expr.elt) + self.stack.pop() + else: + for child in ast.iter_child_nodes(expr): + if isinstance(child, ast.expr): + self.visit_expr(child) + elif isinstance(child, ast.keyword) and child.value is not None: + self.visit_expr(child.value) + + def visit_stmt(self, stmt: ast.stmt): + if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)): + self._bind(stmt.name) + for dec in stmt.decorator_list: + self.visit_expr(dec) + for default in [*stmt.args.defaults, *[d for d in stmt.args.kw_defaults if d]]: + self.visit_expr(default) + for arg in stmt.args.posonlyargs + stmt.args.args + stmt.args.kwonlyargs: + if arg.annotation: + self.visit_expr(arg.annotation) + if stmt.args.vararg and stmt.args.vararg.annotation: + self.visit_expr(stmt.args.vararg.annotation) + if stmt.args.kwarg and stmt.args.kwarg.annotation: + self.visit_expr(stmt.args.kwarg.annotation) + self.stack.append(_signature_names(stmt)) + for body_stmt in stmt.body: + self.visit_stmt(body_stmt) + self.stack.pop() + elif isinstance(stmt, ast.ClassDef): + self._bind(stmt.name) + for dec in stmt.decorator_list: + self.visit_expr(dec) + for base in stmt.bases: + self.visit_expr(base) + for kw in stmt.keywords: + self.visit_expr(kw.value) + self.stack.append(set()) + for body_stmt in stmt.body: + self.visit_stmt(body_stmt) + self.stack.pop() + elif isinstance(stmt, ast.Assign): + for target in stmt.targets: + self.visit_expr(target) + self.visit_expr(stmt.value) + elif isinstance(stmt, ast.AnnAssign): + self.visit_expr(stmt.target) + self.visit_expr(stmt.annotation) + if stmt.value: + self.visit_expr(stmt.value) + elif isinstance(stmt, ast.AugAssign): + self.visit_expr(stmt.target) + self.visit_expr(stmt.value) + elif isinstance(stmt, (ast.For, ast.AsyncFor)): + self.visit_expr(stmt.target) + self.visit_expr(stmt.iter) + for body_stmt in [*stmt.body, *stmt.orelse]: + self.visit_stmt(body_stmt) + elif isinstance(stmt, (ast.With, ast.AsyncWith)): + for item in stmt.items: + self.visit_expr(item.context_expr) + if item.optional_vars: + self.visit_expr(item.optional_vars) + for body_stmt in stmt.body: + self.visit_stmt(body_stmt) + elif isinstance(stmt, ast.Try) or (sys.version_info >= (3, 11) and isinstance(stmt, ast.TryStar)): + for body_stmt in [*stmt.body, *stmt.orelse, *stmt.finalbody]: + self.visit_stmt(body_stmt) + for handler in stmt.handlers: + if handler.type: + self.visit_expr(handler.type) + if handler.name: + self._bind(handler.name) + for body_stmt in handler.body: + self.visit_stmt(body_stmt) + elif isinstance(stmt, ast.Import): + for alias in stmt.names: + self._bind(alias.asname or alias.name.split(".")[0]) + elif isinstance(stmt, ast.ImportFrom): + for alias in stmt.names: + self._bind(alias.asname or alias.name) + elif isinstance(stmt, ast.If): + self.visit_expr(stmt.test) + for body_stmt in [*stmt.body, *stmt.orelse]: + self.visit_stmt(body_stmt) + elif isinstance(stmt, ast.While): + self.visit_expr(stmt.test) + for body_stmt in [*stmt.body, *stmt.orelse]: + self.visit_stmt(body_stmt) + else: + for child in ast.iter_child_nodes(stmt): + if isinstance(child, ast.expr): + self.visit_expr(child) + elif isinstance(child, ast.stmt): + self.visit_stmt(child) + + +def _collect_unchanged_references( + pairs: list[tuple[ast.stmt, ast.stmt]], changes: list[FunctionChange], new_nodes: ast.Module +) -> set[str]: + """收集未变更代码(顶层语句、类级语句、未变更函数/方法体)引用的全局名""" + collector = _GlobalNameCollector() + used: set[str] = set() + for _, new_stmt in pairs: + if isinstance(new_stmt, (ast.Import, ast.ImportFrom, ast.FunctionDef, ast.AsyncFunctionDef)): + continue + if isinstance(new_stmt, ast.ClassDef): + class_stmts = [s for s in new_stmt.body if not isinstance(s, (ast.FunctionDef, ast.AsyncFunctionDef))] + if class_stmts: + used |= collector.collect(class_stmts) + continue + used |= collector.collect([new_stmt]) + changed_nodes = {c.node for c in changes} + for _, _, fn in iter_functions(new_nodes.body): + if fn in changed_nodes: + continue + used |= collector.collect(fn.body, _signature_names(fn)) + return used + + +def classify(old_nodes: ast.Module, new_nodes: ast.Module) -> list[FunctionChange] | None: + """比较新旧模块的 AST,返回可以热替换的函数列表 + + 函数配对:先按(名称组, 完整节点 dump 相同)锚定未变化函数(容忍位置交换/重排), + 剩余函数按组内顺序配对比较(容忍原地编辑);重命名(组键变化)与增删(组内数量不匹配)走全量重载。 + 文件底部追加的新函数 走 append 路径(整句执行,含装饰器注册,要求旧 body 是新的结构前缀且尾部新增全为函数定义)。 + 前缀中的 import 语句允许就地变化/增删,条件是变更的绑定名不被任何未变更代码引用(否则走全量重载)。 + + Returns: + 可就地替换的函数列表,None 表示模块整体需要全量重载 + """ + old_body = old_nodes.body + new_body = new_nodes.body + aligned = _align_prefix(old_body, new_body) + if aligned is None: + return None + old_imports, new_imports, pairs, tail = aligned + if tail and not all( + isinstance(s, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Import, ast.ImportFrom)) for s in tail + ): + return None + old_groups: dict[str, list[tuple[int, ast.FunctionDef | ast.AsyncFunctionDef]]] = {} + new_groups: dict[str, list[tuple[int, ast.FunctionDef | ast.AsyncFunctionDef]]] = {} + for qualname, ordinal, fn in iter_functions([o for o, _ in pairs]): + old_groups.setdefault(qualname, []).append((ordinal, fn)) + for qualname, ordinal, fn in iter_functions([n for _, n in pairs]): + new_groups.setdefault(qualname, []).append((ordinal, fn)) + if set(old_groups.keys()) != set(new_groups.keys()): + return None + changes: list[FunctionChange] = [] + for qualname, old_list in old_groups.items(): + new_list = new_groups[qualname] + if len(old_list) != len(new_list): + return None + used_old: set[int] = set() + used_new: set[int] = set() + for i, (_, o_node) in enumerate(old_list): + for j, (_, n_node) in enumerate(new_list): + if ( + i not in used_old + and j not in used_new + and ast.dump(o_node, include_attributes=False) == ast.dump(n_node, include_attributes=False) + ): + used_old.add(i) + used_new.add(j) + break + rest_old = [old_list[i] for i in range(len(old_list)) if i not in used_old] + rest_new = [new_list[j] for j in range(len(new_list)) if j not in used_new] + for (o_ord, o_node), (n_ord, n_node) in zip(rest_old, rest_new): + if list_dump(n_node.decorator_list) != list_dump(o_node.decorator_list): + return None + signature_changed = n_node.__class__ is not o_node.__class__ or ast.dump( + n_node.args, include_attributes=False + ) != ast.dump(o_node.args, include_attributes=False) + # if signature_changed or list_dump(n_node.body) != list_dump(o_node.body): + changes.append(FunctionChange(qualname, o_ord, len(old_list), n_node, signature_changed)) + for stmt in tail: + if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)): + changes.append(FunctionChange(stmt.name, 0, 1, stmt, signature_changed=False, append=True)) + if changed_import_names := _changed_import_names(old_imports, new_imports): + used = _collect_unchanged_references(pairs, changes, new_nodes) + if changed_import_names & used: + log.plugin.debug( + f"changed import names {sorted(changed_import_names & used)!r} are referenced " + f"by unchanged code, fallback to full reload" + ) + return None + return changes + + +def collect_global_names(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> set[str]: + """收集新函数体中所有会在模块全局或 builtins 解析的名字(含嵌套函数)""" + collector = _GlobalNameCollector() + return collector.collect(fn.body, initial=_signature_names(fn)) + + +def _unwrap_fn(target: Any) -> types.FunctionType | None: + """从模块/类绑定或 Subscriber 中取出裸函数对象""" + if isinstance(target, types.FunctionType): + return target + if isinstance(target, (classmethod, staticmethod)): + inner = target.__func__ + return inner if isinstance(inner, types.FunctionType) else None + if hasattr(target, "callable_target"): + inner = getattr(target, "callable_target") + return inner if isinstance(inner, types.FunctionType) else None + return None + + +def _resolve_old_fn(plugin: Plugin, qualname: str, ordinal: int, count: int) -> types.FunctionType | None: + """按 (qualname, 序号) 解析活模块中的旧函数对象;返回 None 表示不可就地替换 + + 最后一个同名定义经模块/类绑定解析(模块执行时同名后者胜出); + 非最后一个同名定义只能经插件 scope 的 Subscriber 按名字+注册序解析 + (即 `_` 惯用法:多个 `@plugin.listen` 装饰的同名函数)。 + """ + module = plugin.module + parts = qualname.split(".") + if len(parts) > 1: + if ordinal != count - 1: + return None + parent = module.__dict__.get(parts[0]) + if parent is None: + return None + for part in parts[1:-1]: + parent = inspect.getattr_static(parent, part, None) + if parent is None: + return None + return _unwrap_fn(inspect.getattr_static(parent, parts[-1], None)) + if ordinal == count - 1: + return _unwrap_fn(module.__dict__.get(parts[0])) + candidates = [ + slot.subscriber.callable_target + for slot in plugin._scope.subscribers + if isinstance(slot.subscriber.callable_target, types.FunctionType) + and slot.subscriber.callable_target.__name__ == parts[0] + ] + if len(candidates) != count: + return None + return candidates[ordinal] + + +def _build_new_fn(new_node: ast.FunctionDef | ast.AsyncFunctionDef, module: ModuleType) -> types.FunctionType | None: + """剥掉装饰器后在临时命名空间执行新函数定义,返回新函数对象""" + node = copy.deepcopy(new_node) + node.decorator_list = [] + ast.fix_missing_locations(node) + try: + code = compile( + ast.Module(body=[node], type_ignores=[]), + module.__name__, + "exec", + dont_inherit=True, + optimize=-1, + ) + ns = dict(module.__dict__) + exec(code, ns) + except Exception as e: + log.plugin.error(f"failed to build new function {node.name!r}: {e!r}") + return None + fn = ns.get(node.name) + return fn if isinstance(fn, types.FunctionType) else None + + +def _decorator_global_names(node: ast.FunctionDef | ast.AsyncFunctionDef) -> set[str]: + """装饰器表达式中的全局引用(append 路径整句执行时需解析)""" + used: set[str] = set() + for dec in node.decorator_list: + for name in ast.walk(dec): + if isinstance(name, ast.Name) and isinstance(name.ctx, ast.Load): + used.add(name.id) + return used + + +def _exec_imports(plugin: Plugin, nodes: ast.Module) -> bool: + """将新源码中的 import 语句按源码顺序执行并写入模块中""" + module = plugin.module + for stmt in nodes.body: + if not isinstance(stmt, (ast.Import, ast.ImportFrom)): + continue + stmt_cp = copy.deepcopy(stmt) + ast.fix_missing_locations(stmt_cp) + code = compile( + ast.Module(body=[stmt_cp], type_ignores=[]), + module.__name__, + "exec", + dont_inherit=True, + optimize=-1, + ) + try: + exec(code, module.__dict__) + except Exception as e: + log.plugin.error(f"failed to exec import statement at line {stmt.lineno}: {e!r}") + return False + return True + + +def _exec_append(plugin: Plugin, node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool: + """在插件上下文(current_plugin + scope)中整句执行新增语句,完成装饰器注册""" + module = plugin.module + stmt = copy.deepcopy(node) + ast.fix_missing_locations(stmt) + code = compile( + ast.Module(body=[stmt], type_ignores=[]), + module.__name__, + "exec", + dont_inherit=True, + optimize=-1, + ) + token = current_plugin.set(plugin) + try: + if not plugin.is_static: + token1 = scope_ctx.set(plugin._scope) + try: + exec(code, module.__dict__) + finally: + scope_ctx.reset(token1) + else: + exec(code, module.__dict__) + finally: + current_plugin.reset(token) + return True + + +def swap_functions(plugin: Plugin, new_nodes: ast.Module, changes: list[FunctionChange]) -> bool: + """就地替换函数实现,如果会影响插件自身或下游依赖方则返回 False,调用方走全量重载""" + module = plugin.module + import_names = { + name + for stmt in new_nodes.body + if isinstance(stmt, (ast.Import, ast.ImportFrom)) + for name in _import_names(stmt) + } + resolved: list[tuple[types.FunctionType, types.FunctionType, FunctionChange]] = [] + for change in changes: + new_fn = change.node + free_names = collect_global_names(new_fn) + missing = { + name + for name in free_names + if name not in module.__dict__ and name not in vars(builtins) and name not in import_names + } + if missing: + log.plugin.warning( + f"cannot hot-swap {change.qualname!r}: free names {missing!r} missing, " + "fallback to full reload" + ) + return False + if change.append: + free_names |= _decorator_global_names(new_fn) + missing = { + name + for name in free_names + if name not in module.__dict__ and name not in vars(builtins) and name not in import_names + } + if missing: + log.plugin.warning( + f"cannot append {change.qualname!r}: free names {missing!r} missing, " + "fallback to full reload" + ) + return False + continue + old_fn = _resolve_old_fn(plugin, change.qualname, change.ordinal, change.count) + if old_fn is None: + log.plugin.warning(f"cannot resolve {change.qualname!r} in module, fallback to full reload") + return False + if change.signature_changed: + from ..command import _commands + + if any(sub.callable_target is old_fn for sub in _commands.subscribers.values()): + log.plugin.warning( + f"signature of command target {change.qualname!r} changed, " "fallback to full reload" + ) + return False + new_function = _build_new_fn(new_fn, module) + if new_function is None: + return False + resolved.append((old_fn, new_function, change)) + if not _exec_imports(plugin, new_nodes): + return False + for old_fn, new_function, change in resolved: + old_fn.__code__ = new_function.__code__ + old_fn.__defaults__ = new_function.__defaults__ + old_fn.__kwdefaults__ = new_function.__kwdefaults__ + old_fn.__annotations__ = new_function.__annotations__ + for slot in plugin._scope.subscribers: + if slot.subscriber.callable_target is not old_fn: + continue + sub = slot.subscriber + sub.cancel_running() + if change.signature_changed: + sub.callable_target = new_function + try: + sub._recompile() + except Exception as e: + log.plugin.error(f"failed to recompile subscriber of {change.qualname!r}: {e!r}") + for change in changes: + if change.append: + try: + _exec_append(plugin, change.node) + except Exception as e: + log.plugin.error(f"failed to append {change.qualname!r}: {e!r}") + return False + plugin._inspect = PluginInspect(new_nodes, ast.dump(new_nodes, include_attributes=False)) + return True diff --git a/arclet/entari/scheduler.py b/arclet/entari/scheduler.py index 060739a..66549b4 100644 --- a/arclet/entari/scheduler.py +++ b/arclet/entari/scheduler.py @@ -13,7 +13,7 @@ from .config import BasicConfModel from .event.config import ConfigReload from .logger import log -from .plugin import PluginRole, RootlessPlugin, get_plugin, metadata, plugin_config +from .plugin import PluginRole, get_plugin, metadata, niche, plugin_config @make_event(name="entari.event/internal/schedule") @@ -137,8 +137,8 @@ class _SchedulerConf(BasicConfModel): schedule = scheduler.schedule -@RootlessPlugin.apply("scheduler", default=True) -def _(plg: RootlessPlugin): +@niche("scheduler") +def _(plg): metadata( "Simple Scheduler with interval / crontab task", PluginRole.LIBRARY, diff --git a/arclet/entari/session.py b/arclet/entari/session.py index 854c2b7..45d52a0 100644 --- a/arclet/entari/session.py +++ b/arclet/entari/session.py @@ -1,6 +1,9 @@ import asyncio +import inspect import secrets from collections.abc import Awaitable, Callable, Iterable +from functools import wraps +from types import MethodType from typing import Any, Generic, NoReturn, cast, overload from typing_extensions import TypeVar @@ -26,6 +29,7 @@ from . import command from .config import EntariConfig +from .event.api import APIRequest, APIResponse, SendRequest, SendResponse from .event.base import ( FriendRequestEvent, GuildMemberRequestEvent, @@ -35,7 +39,6 @@ Reply, SatoriEvent, ) -from .event.send import SendRequest, SendResponse from .message import MessageChain, Render TEvent = TypeVar("TEvent", bound=SatoriEvent, default=SatoriEvent) @@ -55,14 +58,51 @@ async def rule(elem: Element, sess: "Session"): return await content.transform_async(rule, session) +STATIC_METHODS = frozenset( + { + "__init__", + "call_api", + "request_internal", + "send", + "send_message", + "send_private_message", + "update_message", + "message_create", + } +) + + class EntariProtocol(ApiProtocol): # fmt: off + def __init__(self, account: Account["EntariProtocol"]): + super().__init__(account) + funcs = inspect.getmembers(self, predicate=lambda x: isinstance(x, MethodType)) + for name, func in funcs: + if name in STATIC_METHODS: + continue + + @wraps(func) + async def wrapper(*args, _func=func, _sig=inspect.signature(func), **kwargs): + bounds = _sig.bind(*args, **kwargs) + bounds.apply_defaults() + try: + if result := await es.post(APIRequest(self.account, _func.__name__, bounds.arguments)): + ans = result.value + else: + ans = await _func(**bounds.arguments) + success = True + except Exception as e: + ans = e + success = False + await es.publish(APIResponse(self.account, _func.__name__, bounds.arguments, success, ans)) + return ans + setattr(self, name, wrapper) + async def send_message(self, channel: str | Channel, message: str | Iterable[str | Element], at_sender: At | None = None, reply_to: Quote | None = None, referrer: dict[str, Any] | None = None) -> list[MessageObject]: # noqa: E501 """发送消息。返回一个 `MessageReceipt` 对象构成的数组。 - Args: - channel (str | Channel): 要发送的频道 ID + Args: channel (str | Channel): 要发送的频道 ID message (str | Iterable[str | Element]): 要发送的消息 at_sender (At | None): 是否 @ 发送者,默认为 None reply_to (Quote | None): 是否作为回复发送,默认为 None @@ -123,7 +163,7 @@ async def message_create(self, channel_id: str, content: str | Iterable[str | El msg = await component_transform(sess, msg) referrer = {k: v for k, v in referrer.items() if k != "source"} sess.elements = msg - btns = select(msg, Button) + btns = select(msg.content, Button) for btn in btns: if btn.type != "link" and not btn.id: btn.id = secrets.token_urlsafe(16) diff --git a/arclet/entari/utils.py b/arclet/entari/utils.py index b02c556..baf0488 100644 --- a/arclet/entari/utils.py +++ b/arclet/entari/utils.py @@ -1,7 +1,4 @@ import re -from typing import TypeVar - -T = TypeVar("T") def escape_tag(s: str) -> str: diff --git a/entari.schema.json b/entari.schema.json index 46c7d76..8872b08 100644 --- a/entari.schema.json +++ b/entari.schema.json @@ -77,6 +77,23 @@ "default": false, "description": "是否利用元数据进行插件导入检测(可能会增加启动时间)", "title": "Check Metadata" + }, + "str_as_message": { + "type": "boolean", + "default": true, + "description": "发送字符串时是否自动转换为消息链", + "title": "Str As Message" + }, + "superusers": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": "超级用户配置,键为平台名称,值为该平台的超级用户 ID 列表", + "title": "Superusers" } }, "additionalProperties": false, @@ -102,7 +119,14 @@ "title": "Host" }, "port": { - "type": "integer", + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ], "default": 5140, "description": "服务器端口", "title": "Port" @@ -156,7 +180,14 @@ "title": "Host" }, "port": { - "type": "integer", + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ], "default": 8080, "description": "本机 Webhook 服务器端口", "title": "Port" @@ -185,9 +216,16 @@ "title": "Server Host" }, "server_port": { - "type": "integer", + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ], "default": 5140, - "description": "发送请求的目标服务器端口", + "description": "发送请求的目标服务器端port", "title": "Server Port" }, "server_path": { @@ -306,14 +344,21 @@ "type": "string" }, "plugins": { - "description": "List of plugins under the prefix", - "items": { - "type": "string", - "description": "Plugin name" - }, - "title": "Plugins", - "type": "array", - "uniqueItems": true + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string", + "description": "Plugin name" + }, + "type": "array", + "uniqueItems": true + } + ], + "description": "List of plugins under the prefix, or select an item of $files to apply plugins", + "title": "Plugins" } }, "required": [ @@ -425,6 +470,12 @@ "description": "是否记录发送的消息", "title": "Record Send" }, + "short_message": { + "type": "boolean", + "default": false, + "description": "是否在日志中使用简短的消息内容", + "title": "Short Message" + }, "$disable": { "type": "string", "description": "Expression for whether disable this plugin" @@ -443,9 +494,13 @@ }, ".scheduler": { "type": "object", - "description": "Simple Scheduler with interval / crontab task; no configuration required", - "additionalProperties": true, + "title": "_SchedulerConf", "properties": { + "debug": { + "type": "boolean", + "default": true, + "title": "Debug" + }, "$disable": { "type": "string", "description": "Expression for whether disable this plugin" @@ -458,7 +513,9 @@ "type": "string", "description": "Plugin filter expression, which will be evaluated in the context of the plugin" } - } + }, + "additionalProperties": false, + "description": "Simple Scheduler with interval / crontab task" }, "reusable@1": { "type": "object", diff --git a/entari.yml b/entari.yml index 5c21ebd..3836cc7 100644 --- a/entari.yml +++ b/entari.yml @@ -42,9 +42,7 @@ plugins: output: bar! example_plugin2: $filter: > - (platform in ("onebot", "milky") and member.roles[0].id ne 'member') - or - platform eq "console" + (platform in ("onebot", "milky") and member.roles[0].id ne 'member') or platform eq "console" example_plugin3: $filter: message == "test_plugin3" ?example_plugin4: {} diff --git a/example_plugin.py b/example_plugin.py index f51ddf8..d535845 100644 --- a/example_plugin.py +++ b/example_plugin.py @@ -32,7 +32,7 @@ async def cleanup(): @plug.dispatch(MessageCreatedEvent) @filter_.public -async def _(session: Session): +async def invoke_test(session: Session): if session.content == "test": resp = await session.send("This message will recall in 5s...", at_sender=True) @@ -40,6 +40,7 @@ async def _(session: Session): async def _(): await session.message_delete(resp[0].id) + disp_message = plug.dispatch(MessageCreatedEvent) @@ -49,13 +50,13 @@ async def filter_content(session: Session): @disp_message.on().if_(filter_content) -async def _(session: Session): +async def filter_content1(session: Session): return await session.send("Filter: public message, to me, and content is 'aaa'") @disp_message -@filter_.public & filter_.to_me & filter_(lambda sess: str(sess.content) != "aaa") -async def _(session: Session): +@ filter_.public & filter_.to_me & filter_(lambda sess: str(sess.content) != "aaa") +async def filter_content2(session: Session): return await session.send("Filter: public message, to me, but content is not 'aaa'") @@ -84,6 +85,7 @@ async def show(session: Session): await session.send_message(f"Execute `echo 123` Result: {res}") return f"Data: {kept_data}" + TEST = 7 print([*Plugin.current()._scope.subscribers]) @@ -123,6 +125,7 @@ async def output_hook(event: CommandOutput): content = event.content return f"{event.type.title()}:\n{content}" + # @scheduler.cron("* * * * *") # async def broadcast(app: Entari): # for account in app.accounts.values(): diff --git a/example_plugins/example_plugin2/__init__.py b/example_plugins/example_plugin2/__init__.py index 73c6b2a..4aff82a 100644 --- a/example_plugins/example_plugin2/__init__.py +++ b/example_plugins/example_plugin2/__init__.py @@ -1,16 +1,31 @@ -from arclet.entari import metadata, inject, listen +from typing import Annotated + +from arclet.entari import metadata, listen from arclet.entari.event.lifespan import Startup from . import command -from example_plugin import kept_data +from arclet.entari.builtins.auto_reload import Watcher +from arclet.entari.core import on_stage metadata(__file__) @listen(Startup) -@inject({"id": "entari.plugin.auto_reload/watcher", "stage": "blocking"}) # type: ignore -async def startup(): - print(kept_data) +async def startup1(_: Watcher): + print(">>>>>>>>>>>>>>>>>>>>>>>>>>>> startup1 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<") + print("waiting for watcher to be prepared...") + + +@listen(Startup) +async def startup2(_: Annotated[Watcher, on_stage("blocking")]): + print(">>>>>>>>>>>>>>>>>>>>>>>>>>>> startup2 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<") + print("waiting for watcher to be blocking...") + + +@listen(Startup) +async def startup3(_: Watcher = on_stage("prepared")): + print(">>>>>>>>>>>>>>>>>>>>>>>>>>>> startup3 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<") + print("waiting for watcher to be prepared...") # from example_reusable import conf # entari: plugin diff --git a/example_plugins/example_plugin5/foo/bar.py b/example_plugins/example_plugin5/foo/bar.py index 23f5ad6..93d461a 100644 --- a/example_plugins/example_plugin5/foo/bar.py +++ b/example_plugins/example_plugin5/foo/bar.py @@ -2,10 +2,9 @@ from ..tools.qux import calc - metadata(__file__, description="A test plugin 5") -@command.on("exam5 ") -def exam5(x: int, y: int): +@command.on("exam5 {x} {y}") +async def exam5(x: int, y: int): return f"example_plugin5: {x} * {y} = {calc(x, y)}" diff --git a/example_plugins/example_plugin6/foo/__init__.py b/example_plugins/example_plugin6/foo/__init__.py index f3c2515..bafb447 100644 --- a/example_plugins/example_plugin6/foo/__init__.py +++ b/example_plugins/example_plugin6/foo/__init__.py @@ -1 +1 @@ -from . import bar as bar \ No newline at end of file +from . import bar as bar diff --git a/example_plugins/example_plugin6/foo/bar.py b/example_plugins/example_plugin6/foo/bar.py index fc02c29..6a74e79 100644 --- a/example_plugins/example_plugin6/foo/bar.py +++ b/example_plugins/example_plugin6/foo/bar.py @@ -5,4 +5,3 @@ @command.on("exam6") def exam6(): return f"example_plugin6 token: {generate_token()}" - diff --git a/example_plugins/example_plugin7/__init__.py b/example_plugins/example_plugin7/__init__.py new file mode 100644 index 0000000..05c121a --- /dev/null +++ b/example_plugins/example_plugin7/__init__.py @@ -0,0 +1,26 @@ +from arclet.entari import BasicConfModel, metadata, plugin_config, register_schema + +from .config import Config +from . import listener + +metadata(__file__, description="1", config=Config) +conf = plugin_config(Config) + + +class Foo(BasicConfModel): + abc: int + + +class Bar(BasicConfModel): + xyz: str + + +if conf.tools: + register_schema( + "properties.tools", {"type": "object", "description": "Tools configuration", "properties": {}}, replace=True + ) + for tool in conf.tools: + if tool == "foo": + register_schema(f"properties.tools.properties.{tool}", Foo) + elif tool == "bar": + register_schema(f"properties.tools.properties.{tool}", Bar) diff --git a/example_plugins/example_plugin7/config.py b/example_plugins/example_plugin7/config.py new file mode 100644 index 0000000..edb8f3c --- /dev/null +++ b/example_plugins/example_plugin7/config.py @@ -0,0 +1,7 @@ +from arclet.entari.config import BasicConfModel, model_field + + +class Config(BasicConfModel): + input: str + output: str + tools: dict[str, dict] = model_field(default_factory=dict) diff --git a/example_plugins/example_plugin7/listener.py b/example_plugins/example_plugin7/listener.py new file mode 100644 index 0000000..75a9d18 --- /dev/null +++ b/example_plugins/example_plugin7/listener.py @@ -0,0 +1,15 @@ +from arclet.entari import BasicConfModel, MessageCreatedEvent, plugin + + +class Config(BasicConfModel): + foo: str + bar: str + + +plugin.metadata(__file__, config=Config) +conf = plugin.get_config(Config) + + +@plugin.listen(MessageCreatedEvent) +async def _(): + print(conf, 2) diff --git a/example_plugins/example_plugin8.py b/example_plugins/example_plugin8.py new file mode 100644 index 0000000..7998899 --- /dev/null +++ b/example_plugins/example_plugin8.py @@ -0,0 +1,35 @@ +from arclet.entari.filter.message import startswith, regexmatch, regex_origin +from arclet.entari import MessageCreatedEvent, MessageChain, Session, listen, Image, Text, User + + +@listen(MessageCreatedEvent).if_(startswith("!hello")) +async def hello_listener1(sess: Session, message: MessageChain, user: User): + await sess.send(f"Hello! This is a response from the hello_listener. {user}") + await sess.send(message) + + +@listen(MessageCreatedEvent).if_(startswith(Image, include=True)) +async def image_listener(sess: Session, message: MessageChain): + await sess.send("Hello! This is a response from the image_listener.") + await sess.send(message) + + +@listen(MessageCreatedEvent).if_(startswith("!world", bind="world")) +async def hello_listener2(sess: Session, message: MessageChain, world: MessageChain): + await sess.send("Hello! This is a response from the hello_listener2.") + await sess.send(message) + await sess.send(world) + + +@listen(MessageCreatedEvent).if_(regexmatch(r"test (\d+)", flags=2)) +async def regex_listener( + sess: Session, + message: MessageChain, + match=regex_origin(), + group1: str = regex_origin().group(1), + dicts: dict = regex_origin().groupdict(), +): + await sess.send(f"Hello! This is a response from the regex_listener. You said: {message}") + await sess.send(f"Matched: {Text(str(match))}") + await sess.send(f"Matched group 1: {group1}") + await sess.send(f"Matched dicts: {dicts}") diff --git a/old/arclet/edoves/__init__.py b/old/arclet/edoves/__init__.py deleted file mode 100644 index 8762235..0000000 --- a/old/arclet/edoves/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from typing import TYPE_CHECKING - -if not TYPE_CHECKING: - import arclet.edoves.builtin.event - -edoves_version = (0, 0, 15) diff --git a/old/arclet/edoves/builtin/__init__.py b/old/arclet/edoves/builtin/__init__.py deleted file mode 100644 index 7198ec7..0000000 --- a/old/arclet/edoves/builtin/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Edoves 内置组件""" diff --git a/old/arclet/edoves/builtin/actions.py b/old/arclet/edoves/builtin/actions.py deleted file mode 100644 index b40704e..0000000 --- a/old/arclet/edoves/builtin/actions.py +++ /dev/null @@ -1,133 +0,0 @@ -import inspect -from typing import TYPE_CHECKING, Any, Optional, Union - -from arclet.edoves.main.action import ExecutiveAction - -from .medium import Message, Request - -if TYPE_CHECKING: - from arclet.edoves.main.interact.monomer import Monomer - - -class GetMonomer(ExecutiveAction): - mono_id: str - rs: str - - def __init__(self, target: Union[int, str], relationship: str, whole: bool = False, **kwargs): - self.mono_id = str(target) - self.rs = relationship - self.rest = kwargs - self.rest.setdefault("list_all", whole) - super().__init__("relationship_get") - - async def execute(self) -> "Monomer": - entity = self.target.protocol.current_scene.monomer_map.get(self.mono_id) - if not entity: - return await self.target.action(self.action)(self.mono_id, self.rs, **self.rest) - return entity - - -class ChangeStatus(ExecutiveAction): - status: str - value: Any - - def __init__(self, target: "Monomer", status: str, value: Any, **kwargs): - super(ChangeStatus, self).__init__("change_monomer_status") - self.target = target - self.status = status - self.value = value - self.rest = kwargs - - async def execute(self): - return await self.target.action(self.action)( - self.target, self.status, **{self.status: self.value, **self.rest} - ) - - -class MessageAction(ExecutiveAction): - data: Message - - def __init__(self, action: str, message: Message): - super().__init__(action) - self.data = message - - async def execute(self): - return await self.target.action(self.action)(self.data, target=self.target) - - -class MessageRevoke(MessageAction): - message_id: int - - def __init__(self, message: Message, target: int = None): - super().__init__("revoke", message) - self.message_id = target - - async def execute(self): - return await self.target.action(self.action)(self.data, target=self.message_id) - - -class MessageSend(MessageAction): - - def __init__(self, message: Optional[Message] = None): - if not message: - try: - lcs = inspect.currentframe().f_back.f_back.f_locals - for v in lcs.values(): - if isinstance(v, Message): - message = v - except AttributeError: - raise ValueError - else: - if not message: - raise ValueError - super().__init__("send_with", message) - - -class MessageSendDirectly(MessageSend): - async def execute(self): - return await self.target.action(self.action)(self.data, type=self.data.type) - - -class RequestAction(ExecutiveAction): - data: Request - msg: str - - def __init__(self, action: str, request: Request, msg: str): - super().__init__(action) - self.msg = msg - self.data = request - - async def execute(self): - return await self.target.action(self.action)(self.data, msg=self.msg) - - -class RequestAccept(RequestAction): - def __init__(self, request: Optional[Request] = None, msg: Optional[str] = ""): - if not request: - try: - lcs = inspect.currentframe().f_back.f_back.f_locals - for v in lcs.values(): - if isinstance(v, Request): - request = v - except AttributeError: - raise ValueError - else: - if not Request: - raise ValueError - super().__init__("accept", request, msg) - - -class RequestReject(RequestAction): - def __init__(self, request: Optional[Request] = None, msg: Optional[str] = ""): - if not request: - try: - lcs = inspect.currentframe().f_back.f_back.f_locals - for v in lcs.values(): - if isinstance(v, Request): - request = v - except AttributeError: - raise ValueError - else: - if not Request: - raise ValueError - super().__init__("accept", request, msg) diff --git a/old/arclet/edoves/builtin/alconna.py b/old/arclet/edoves/builtin/alconna.py deleted file mode 100644 index 75b5dcf..0000000 --- a/old/arclet/edoves/builtin/alconna.py +++ /dev/null @@ -1,22 +0,0 @@ -from arclet.alconna import Alconna, Arpamar, compile -from arclet.letoderea.entities.auxiliary import BaseAuxiliary -from arclet.letoderea.exceptions import ParsingStop -from arclet.letoderea.utils import ArgumentPackage - -from .message.chain import MessageChain - - -class AlconnaAuxiliary(BaseAuxiliary): - alconna: Alconna - - def __init__(self, alconna: Alconna): - self.analyser = compile(alconna) - super().__init__() - - @self.set_aux("before_parse", "supply", keep=True) - def supply(target_argument: ArgumentPackage) -> Arpamar: - if target_argument.annotation == MessageChain: - res = self.analyser.analyse(target_argument.value) - if not res.matched: - raise ParsingStop - return res diff --git a/old/arclet/edoves/builtin/behavior.py b/old/arclet/edoves/builtin/behavior.py deleted file mode 100644 index fa43516..0000000 --- a/old/arclet/edoves/builtin/behavior.py +++ /dev/null @@ -1,152 +0,0 @@ -from typing import Any, Optional, Union - -from arclet.edoves.main.interact.monomer import BaseMonoBehavior, Monomer -from arclet.edoves.main.protocol import AbstractProtocol - -from .medium import DictMedium, Message, Request - - -class MiddlewareBehavior(BaseMonoBehavior): - protocol: AbstractProtocol - - def activate(self): - self.protocol = self.io.protocol - - async def revoke(self, medium: Message, target_message_id: int = None): - await self.protocol.screen.push( - DictMedium().create( - self.io, {"target": target_message_id if target_message_id else medium.id}, "MessageRevoke" - ) - ) - await self.protocol.execution_handle() - - async def nudge(self, target: Union[str, Monomer, None], **rest): - resp = await self.protocol.screen.push( - DictMedium().create( - self.io, - {"target": target if isinstance(target, str) else target.metadata.identifier, "rest": rest}, - "NudgeSend", - ) - ) - await self.protocol.execution_handle() - resp_data: DictMedium = await resp.wait_response() - self.protocol.screen.edoves.logger.info( - f"{self.protocol.current_scene.protagonist.metadata.identifier}: " - f"{resp_data.type}({resp_data.content['id']}) <- Nudge" - ) - - async def send_with( - self, - medium: Message, - target: Optional[Monomer] = None, - reply: bool = False, - nudge: bool = False, - **rest, - ): - target = target or medium.purveyor - if nudge: - await self.nudge(target.metadata.identifier, **rest) - - resp = await self.protocol.screen.push( - DictMedium().create( - self.io, - { - "target": target.metadata.identifier, - "reply": reply, - "content": medium.content.to_sendable(), - "rest": rest, - }, - "MessageSend", - ) - ) - await self.protocol.execution_handle() - resp_data: DictMedium = await resp.wait_response() - self.protocol.screen.edoves.logger.info( - f"{self.protocol.current_scene.protagonist.metadata.identifier}: " - f"{resp_data.type}({resp_data.content.get('id') or target.metadata.identifier})" - f" <- {medium.content.to_text()}" - ) - return resp_data.content.get("messageId") - - async def request_accept(self, medium: Request, msg: str = None): - await self.protocol.screen.push( - DictMedium().create( - self.io, - { - "event": medium.type, - "operate": 0, - "eventId": medium.event, - "msg": msg, - "content": medium.content, - }, - "Accept", - ) - ) - await self.protocol.execution_handle() - - async def request_reject(self, medium: Request, msg: str = None): - await self.protocol.screen.push( - DictMedium().create( - self.io, - { - "event": medium.type, - "operate": 1, - "eventId": medium.event, - "msg": msg, - "content": medium.content, - }, - "Reject", - ) - ) - await self.protocol.execution_handle() - - async def relationship_remove(self, target: Union[str, Monomer], relationship: str = None): - target = await self.relationship_get(target, relationship) if isinstance(target, str) else target - await self.protocol.screen.push( - DictMedium().create( - self.io, - { - "relationship": target.prime_tag, - "target": target, - }, - "RelationshipRemove", - ) - ) - await self.protocol.execution_handle() - - async def relationship_get(self, target: str, relationship: str, **rest): - resp = await self.protocol.screen.push( - DictMedium().create( - self.io, {"relationship": relationship, "target": target, "rest": rest}, "RelationshipGet" - ) - ) - await self.protocol.execution_handle() - return await resp.wait_response() - - async def change_monomer_status(self, target: Monomer, status: str, **rest): - await self.protocol.screen.push( - DictMedium().create( - self.io, - { - "target": target, - "status": status, - "rest": rest, - }, - "ChangeMonomerStatus", - ) - ) - await self.protocol.execution_handle() - - async def change_metadata(self, meta: str, value: Any, target: Optional["Monomer"] = None, **addition): - await self.protocol.screen.push( - DictMedium().create( - self.io, - { - "target": target or self.io, - "meta": meta, - "rest": addition, - }, - "ChangeMonomerMetadata", - ) - ) - await self.protocol.execution_handle() diff --git a/old/arclet/edoves/builtin/client.py b/old/arclet/edoves/builtin/client.py deleted file mode 100644 index 1db4f7b..0000000 --- a/old/arclet/edoves/builtin/client.py +++ /dev/null @@ -1,146 +0,0 @@ -from contextlib import asynccontextmanager -import json -from typing import Any, Dict, List, Optional, Union - -from aiohttp import ClientResponse, ClientSession, ClientWebSocketResponse, FormData, WSMessage -from arclet.edoves.main.network import HTTP_METHODS, NetworkClient, NetworkConnection, NetworkResponse -from yarl import URL - - -class AiohttpResponse(NetworkResponse): - response: ClientResponse - - def __init__(self, response: ClientResponse): - self.response = response - - @property - def url(self) -> URL: - return self.response.url - - @property - def status(self) -> int: - return self.response.status - - def raise_for_status(self): - self.response.raise_for_status() - - async def read_json(self) -> dict: - return await self.response.json() - - async def read(self) -> bytes: - return await self.response.read() - - async def cookies(self) -> Dict[str, str]: - return {k: str(v) for k, v in self.response.cookies.items()} - - async def headers(self) -> Dict[str, str]: - return {k: str(v) for k, v in self.response.headers.items()} - - async def close(self): - self.response.close() - - -class AiohttpWSConnection(NetworkConnection): - - async def accept(self) -> None: - pass - - def status(self) -> int: - pass - - def raise_for_code(self): - pass - - response: ClientWebSocketResponse - - def __init__(self, response: ClientWebSocketResponse): - self.response = response - - async def send(self, data: Union[bytes, str, dict]) -> None: - if isinstance(data, str): - return await self.response.send_str(data) - if isinstance(data, bytes): - return await self.response.send_bytes(data) - if isinstance(data, dict): - return await self.response.send_str(json.dumps(data)) - - async def receive(self, timeout: Optional[float] = None) -> WSMessage: - return await self.response.receive(timeout) - - async def ping(self) -> None: - return await self.response.ping() - - async def pong(self) -> None: - return await self.response.pong() - - async def close(self, code: int = 1000, message: bytes = b"") -> None: - await self.response.close(code=code, message=message) - - -class AiohttpClient(NetworkClient): - session: ClientSession - resp_list: List[ClientWebSocketResponse] - - def __init__(self): - self.session = ClientSession() - self.resp_list = [] - - async def close(self): - for resp in self.resp_list: - await resp.close() - await self.session.close() - - @asynccontextmanager - async def ensure_network( - self, url: Union[str, URL], *, method: str = "GET", timeout: float = 10.0, **kwargs: Any - ): - resp: ClientWebSocketResponse = await self.session.ws_connect( - url, timeout=timeout, **kwargs - ).__aenter__() - self.resp_list.append(resp) - yield AiohttpWSConnection(resp) - pass - - @asynccontextmanager - async def request( - self, - method: "HTTP_METHODS", - url: Union[str, URL], - headers: Dict[str, str] = None, - data: Union[str, bytes] = None, - **kwargs: Any, - ): - async with self.session.request(method, url, **{"headers": headers, "data": data, **kwargs}) as resp: - yield AiohttpResponse(resp) - - @asynccontextmanager - async def get(self, url: Union[str, URL], headers: Dict[str, str] = None, **kwargs: Any): - async with self.session.get(url, **{"headers": headers, **kwargs}) as resp: - yield AiohttpResponse(resp) - - @asynccontextmanager - async def post( - self, - url: Union[str, URL], - data: Union[str, bytes, FormData], - headers: Dict[str, str] = None, - **kwargs: Any, - ): - async with self.session.post(url, **{"headers": headers, "data": data, **kwargs}) as resp: - yield AiohttpResponse(resp) - - @asynccontextmanager - async def put( - self, url: Union[str, URL], data: Union[str, bytes], headers: Dict[str, str] = None, **kwargs: Any - ): - pass - - @asynccontextmanager - async def delete(self, url: Union[str, URL], headers: Dict[str, str] = None, **kwargs: Any): - pass - - @asynccontextmanager - async def patch( - self, url: Union[str, URL], data: Union[str, bytes], headers: Dict[str, str] = None, **kwargs: Any - ): - pass diff --git a/old/arclet/edoves/builtin/commander.py b/old/arclet/edoves/builtin/commander.py deleted file mode 100644 index c3a25f5..0000000 --- a/old/arclet/edoves/builtin/commander.py +++ /dev/null @@ -1,127 +0,0 @@ -from typing import Callable, Dict, Optional - -from arclet.alconna import Alconna, AlconnaString, command_manager, config, output_manager, split_once -from arclet.edoves.main.interact.module import BaseModule, Component, ModuleMetaComponent -from arclet.edoves.main.typings import TProtocol -from arclet.edoves.main.utilles.security import EDOVES_DEFAULT -from arclet.letoderea.entities.subscriber import Subscriber -from arclet.letoderea.handler import await_exec_target - -from .event.message import MessageReceived -from .medium import Message - - -class CommandParser: - command: Alconna - param_reaction: Callable - - def __init__(self, alconna: Alconna, func: Callable): - self.command = alconna - self.param_reaction = Subscriber(func) - - async def exec(self, params): - await await_exec_target(self.param_reaction, MessageReceived.param_export(**params)) - - -class CommanderData(ModuleMetaComponent): - verify_code: str = EDOVES_DEFAULT - identifier = "edoves.builtin.commander" - name = "Builtin Commander Module" - description = "Based on Edoves and Arclet-Alconna" - usage = """\n@commander.command("test ")\ndef test(foo: str):\n\t...""" - command_namespace: str - max_command_length: int = 10 - - -class CommandParsers(Component): - io: "Commander" - parsers: Dict[str, CommandParser] - - def __init__(self, io: "Commander"): - super(CommandParsers, self).__init__(io) - self.parsers = {} - - def command(self, command: str, *option: str, sep: str = " "): - alc = AlconnaString(command, *option, sep=sep) - - def __wrapper(func): - cmd = CommandParser(alc, func) - self.parsers.setdefault(alc.name, cmd) - return command - - return __wrapper - - def shortcut(self, shortcut: str, command: str): - name = split_once(command, " ")[0] - cmd = self.parsers.get(name) - if cmd is None: - return - cmd.command.shortcut(shortcut, command) - - def remove_handler(self, command: str): - del self.parsers[command] - - -class Commander(BaseModule): - prefab_metadata = CommanderData - command_parsers: CommandParsers - - __slots__ = ["command_parsers"] - - def __init__(self, protocol: TProtocol, namespace: Optional[str] = None): - super().__init__(protocol) - self.metadata.command_namespace = namespace or protocol.current_scene.scene_name + "_Commander" - self.command_parsers = CommandParsers(self) - if self.local_storage.get(self.__class__): - for k, v in self.local_storage[self.__class__].items(): - self.get_component(CommandParsers).parsers.setdefault(k, v) - config.set_loop(self.protocol.screen.edoves.loop) - - @self.behavior.add_handlers(MessageReceived) - async def command_message_handler(message: Message): - async def _action(doc: str): - await message.set(doc).send() - - for cmd, psr in self.command_parsers.parsers.items(): - output_manager.set_action(_action, psr.command.name) - result = psr.command.parse(message.content) - if result.matched: - await psr.exec( - { - **result.all_matched_args, - "result": result, - "message": message, - "sender": message.purveyor, - "edoves": self.protocol.screen.edoves, - "scene": self.protocol.current_scene, - } - ) - break - - @self.command("help #显示帮助") - async def _(message: Message, page: int): - await message.set( - command_manager.all_command_help( - self.metadata.command_namespace, max_length=self.metadata.max_command_length, page=page - ) - ).send() - - def command(__commander_self__, command: str, *option: str, sep: str = " "): - alc = AlconnaString(command, *option, sep=sep).reset_namespace( - __commander_self__.metadata.command_namespace - ) - - def __wrapper(func): - cmd = CommandParser(alc, func) - try: - __commander_self__.command_parsers.parsers.setdefault(alc.headers[0], cmd) - except AttributeError: - if not __commander_self__.local_storage.get(__commander_self__.__class__): - __commander_self__.local_storage.setdefault(__commander_self__.__class__, {}) - __commander_self__.local_storage[__commander_self__.__class__].setdefault(alc.headers[0], cmd) - return command - - return __wrapper - - def get_command(self, name: str): - return self.command_parsers.parsers[name].command diff --git a/old/arclet/edoves/builtin/event/__init__.py b/old/arclet/edoves/builtin/event/__init__.py deleted file mode 100644 index af9f393..0000000 --- a/old/arclet/edoves/builtin/event/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from arclet.edoves.main.event import EdovesBasicEvent - -from ..medium import Request -from .lifecycle import StartRunning, StopRunning -from .message import MessageReceived, MessageRevoked -from .network import DockerOperate -from .notice import ( - MonomerMetadataUpdate, - MonomerStatusUpdate, - NoticeMe, - RelationshipSetup, - RelationshipSevered, - RelationshipTerminate, -) - - -class RequestReceived(EdovesBasicEvent): - medium: Request diff --git a/old/arclet/edoves/builtin/event/lifecycle.py b/old/arclet/edoves/builtin/event/lifecycle.py deleted file mode 100644 index 0136cee..0000000 --- a/old/arclet/edoves/builtin/event/lifecycle.py +++ /dev/null @@ -1,18 +0,0 @@ -from arclet.edoves.main.context import edoves_instance -from arclet.edoves.main.event import EdovesBasicEvent - - -class LifeCycle(EdovesBasicEvent): - - def get_params(self): - return self.param_export( - edoves=edoves_instance.get(), - ) - - -class StartRunning(LifeCycle): - pass - - -class StopRunning(LifeCycle): - pass diff --git a/old/arclet/edoves/builtin/event/message.py b/old/arclet/edoves/builtin/event/message.py deleted file mode 100644 index 697a6b6..0000000 --- a/old/arclet/edoves/builtin/event/message.py +++ /dev/null @@ -1,11 +0,0 @@ -from arclet.edoves.main.event import EdovesBasicEvent - -from ..medium import Message - - -class MessageReceived(EdovesBasicEvent): - medium: Message - - -class MessageRevoked(EdovesBasicEvent): - medium: Message diff --git a/old/arclet/edoves/builtin/event/network.py b/old/arclet/edoves/builtin/event/network.py deleted file mode 100644 index 41f2fc9..0000000 --- a/old/arclet/edoves/builtin/event/network.py +++ /dev/null @@ -1,11 +0,0 @@ -from arclet.edoves.main.event import EdovesBasicEvent - -from ..medium import DictMedium - - -class DockerOperate(EdovesBasicEvent): - medium: DictMedium - - -class DockerStart(EdovesBasicEvent): - medium: DictMedium diff --git a/old/arclet/edoves/builtin/event/notice.py b/old/arclet/edoves/builtin/event/notice.py deleted file mode 100644 index 82620e2..0000000 --- a/old/arclet/edoves/builtin/event/notice.py +++ /dev/null @@ -1,61 +0,0 @@ -from arclet.edoves.main.event import EdovesBasicEvent, ctx_module, edoves_instance - -from ..medium import Notice - - -class _NoticeEvent(EdovesBasicEvent): - - __field__: str - - def get_params(self): - return self.param_export( - edoves=edoves_instance.get(), - module=ctx_module.get(), - medium=self.medium, - **self.medium_vars(), - **{self.__field__: getattr(self, self.__field__, None)}, - ) - - -class NoticeMe(_NoticeEvent): - medium: Notice - - -class MonomerMetadataUpdate(_NoticeEvent): - """数据更改, 比如名字, 头衔等等""" - - action: str - medium: Notice - __field__ = "action" - - -class MonomerStatusUpdate(_NoticeEvent): - """状态更改, 比如权限、是否被禁言, 等等""" - - action: str - medium: Notice - __field__ = "action" - - -class RelationshipSetup(_NoticeEvent): - """关系建立""" - - relationship: str - medium: Notice - __field__ = "relationship" - - -class RelationshipTerminate(_NoticeEvent): - """主动解除关系""" - - relationship: str - medium: Notice - __field__ = "relationship" - - -class RelationshipSevered(_NoticeEvent): - """被动解除关系""" - - relationship: str - medium: Notice - __field__ = "relationship" diff --git a/old/arclet/edoves/builtin/filter.py b/old/arclet/edoves/builtin/filter.py deleted file mode 100644 index 8cf2a4e..0000000 --- a/old/arclet/edoves/builtin/filter.py +++ /dev/null @@ -1,49 +0,0 @@ -from typing import Any, List - -from arclet.edoves.main.utilles.event_filter import EdovesBasicEvent, EventFilter - - -class MediumTypeLimit(EventFilter): - mtype: str - - def __init__(self, type: str): - self.mtype = type - super().__init__() - - @self.set_aux("before_parse", "judge") - def judge(sf: MediumTypeLimit, event: EdovesBasicEvent) -> bool: - return any([event.medium.type == sf.mtype, event.__class__.__name__ == sf.mtype]) - - -class MonomerTagLimit(EventFilter): - tags: List[str] - - def __init__(self, *monomer_tag: str): - self.tags = list(monomer_tag) - super().__init__() - - @self.set_aux("before_parse", "judge") - def judge(sf: MonomerTagLimit, event: EdovesBasicEvent) -> bool: - if len(sf.tags) > 1: - return event.medium.purveyor.compare(*sf.tags) - return event.medium.purveyor.prime_tag == sf.tags[0] - - -class MonomerMetaLimit(EventFilter): - path: List[str] - - def __init__(self, meta_name: str, value: Any): - self.path = meta_name.split(".") - if len(self.path) == 1: - self.path.insert(0, "") - self.value = value - super().__init__() - - @self.set_aux("before_parse", "judge") - def judge(sf: MonomerMetaLimit, event: EdovesBasicEvent) -> bool: - if sf.path[0] == "": - return event.medium.purveyor.__getattribute__(sf.path[1]) == sf.value - if sf.path[0] == "metadata": - return event.medium.purveyor.metadata.__getitem__(sf.path[1]) == sf.value - if sf.path[0] == "behavior": - return event.medium.purveyor.behavior.__getitem__(sf.path[1]) == sf.value diff --git a/old/arclet/edoves/builtin/medium.py b/old/arclet/edoves/builtin/medium.py deleted file mode 100644 index 62a5a81..0000000 --- a/old/arclet/edoves/builtin/medium.py +++ /dev/null @@ -1,78 +0,0 @@ -import json as JSON -from typing import Dict, Iterable, Optional, Union - -from arclet.edoves.main.medium import BaseMedium, Monomer -from arclet.edoves.main.typings import TData - -from .message.chain import MessageChain, MessageElement - - -class DictMedium(BaseMedium): - """基础的Medium,用于存储字典数据。""" - - content: Dict[str, TData] - - def json_loads(self, json: Union[dict, str]): - if isinstance(json, str): - json = JSON.loads(json) - self.content = json - return self - - -class Message(BaseMedium): - id: str - content: MessageChain - - def __init__( - self, *elements: Union[Iterable[MessageElement], MessageElement, str], target: Monomer = None - ): - super().__init__() - self.id = "" - self.content = MessageChain.create(*elements) - self.purveyor = target - self.type = "Message" - - def set(self, *elements: Union[Iterable[MessageElement], MessageElement, str]): - """ - 设置消息内容。 - - Returns: - Message: 返回一个新的消息媒介。 - """ - new = Message().create(self.purveyor, MessageChain.create(*elements), self.type) - new.id = self.id - new.time = self.time - return new - - def __call__(self, *args, **kwargs): - return self.set(*args) - - async def send( - self, - target: Optional[Union[int, str, Monomer]] = None, - reply: bool = False, - ): - if target: - return await self.action("send_with")(self, target, reply) - return await self.action("send_with")(self, reply=reply) - - def __await__(self): - return self.send().__await__() - - -class Notice(BaseMedium): - operator: Optional[Monomer] - content: Dict[str, TData] # 子类请使用TypedDict或Literal代替str来固定meta - - __export__ = ["operator"] - - def get_data(self, key: str): - return self.content.get(key) - - -class Request(BaseMedium): - event: str - content: Dict[str, TData] - - def get_data(self, key: str): - return self.content.get(key) diff --git a/old/arclet/edoves/builtin/message/__init__.py b/old/arclet/edoves/builtin/message/__init__.py deleted file mode 100644 index 7902d73..0000000 --- a/old/arclet/edoves/builtin/message/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .chain import * -from .element import * diff --git a/old/arclet/edoves/builtin/message/chain.py b/old/arclet/edoves/builtin/message/chain.py deleted file mode 100644 index 0b9df68..0000000 --- a/old/arclet/edoves/builtin/message/chain.py +++ /dev/null @@ -1,327 +0,0 @@ -from typing import Iterable, Iterator, List, SupportsIndex, Type, TypeVar, Union - -from arclet.edoves.main.utilles import DataStructure, gen_subclass - -from .element import MessageElement - -TMElement = TypeVar("TMElement", bound=MessageElement) - - -class MessageChain(DataStructure): - """ - 即 "消息链", 用于承载整个消息内容的数据结构, 包含有一有序列表, 包含有继承了 MessageElement 的类实例. - """ - - __root__: List[TMElement] - - @staticmethod - def search_element(name: str): - for i in gen_subclass(MessageElement): - if i.__name__ == name: - return i - - @staticmethod - def build_chain(obj: List[Union[dict, TMElement]]): - elements = [] - for i in obj: - if isinstance(i, MessageElement): - elements.append(i) - elif isinstance(i, dict) and "type" in i: - elements.append(MessageChain.search_element(i["type"]).parse_obj(i)) - return elements - - @classmethod - def parse_obj(cls: Type["MessageChain"], obj: List[Union[dict, TMElement]]) -> "MessageChain": - return cls(__root__=cls.build_chain(obj)) # 默认是不可变型 - - def __init__(self, __root__: Iterable[TMElement]): - super().__init__(__root__=self.build_chain(list(__root__))) - - @classmethod - def create(cls, *elements: Union[Iterable[TMElement], TMElement, str]) -> "MessageChain": - element_list = [] - for ele in elements: - if isinstance(ele, MessageElement): - element_list.append(ele) - elif isinstance(ele, str): - from .element import Text - - element_list.append(Text(ele)) - else: - element_list.extend(list(ele)) - return cls(__root__=element_list) - - def to_text(self) -> str: - """获取以字符串形式表示的消息链, 且趋于通常你见到的样子. - - Returns: - str: 以字符串形式表示的消息链 - """ - return "".join(i.to_text() for i in self.__root__) - - def to_serialization(self) -> str: - """获取可序列化的字符串形式表示的消息链, 会存储所有的信息. - - Returns: - str: 序列化的字符串形式的消息链 - """ - return "__root__: " + "".join( - i.to_serialization() for i in self.replace_text("[", "[_").replace_text("]", "_]").__root__ - ) - - @staticmethod - def from_text(text: str) -> "MessageChain": - from .element import Text - - return MessageChain([Text(text)]) - - def only_text(self) -> str: - """获取消息链中的纯文字部分""" - return "".join(i.to_text() if i else "" for i in self.findall("Plain")) - - def is_instance(self, element_type: Union[str, Type[TMElement]]) -> bool: - if isinstance(element_type, str): - element_type = MessageChain.search_element(element_type) - for i, v in enumerate(self.__root__): - if v.__class__.__name__ in ("Source", "Quote"): - continue - if type(v) is not element_type: - return False - return True - - def findall(self, element_type: Union[str, Type[TMElement]]) -> List[TMElement]: - """返回消息链内可能的所有指定元素""" - if isinstance(element_type, str): - element_type = MessageChain.search_element(element_type) - return [i for i in self.__root__ if type(i) is element_type] - - def find(self, element_type: Union[str, Type[TMElement]], index: int = 0) -> Union[bool, TMElement]: - """ - 当消息链内有指定元素时返回该元素 - 无则返回False - - Args: - element_type : 指定的元素类型 - index: 位置索引, 默认为0 - """ - ele = self.findall(element_type) - return False if not ele else ele[index] - - def has(self, element_type: Union[str, Type[TMElement]]) -> bool: - """ - 当消息链内有指定元素时返回True - 无则返回False - """ - ele = self.findall(element_type) - return False if not ele else True - - def pop(self, index: int) -> TMElement: - return self.__root__.pop(index) - - def index(self, element_type: Union[str, Type[TMElement]]) -> int: - ele = self.find(element_type) - if ele: - return self.__root__.index(ele) - else: - raise ValueError(f"{element_type} is not in this MessageChain") - - def replace( - self, element_type: Union[str, Type[TMElement]], new_element: TMElement, counts: int = None - ) -> "MessageChain": - """替换消息链中的所有指定的消息元素类型为新的消息元素实例;不改变消息链本身 - - Args: - element_type : 要替换的元素的类型 - new_element: 新的消息元素实例 - counts: 替换的次数,不填写时默认为替换全部符合的元素 - Returns: - MessageChain: 新的消息链 - """ - if isinstance(element_type, str): - element_type = MessageChain.search_element(element_type) - new_message = MessageChain(self.__root__) - if not counts: - for i, v in enumerate(self.__root__): - if type(v) is element_type: - new_message.__root__[i] = new_element - elif counts > 0: - for i, v in enumerate(self.__root__): - if type(v) is element_type: - counts -= 1 - new_message.__root__[i] = new_element - if counts == 0: - break - return new_message - - def replace_text(self, old_text: str, new_text: str, counts: int = -1) -> "MessageChain": - """替换消息链中可能含有的文本消息中的文本为指定文本;不改变消息链本身 - - Args: - old_text : 要替换的文本内容 - new_text: 新的文本内容 - counts: 替换的次数,不填写时默认为替换全部符合的元素 - Returns: - MessageChain: 新的消息链 - """ - new_message = MessageChain(self.__root__) - from .element import Text - - for ele in new_message: - if isinstance(ele, Text): - ele.text = ele.text.replace(old_text, new_text, counts) - return new_message - - def replace_type( - self, - element_type: Union[str, Type[TMElement]], - new_element_type: Union[str, Type[TMElement]], - counts: int = None, - ): - """替换消息链中的所有指定的消息元素类型为新的消息元素类型, 不改变元素内容;不改变消息链本身 - - Args: - element_type : 要替换的元素的类型 - new_element_type: 新的消息元素类型 - counts: 替换的次数,不填写时默认为替换全部符合的元素 - Returns: - MessageChain: 新的消息链 - """ - if isinstance(element_type, str): - element_type = MessageChain.search_element(element_type) - if isinstance(new_element_type, str): - new_element_type = MessageChain.search_element(new_element_type) - new_message = MessageChain(self.__root__) - if not counts: - for i, v in enumerate(self.__root__): - if type(v) is element_type: - new_message.__root__[i].type = new_element_type.__name__ - elif counts > 0: - for i, v in enumerate(self.__root__): - if type(v) is element_type: - counts -= 1 - new_message.__root__[i].type = new_element_type.__name__ - if counts == 0: - break - return new_message - - def remove(self, element_type: Union[str, Type[TMElement]], counts: int = None): - """删除消息链中的所有指定的消息元素类型 - - Args: - element_type : 要删除的元素的类型 - counts: 删除的次数,不填写时默认为删除全部符合的元素 - Returns: - 操作完成后的消息链本身 - """ - if isinstance(element_type, str): - element_type = MessageChain.search_element(element_type) - if not counts: - self.__root__ = [i for i in self.__root__ if type(i) is not element_type] - elif counts > 0: - i = 0 - while counts: - if type(self.__root__[i]) is element_type: - try: - self.__root__.remove(self.__root__[i]) - counts -= 1 - i -= 1 - except ValueError: - break - i += 1 - return self - - def only_save(self, element_type: Union[str, Type[TMElement]]): - """删除消息链中的所有非指定的消息元素类型 - - Args: - element_type : 要保留的元素的类型 - Returns: - 操作完成后的消息链本身 - """ - if isinstance(element_type, str): - element_type = MessageChain.search_element(element_type) - self.__root__ = [i for i in self.__root__ if type(i) is element_type] - return self - - def insert(self, index: int, element: TMElement): - """在指定位置插入一个消息元素实例 - Args: - index: 插入的位置 - element: 需要插入的消息元素 - Returns: - 操作完成后的消息链本身 - """ - self.__root__.insert(index, element) - return self - - def append(self, element: TMElement): - """在消息链尾部增加一个消息元素实例 - - Args: - element: 需要插入的消息元素 - Returns: - 操作完成后的消息链本身 - """ - self.__root__.append(element) - return self - - def extend(self, *elements: Union[TMElement, List[TMElement]]): - element_list = [] - for ele in elements: - if isinstance(ele, MessageElement): - element_list.append(ele) - else: - element_list.extend(ele) - self.__root__ += element_list - return self - - def copy_self(self) -> "MessageChain": - return MessageChain(self.__root__) - - def __add__(self, other) -> "MessageChain": - if isinstance(other, MessageElement): - self.__root__.append(other) - return self - elif isinstance(other, MessageChain): - self.__root__.extend(i for i in other.__root__ if i.type != "Source") - return self - elif isinstance(other, List): - self.__root__ += other - return self - - def __str__(self): - return self.to_text() - - def __repr__(self) -> str: - return rf"MessageChain({repr(self.__root__)})" - - def __iter__(self) -> Iterator[TMElement]: - yield from self.__root__ - - def __getitem__(self, index: SupportsIndex) -> TMElement: - return self.__root__[index] - - def __len__(self) -> int: - return len(self.__root__) - - def __contains__(self, item: Union[Type[TMElement], str]) -> bool: - """ - 是否包含特定元素类型/字符串 - """ - if isinstance(item, str): - return item in self.find("Text").to_text() - else: - return self.has(item) - - def to_sendable(self): - return self.remove("Source").remove("Quote").remove("File") - - def startswith(self, string: str) -> bool: - if not self.__root__: - return False - return self.to_text().startswith(string) - - def endswith(self, string: str) -> bool: - if not self.__root__: - return False - return self.to_text().endswith(string) diff --git a/old/arclet/edoves/builtin/message/element.py b/old/arclet/edoves/builtin/message/element.py deleted file mode 100644 index 7ad3fcf..0000000 --- a/old/arclet/edoves/builtin/message/element.py +++ /dev/null @@ -1,142 +0,0 @@ -from abc import ABC -from base64 import b64decode, b64encode -import json -from pathlib import Path -from typing import Optional, Union - -import aiohttp -from arclet.edoves.main.utilles import DataStructure -from pydantic import Field - - -class MessageElement(ABC, DataStructure): - type: str - - def __hash__(self): - return hash((type(self),) + tuple(self.__dict__.values())) - - def to_serialization(self) -> str: - return f"[{self.type}:{json.dumps(self.dict(exclude={'type'}))}]".replace("\n", "\\n").replace( - "\t", "\\t" - ) - - def to_text(self) -> str: - return "" - - -class MediaElement(MessageElement): - id: Optional[str] - url: Optional[str] = None - base64: Optional[str] = None - - def __init__( - self, - id: Optional[str] = None, - url: Optional[str] = None, - *, - path: Optional[Union[Path, str]] = None, - base64: Optional[str] = None, - data_bytes: Optional[bytes] = None, - **kwargs, - ) -> None: - data = {} - - for key, value in kwargs.items(): - if key.lower().endswith("id"): - data["id"] = value - - data["id"] = data["id"] if "id" in data else id - data["url"] = url - data["base64"] = base64 - super().__init__(**data, **kwargs) - self.to_sendable(path, data_bytes) - - async def get_bytes(self) -> bytes: - if self.url and not self.base64: - async with aiohttp.request("GET", self.url) as response: - if response.status != 200: - raise ConnectionError(response.status, await response.text()) - data = await response.content.readexactly(response.content_length) - self.base64 = str(b64encode(data), encoding="utf-8") - return data - if self.base64: - return b64decode(self.base64) - - def to_sendable(self, path: Optional[Union[Path, str]] = None, data_bytes: Optional[bytes] = None): - if sum([bool(self.url), bool(path), bool(self.base64)]) > 1: - raise ValueError("Too many binary initializers!") - if path: - if isinstance(path, str): - path = Path(path) - if not path.exists(): - raise FileNotFoundError(f"{path} is not exist!") - self.base64 = str(b64encode(path.read_bytes()), encoding="utf-8") - elif data_bytes: - self.base64 = str(b64encode(data_bytes), encoding="utf-8") - - -class Text(MessageElement): - type: str = "Text" - text: str - - def __init__(self, text: str, **kwargs) -> None: - """实例化一个 Plain 消息元素, 用于承载消息中的文字. - - Args: - text (str): 元素所包含的文字 - """ - super().__init__(text=text, **kwargs) - - def to_text(self): - return self.text.replace("\n", "\\n").replace("\t", "\\t") - - def to_serialization(self) -> str: - return self.text.replace("\n", "\\n").replace("\t", "\\t") - - -class At(MessageElement): - """该消息元素用于承载消息中用于提醒/呼唤特定用户的部分.""" - - type: str = "At" - target: int - display: Optional[str] = None - - def __init__(self, target: int, **kwargs) -> None: - """实例化一个 At 消息元素, 用于承载消息中用于提醒/呼唤特定用户的部分. - - Args: - target (int): 需要提醒/呼唤的特定用户的 QQ 号(或者说 id.) - """ - super().__init__(target=target, **kwargs) - - def to_text(self) -> str: - return f"@{str(self.display)}" if self.display else f"@{self.target}" - - -class AtAll(MessageElement): - """该消息元素用于群组中的管理员提醒群组中的所有成员""" - - type: str = "AtAll" - - def to_text(self) -> str: - return "@全体成员" - - -class Voice(MediaElement): - type = "Voice" - id: Optional[str] = Field(None, alias="voiceId") - - length: Optional[int] - - def to_text(self) -> str: - return "[语音]" - - -class Image(MediaElement): - """该消息元素用于承载消息中所附带的图片.""" - - type = "Image" - id: Optional[str] = Field(None, alias="imageId") - - def to_text(self) -> str: - return "[图片]" diff --git a/old/arclet/edoves/builtin/timer/__init__.py b/old/arclet/edoves/builtin/timer/__init__.py deleted file mode 100644 index dfb1e46..0000000 --- a/old/arclet/edoves/builtin/timer/__init__.py +++ /dev/null @@ -1,127 +0,0 @@ -from datetime import datetime, timedelta -from typing import Callable, List - -from arclet.edoves.main.interact.module import BaseModule, Component, ModuleBehavior, ModuleMetaComponent -from arclet.edoves.main.typings import TProtocol -from arclet.edoves.main.utilles.security import EDOVES_DEFAULT -from arclet.letoderea.utils import run_always_await - -TIMER = Callable[[], timedelta] - - -class TimerModuleData(ModuleMetaComponent): - verify_code = EDOVES_DEFAULT - identifier = "edoves.builtin.timer" - name = "Timer Module" - description = "A module for time scheduler" - - -class TimerMounter: - route: bool - interval: TIMER - next_time: datetime - scheduler_task: Callable - - def __init__(self, scheduler_task: Callable, interval: TIMER, route: bool = False): - self.scheduler_task = scheduler_task - self.interval = interval - self.route = route - self.offset = timedelta(seconds=0) - if not route: - self.next_time = datetime.now() + self.interval() - else: - self.next_time = self.route_time() - - def route_time(self): - now = datetime.now() - year = now.year - month = now.month - day = now.day - hour = now.hour - minute = now.minute - second = now.second - _interval = self.interval() - if _interval.total_seconds() < 60: - if _interval.seconds - second < 0: - minute += 1 - second = _interval.seconds - elif 0 < _interval.total_seconds() < 3600: - if _interval.seconds / 60 - minute < 0: - hour += 1 - minute = _interval.seconds // 60 - second = 0 - elif 3600 < _interval.total_seconds() < 86400: - if _interval.seconds / 3600 - hour < 0: - day += 1 - hour = _interval.seconds // 3600 - minute = 0 - second = 0 - - elif 0 < _interval.days < 32: - if _interval.days - day < 0: - month += 1 - day = _interval.days - hour = 0 - minute = 0 - second = 0 - return datetime( - year=year, - month=month, - day=day, - hour=hour, - minute=minute, - second=second, - microsecond=now.microsecond, - ) - - async def update(self): - now = datetime.now() - self.offset = timedelta(seconds=now.microsecond / 1000000) - if self.next_time < now - self.offset: - if self.route: - self.next_time = self.route_time() - else: - self.next_time = now + self.interval() - self.offset - await run_always_await(self.scheduler_task) - - -class TimerHandler(Component): - io: "Timer" - mounters: List[TimerMounter] - - def __init__(self, io: "Timer"): - super().__init__(io) - self.mounters = [] - - def add_timer(self, scheduler_task: Callable, interval: TIMER, route: bool = False): - self.mounters.append(TimerMounter(scheduler_task, interval, route)) - - -class TimerBehavior(ModuleBehavior): - - async def update(self): - timer_handler = self.get_component(TimerHandler) - for mounter in timer_handler.mounters: - await mounter.update() - - -class Timer(BaseModule): - prefab_metadata = TimerModuleData - prefab_behavior = TimerBehavior - timer_handler: TimerHandler - - __slots__ = ["timer_handler"] - - def __init__(self, protocol: TProtocol): - super().__init__(protocol) - self.timer_handler = TimerHandler(self) - - def schedule( - __timer_self__, - interval: TIMER, - route: bool = False, - ): - def __wrapper(func): - __timer_self__.timer_handler.add_timer(func, interval, route) - - return __wrapper diff --git a/old/arclet/edoves/builtin/timer/timers.py b/old/arclet/edoves/builtin/timer/timers.py deleted file mode 100644 index 47e1d57..0000000 --- a/old/arclet/edoves/builtin/timer/timers.py +++ /dev/null @@ -1,134 +0,0 @@ -from datetime import datetime, timedelta -import time - - -def every_second(): - """每秒执行一次""" - return lambda: timedelta(seconds=1) - - -def every_custom_seconds(seconds: int): - """每 seconds 秒执行一次 - - Args: - seconds (int): 距离下一次执行的时间间隔, 单位为秒 - """ - if seconds > 59 or seconds < 1: - raise ValueError - return lambda: timedelta(seconds=seconds) - - -def every_minute(): - """每分钟执行一次""" - return lambda: timedelta(minutes=1) - - -def every_custom_minutes(minutes: int): - """每 minutes 分钟执行一次 - - Args: - minutes (int): 距离下一次执行的时间间隔, 单位为分 - """ - if minutes > 59 or minutes < 1: - raise ValueError - return lambda: timedelta(minutes=minutes) - - -def every_hour(): - """每小时执行一次""" - return lambda: timedelta(hours=1) - - -def every_custom_hours(hours: int): - """每 hours 小时执行一次 - - Args: - hours (int): 距离下一次执行的时间间隔, 单位为小时 - """ - if hours > 23 or hours < 1: - raise ValueError - return lambda: timedelta(hours=hours) - - -def every_week(): - """每隔一周执行一次""" - return lambda: timedelta(weeks=1) - - -def every_custom_weeks(weeks: int): - """每 weeks 周执行一次 - - Args: - weeks (int): 距离下一次执行的时间间隔, 单位为周 - """ - if weeks > 52 or weeks < 1: - raise ValueError - return lambda: timedelta(weeks=weeks) - - -def every_day(): - """每隔一天执行一次""" - return lambda: timedelta(days=1) - - -def every_custom_days(days: int): - """每 days 天执行一次 - - Args: - days (int): 距离下一次执行的时间间隔, 单位为天 - """ - if days > 31 or days < 1: - raise ValueError - return lambda: timedelta(days=days) - - -def set_special_day(month: int, day: int, hour: int = 0, minute: int = 0, second: int = 0): - interval = {"month": month, "day": day, "hour": hour, "minute": minute, "second": second} - now = datetime.now() - year = now.year - if interval["month"] < now.month: - year += 1 - return lambda: datetime(year=year, **interval) - now - - -if __name__ == "__main__": - - def route_time(timer): - import time - - now = datetime.now() - year = now.year - month = now.month - day = now.day - hour = now.hour - minute = now.minute - second = now.second - _interval = timer() - if _interval.seconds < 60: - if _interval.seconds - second < 0: - minute += 1 - second = _interval.seconds + 60 - elif 0 < _interval.total_seconds() < 3600: - if _interval.seconds / 60 - minute < 0: - hour += 1 - minute = _interval.seconds // 60 - second = 0 - elif 3600 < _interval.total_seconds() < 86400: - if _interval.seconds / 3600 - hour < 0: - day += 1 - hour = _interval.seconds // 3600 - minute = 0 - second = 0 - - elif 0 < _interval.days < 32: - if _interval.days - day < 0: - month += 1 - day = _interval.days - hour = 0 - minute = 0 - second = 0 - return datetime(year=year, month=month, day=day, hour=hour, minute=minute, second=second) - - while True: - time.sleep(0.5) - print(route_time(every_second())) diff --git a/old/arclet/edoves/mah/__init__.py b/old/arclet/edoves/mah/__init__.py deleted file mode 100644 index 61cd769..0000000 --- a/old/arclet/edoves/mah/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Type - -from arclet.edoves.main.config import TemplateConfig -from arclet.edoves.main.utilles.security import MIRAI_API_HTTP_DEFAULT - -from .parsers import * -from .protocol import MAHProtocol -from .server_docker import MAHServerDocker - -VERIFY_CODE = MIRAI_API_HTTP_DEFAULT - - -class MAHConfig(TemplateConfig): - protocol: Type[MAHProtocol] = MAHProtocol - docker_type: Type[MAHServerDocker] = MAHServerDocker - modules_base_path = "./edoves_modules/mah" diff --git a/old/arclet/edoves/mah/actions.py b/old/arclet/edoves/mah/actions.py deleted file mode 100644 index 10d94f5..0000000 --- a/old/arclet/edoves/mah/actions.py +++ /dev/null @@ -1,91 +0,0 @@ -from typing import cast - -from arclet.edoves.builtin.actions import ChangeStatus, ExecutiveAction, GetMonomer, MessageSend, Union -from arclet.edoves.main.context import ctx_event - -from .monomers import MahEntity, MEMetadata - - -class MuteMember(ChangeStatus): - def __init__(self, target: MahEntity, mute_time: int = None): - if target.prime_tag != "Member": - raise ValueError - super().__init__(target, "mute", True, mute_time=mute_time) - - -class UnmuteMember(ChangeStatus): - def __init__(self, target: MahEntity, mute_time: int = None): - if target.prime_tag != "Member": - raise ValueError - super().__init__(target, "mute", False, mute_time=mute_time) - - -class GroupMuteAll(ChangeStatus): - def __init__(self, target: MahEntity, mute_time: int = None): - if target.prime_tag != "Group": - raise ValueError - super().__init__(target, "mute", True, mute_time=mute_time) - - -class GroupUnmuteAll(ChangeStatus): - def __init__(self, target: MahEntity, mute_time: int = None): - if target.prime_tag != "Group": - raise ValueError - super().__init__(target, "mute", False, mute_time=mute_time) - - -class GetFriend(GetMonomer): - target: MahEntity - - def __init__(self, target: Union[int, str], detail: bool = False): - super().__init__(target, "Friend", detail=detail) - - -class GetMember(GetMonomer): - target: MahEntity - - def __init__(self, target: Union[int, str]): - super().__init__(target, "Member") - self.rest = {"group": self.target.current_group} - - async def execute(self) -> MahEntity: - member = await super().execute() - gid = self.target.get_component(MEMetadata).group_id - if gid: - member.metadata.group_id = gid - return cast(MahEntity, member) - - -class Reply(MessageSend): - async def execute(self): - try: - mid = self.data.id if self.data.id else ctx_event.get().medium.mid - except AttributeError: - raise ValueError("No message to reply to.") - return await self.target.action(self.action)(self.data, target=self.target, reply=True, quote=mid) - - -class NudgeWith(MessageSend): - async def execute(self): - return await self.target.action(self.action)(self.data, target=self.target, nudge=True) - - -class Nudge(ExecutiveAction): - - def __init__(self): - super().__init__("nudge") - - async def execute(self): - return await self.target.action(self.action)(self.target.metadata.identifier) - - -send_message = MessageSend -send_nudge = Nudge -nudge_with = NudgeWith -reply = Reply -get_friend = GetFriend -get_member = GetMember -mute_member = MuteMember -unmute_member = UnmuteMember -mute_all = GroupMuteAll -unmute_all = GroupUnmuteAll diff --git a/old/arclet/edoves/mah/chain.py b/old/arclet/edoves/mah/chain.py deleted file mode 100644 index 77384fc..0000000 --- a/old/arclet/edoves/mah/chain.py +++ /dev/null @@ -1,6 +0,0 @@ -from arclet.edoves.builtin.message.chain import MessageChain - -from .elements import * - -Quote.update_forward_refs(MessageChain=MessageChain) -ForwardNode.update_forward_refs(MessageChain=MessageChain) diff --git a/old/arclet/edoves/mah/elements.py b/old/arclet/edoves/mah/elements.py deleted file mode 100644 index 4cc385f..0000000 --- a/old/arclet/edoves/mah/elements.py +++ /dev/null @@ -1,209 +0,0 @@ -from datetime import datetime -from enum import Enum -import json as JSON -from typing import TYPE_CHECKING, List, Optional, Union -from xml import sax - -from arclet.edoves.builtin.message import At, AtAll, DataStructure, Image, MessageElement, Text -from pydantic import Field, validator - -if TYPE_CHECKING: - from arclet.edoves.builtin.message import MessageChain - - -class Plain(Text): - type: str = "Plain" - - -class Quote(MessageElement): - """表示消息中回复其他消息/用户的部分, 通常包含一个完整的消息链(`origin` 属性)""" - - type: str = "Quote" - id: int - groupId: int - senderId: int - targetId: int - origin: "MessageChain" - - @validator("origin", pre=True, allow_reuse=True) - def _(cls, v): - from .chain import MessageChain - - return MessageChain(v) - - def to_serialization(self) -> str: - return f'[mirai:Quote:{{"id":{self.id},"origin":{self.origin}}}]' - - -class Source(MessageElement): - """表示消息在一个特定聊天区域内的唯一标识""" - - type: str = "Source" - id: int - time: datetime - - -class Face(MessageElement): - """表示消息中所附带的表情, 这些表情大多都是聊天工具内置的.""" - - type: str = "Face" - faceId: int - name: Optional[str] = None - - def to_text(self) -> str: - return f"[表情:{self.name}]" if self.name else f"[表情:{self.faceId}]" - - -class Xml(MessageElement): - type = "Xml" - xml: str - - def __init__(self, xml, *_, **__) -> None: - super().__init__(xml=xml) - - def to_text(self) -> str: - return "[XML消息]" - - def get_xml(self): - return sax.parseString(self.xml, sax.handler.ContentHandler()) - - -class Json(MessageElement): - type = "Json" - Json: str = Field(..., alias="json") - - def __init__(self, json: Union[dict, str], **kwargs) -> None: - if isinstance(json, dict): - json = JSON.dumps(json) - super().__init__(json=json, **kwargs) - - def to_text(self) -> str: - return "[JSON消息]" - - def get_json(self) -> dict: - return JSON.loads(self.Json) - - -class App(MessageElement): - type = "App" - content: str - - def to_text(self) -> str: - return f"[APP消息:{JSON.loads(self.content)['prompt']}]" - - def get_meta_content(self) -> dict: - return JSON.loads(self.content)["meta"] - - -class PokeMethods(Enum): - ChuoYiChuo = "ChuoYiChuo" - BiXin = "BiXin" - DianZan = "DianZan" - XinSui = "XinSui" - LiuLiuLiu = "LiuLiuLiu" - FangDaZhao = "FangDaZhao" - BaoBeiQiu = "BaoBeiQiu" - Rose = "Rose" - ZhaoHuanShu = "ZhaoHuanShu" - RangNiPi = "RangNiPi" - JeiYin = "JeiYin" - ShouLei = "ShouLei" - GouYin = "GouYin" - ZhuaYiXia = "ZhuaYiXia" - SuiPing = "SuiPing" - QiaoMen = "QiaoMen" - - -class Poke(MessageElement): - type = "Poke" - name: PokeMethods - - def to_text(self) -> str: - return f"[戳一戳:{self.name}]" - - -class Dice(MessageElement): - type = "Dice" - value: int - - def to_text(self) -> str: - return f"[骰子:{self.value}]" - - -class File(MessageElement): - type = "File" - file_id: str = Field(..., alias="id") - name: str - size: int - - def to_text(self) -> str: - return f"[文件:{self.name}]" - - -class ImageType(Enum): - Friend = "Friend" - Group = "Group" - Temp = "Temp" - Unknown = "Unknown" - - -class FlashImage(Image): - """该消息元素用于承载消息中所附带的图片.""" - - type = "FlashImage" - - def to_text(self) -> str: - return "[闪照]" - - def to_image(self) -> "Image": - """将 FlashImage 转换为 Image - - Returns: - Image: 转换后的 Image - """ - return Image.parse_obj({**self.dict(), "type": "Image"}) - - -class MusicShare(MessageElement): - type = "MusicShare" - kind: Optional[str] - title: Optional[str] - summary: Optional[str] - jumpUrl: Optional[str] - pictureUrl: Optional[str] - musicUrl: Optional[str] - brief: Optional[str] - - def to_text(self) -> str: - return f"[音乐分享:{self.title}]" - - -class ForwardNode(DataStructure): - """表示合并转发中的一个节点""" - - senderId: int - time: int - senderName: str - messageChain: Optional["MessageChain"] - messageId: Optional[int] - - -class Forward(MessageElement): - """ - 指示合并转发信息 - - nodeList (List[ForwardNode]): 转发的消息节点 - """ - - type = "Forward" - nodeList: List[ForwardNode] - - def to_text(self) -> str: - return f"[合并转发:共{len(self.nodeList)}条]" - - -def _update_forward_refs(): - from arclet.edoves.builtin.message import MessageChain - - Quote.update_forward_refs(MessageChain=MessageChain) - ForwardNode.update_forward_refs(MessageChain=MessageChain) diff --git a/old/arclet/edoves/mah/filter.py b/old/arclet/edoves/mah/filter.py deleted file mode 100644 index 51f8f66..0000000 --- a/old/arclet/edoves/mah/filter.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import List, Union - -from arclet.edoves.builtin.filter import EdovesBasicEvent, MonomerTagLimit - -from .monomers import MEMetadata - - -class FriendLimit(MonomerTagLimit): - ids: List[str] - - def __init__(self, *identifier: Union[str, int]): - super(FriendLimit, self).__init__("Friend") - self.ids = [str(i) for i in identifier] - - @self.set_aux("before_parse", "judge") - def judge(sf: FriendLimit, event: EdovesBasicEvent) -> bool: - if sf.ids: - return ( - event.medium.purveyor.prime_tag == sf.tags[0] - and event.medium.purveyor.metadata.identifier in sf.ids - ) - return event.medium.purveyor.prime_tag == sf.tags[0] - - -class GroupLimit(MonomerTagLimit): - ids: List[str] - - def __init__(self, *identifier: Union[str, int]): - super(GroupLimit, self).__init__("Member") - self.ids = [str(i) for i in identifier] - - @self.set_aux("before_parse", "judge") - def judge(sf: GroupLimit, event: EdovesBasicEvent) -> bool: - if sf.ids: - return ( - event.medium.purveyor.prime_tag == sf.tags[0] - and event.medium.purveyor.get_component(MEMetadata).group_id in sf.ids - ) - return event.medium.purveyor.prime_tag == sf.tags[0] diff --git a/old/arclet/edoves/mah/module/__init__.py b/old/arclet/edoves/mah/module/__init__.py deleted file mode 100644 index 5f42f05..0000000 --- a/old/arclet/edoves/mah/module/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .message import MessageModule diff --git a/old/arclet/edoves/mah/module/message.py b/old/arclet/edoves/mah/module/message.py deleted file mode 100644 index 0db71cc..0000000 --- a/old/arclet/edoves/mah/module/message.py +++ /dev/null @@ -1,13 +0,0 @@ -from arclet.edoves.main.interact.module import BaseModule, ModuleMetaComponent - -from .. import VERIFY_CODE - - -class MessageModuleData(ModuleMetaComponent): - verify_code = VERIFY_CODE - identifier = "edoves.mirai-api-http.test_message_module" - name = "BuiltinMessageModule" - - -class MessageModule(BaseModule): - prefab_metadata = MessageModuleData diff --git a/old/arclet/edoves/mah/monomers.py b/old/arclet/edoves/mah/monomers.py deleted file mode 100644 index 498e8f3..0000000 --- a/old/arclet/edoves/mah/monomers.py +++ /dev/null @@ -1,74 +0,0 @@ -from enum import Enum -from typing import TYPE_CHECKING, Literal, Optional - -from arclet.edoves.builtin.behavior import MiddlewareBehavior -from arclet.edoves.builtin.medium import Message -from arclet.edoves.main.interact.monomer import Monomer, MonoMetaComponent - -if TYPE_CHECKING: - from .protocol import MAHProtocol - - -class Permission(str, Enum): - """描述群成员在群组中的权限""" - - UNKNOWN = "NONE" # 未知或未给 - Member = "MEMBER" # 普通成员 - Administrator = "ADMINISTRATOR" # 管理员 - Owner = "OWNER" # 群主 - - -class Equipment(str, Enum): - """客户端的设备名称信息""" - - Mobile = "MOBILE" # 移动端E - Windows = "WINDOWS" # win电脑 - MacOS = "MACOS" # mac电脑 - - -class MEMetadata(MonoMetaComponent): - permission: Permission - group_id: Optional[str] - - specialTitle: Optional[str] - joinTimestamp: Optional[int] - lastSpeakTimestamp: Optional[int] - mutetimeRemaining: Optional[int] - - __limit__ = ["group_id", "permission"] - - -class MahEntity(Monomer): - prefab_metadata = MEMetadata - prefab_behavior = MiddlewareBehavior - metadata: MEMetadata - protocol: "MAHProtocol" - - def __init__( - self, - protocol: "MAHProtocol", - nickname: str, - identifier: Optional[str] = None, - remark: Optional[str] = None, - ): - super().__init__(protocol, nickname, identifier, remark) - - @property - def current_group(self): - return self.get_parent(self.metadata.group_id) if hasattr(self.metadata, "group_id") else None - - async def avatar(self, size: Literal[640, 140] = 640, get: bool = False): - if get: - async with self.protocol.docker.metadata.client.session.get( - f"https://q1.qlogo.cn/g?b=qq&nk={self.identifier}&s={size}" - ) as resp: - return await resp.read() - return f"https://q4.qlogo.cn/g?b=qq&nk={self.metadata.identifier}&s={size}" - - async def reply(self, *args): - msg = Message(*args, target=self) - msg.type = self.prime_tag + "Message" - return await self.get_component(MiddlewareBehavior).send_with( - msg, - reply=True, - ) diff --git a/old/arclet/edoves/mah/parsers/__init__.py b/old/arclet/edoves/mah/parsers/__init__.py deleted file mode 100644 index ab91af8..0000000 --- a/old/arclet/edoves/mah/parsers/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .message import MessageEventParser, NudgeEventParser -from .notice import RelationshipOperateParser -from .notice.bot import BotBaseEventParser, BotStatusUpdateEventParser -from .notice.friend import FriendEventParser -from .notice.group import GroupMetadataUpdateParser, GroupStatusUpdateParser -from .notice.member import MemberMetadataUpdateParser, MemberStatusUpdateParser -from .request import RequestEventParser diff --git a/old/arclet/edoves/mah/parsers/message.py b/old/arclet/edoves/mah/parsers/message.py deleted file mode 100644 index 3bfbc1d..0000000 --- a/old/arclet/edoves/mah/parsers/message.py +++ /dev/null @@ -1,242 +0,0 @@ -from typing import Dict, cast - -from arclet.edoves.builtin.medium import DictMedium, Message, Notice -from arclet.edoves.main.interact.parser import BaseDataParser, ParserBehavior, ParserMetadata - -from ..chain import MessageChain -from ..monomers import MEMetadata -from ..protocol import MAHProtocol - - -class NudgeOperateMeta(ParserMetadata): - parser_targets = "NudgeSend", "NudgeEvent" - - -class MessageActionMeta(ParserMetadata): - parser_targets = ( - "MessageSend", - "MessageGet", - "MessageRevoke", - "FriendMessage", - "GroupMessage", - "TempMessage", - "StrangerMessage", - "GroupRecallEvent", - "FriendRecallEvent", - ) - - -class NudgeParserBehavior(ParserBehavior): - async def from_docker(self, protocol: MAHProtocol, data: DictMedium): - operator_id = str(data.content.pop("fromId")) - target_id = str(data.content.pop("target")) - operator = protocol.current_scene.monomer_map.get(protocol.encode_unique_identifier(operator_id)) - subject = data.content.pop("subject") - if subject["kind"] == "Group": - if not operator or not getattr(operator.metadata, "group_id", None): - resp = await protocol.screen.push( - DictMedium().create( - protocol.current_scene.protagonist, - { - "relationship": "Member", - "target": operator_id, - "rest": {"group": subject["id"]}, - }, - "RelationshipGet", - ) - ) - await self.to_docker(protocol, await protocol.screen.get()) - operator = await resp.wait_response() - else: - operator.set_prime_tag("Member") - elif subject["kind"] == "Friend": - if not operator: - resp = await protocol.screen.push( - DictMedium().create( - protocol.current_scene.protagonist, - { - "relationship": "Friend", - "target": operator_id, - "rest": {"detail": True}, - }, - "RelationshipGet", - ) - ) - await self.to_docker(protocol, await protocol.screen.get()) - operator = await resp.wait_response() - else: - operator.set_prime_tag("Friend") - target = ( - protocol.current_scene.monomer_map.get(protocol.encode_unique_identifier(target_id)) or target_id - ) - notice = Notice().create(operator, {**data.content, "target": target}, self.io.metadata.select_type) - await protocol.screen.push(notice) - await protocol.screen.broadcast("NoticeMe") - - async def to_docker(self, protocol: MAHProtocol, data: DictMedium): - rest = data.content.get("rest") - source_type = rest.get("type") - subject = target = data.content.get("target") - sender = protocol.current_scene.monomer_map.get(protocol.encode_unique_identifier(target)) - kind = sender.prime_tag - if source_type: - if source_type.startswith("Friend") and sender.compare("Friend"): - kind = "Friend" - elif source_type.startswith("Group") and sender.compare("Member"): - if sender.parents: - subject = sender.get_component(MEMetadata).group_id - kind = "Group" - elif sender.prime_tag == "Member": - if sender.parents: - subject = sender.get_component(MEMetadata).group_id - kind = "Group" - resp: Dict = await protocol.docker.behavior.session_handle( - "post", - "sendNudge", - { - "sessionKey": protocol.docker.metadata.session_keys[protocol.current_scene.scene_name], - "target": target, - "subject": subject, - "kind": kind, - }, - ) - resp["id"] = target - data.send_response(DictMedium().create(sender, resp, kind)) - - -class MessageActionParserBehavior(ParserBehavior): - - async def to_docker(self, protocol: MAHProtocol, data: DictMedium): - action = data.type - target = data.content.get("target") - if action.endswith("Send"): - sender = protocol.current_scene.monomer_map.get(protocol.encode_unique_identifier(target)) - rest = data.content.get("rest") - if source_type := rest.get("type"): - if source_type.startswith("Friend") and sender.compare("Friend"): - action = "sendFriendMessage" - elif source_type.startswith("Group") and sender.compare("Member"): - if sender.parents: - target = sender.get_component(MEMetadata).group_id - action = "sendGroupMessage" - elif sender.prime_tag == "Member": - if sender.parents: - target = sender.get_component(MEMetadata).group_id - action = "sendGroupMessage" - elif sender.prime_tag == "Group": - action = "sendGroupMessage" - elif sender.prime_tag == "Friend": - action = "sendFriendMessage" - message = cast(MessageChain, data.content.get("content")).replace_type("Text", "Plain") - resp: Dict = await protocol.docker.behavior.session_handle( - "post", - action, - { - "sessionKey": protocol.docker.metadata.session_keys[protocol.current_scene.scene_name], - "target": target, - "messageChain": message.dict()["__root__"], - **({"quote": rest.get("quote")} if data.content.get("reply") else {}), - }, - ) - if resp["messageId"] == -1: - protocol.screen.edoves.logger.error( - f"{protocol.current_scene.scene_name}: 消息发送失败:账号可能被风控" - ) - resp["id"] = target - data.send_response( - DictMedium().create(sender, resp, action.replace("send", "").replace("Message", "")) - ) - elif action.endswith("Revoke"): - await protocol.docker.behavior.session_handle( - "post", - "recall", - { - "sessionKey": protocol.docker.metadata.session_keys[protocol.current_scene.scene_name], - "target": target, - }, - ) - elif action.endswith("Get"): - message = await protocol.docker.behavior.session_handle( - "get", - "messageFromId", - { - "sessionKey": protocol.docker.metadata.session_keys[protocol.current_scene.scene_name], - "id": target, - }, - ) - data.send_response(message) - - async def from_docker(self, protocol: MAHProtocol, data: DictMedium): - ev_type: str = self.io.metadata.select_type - sender_data: Dict = data.content.get("sender") - if ev_type.endswith("Message"): - if ev_type.startswith("Friend"): - sender = protocol.include_monomer("friend", sender_data) - elif ev_type.startswith("Group") or ev_type.startswith("Temp"): - sender = protocol.include_monomer("member", sender_data) - group = protocol.include_monomer("group", sender_data.get("group")) - if not group.get_child(sender.metadata.identifier): - group.set_child(sender) - sender.metadata.group_id = group.metadata.identifier - else: - sender = protocol.include_temporary_monomer( - sender_data.get("nickname"), str(sender_data.get("id")) - ) - msg = Message().create(sender, MessageChain.parse_obj(data.content.get("messageChain")), ev_type) - msg.id = str(msg.content.find("Source").id) - msg.time = msg.content.find("Source").time - msg.content.remove("Source") - await protocol.screen.push(msg) - await protocol.screen.broadcast("MessageReceived") - elif ev_type.endswith("RecallEvent"): - call = await protocol.screen.push( - DictMedium().create( - protocol.current_scene.protagonist, - { - "target": data.content.pop("messageId"), - }, - "MessageGet", - ) - ) - await self.to_docker(protocol, await protocol.screen.get()) - message = await call.wait_response() - if ev_type.startswith("Friend"): - resp = await protocol.screen.push( - DictMedium().create( - protocol.current_scene.protagonist, - { - "relationship": "Friend", - "target": data.content.pop("operator"), - "rest": {"detail": True}, - }, - "RelationshipGet", - ) - ) - await self.to_docker(protocol, await protocol.screen.get()) - operator = await resp.wait_response() - else: - group_data = data.content.pop("group") - group = protocol.include_monomer("group", group_data) - operator_data = data.content.pop("operator") - operator = protocol.include_monomer("member", operator_data) - if not group.get_child(operator.metadata.identifier): - group.set_child(operator) - operator.metadata.group_id = group.metadata.identifier - msg = Message().create(operator, MessageChain.parse_obj(message["messageChain"]), ev_type) - msg.id = str(msg.content.find("Source").id) - msg.time = msg.content.find("Source").time - msg.content.remove("Source") - await protocol.screen.push(msg) - await protocol.screen.broadcast("MessageRevoked") - - -@MAHProtocol.register_parser -class NudgeEventParser(BaseDataParser): - prefab_metadata = NudgeOperateMeta - prefab_behavior = NudgeParserBehavior - - -@MAHProtocol.register_parser -class MessageEventParser(BaseDataParser): - prefab_metadata = MessageActionMeta - prefab_behavior = MessageActionParserBehavior diff --git a/old/arclet/edoves/mah/parsers/notice/__init__.py b/old/arclet/edoves/mah/parsers/notice/__init__.py deleted file mode 100644 index ef7000e..0000000 --- a/old/arclet/edoves/mah/parsers/notice/__init__.py +++ /dev/null @@ -1,225 +0,0 @@ -from typing import cast - -from arclet.edoves.builtin.medium import DictMedium, Notice -from arclet.edoves.main.interact import IOManager -from arclet.edoves.main.interact.parser import BaseDataParser, ParserBehavior, ParserMetadata -from arclet.edoves.main.utilles import IOStatus - -from ...monomers import MahEntity -from ...protocol import MAHProtocol - - -class RelationshipOperateMeta(ParserMetadata): - parser_targets = ( - "RelationshipRemove", - "RelationshipGet", - "MemberLeaveEventQuit", - "MemberLeaveEventKick", - "MemberJoinEvent", - ) - - -class RelationshipOperateBehavior(ParserBehavior): - async def from_docker(self, protocol: MAHProtocol, data: DictMedium): - ev_type = self.io.metadata.select_type - member_data = data.content.pop("member") - group_data = member_data.pop("group") - group = protocol.include_monomer("group", group_data) - if "MemberLeaveEvent" in ev_type: - member = protocol.exclude_monomer("member", member_data, group_id=group_data.get("id")) - if ev_type.endswith("Quit"): - notice = Notice().create(member, {"group": group}, ev_type) - await protocol.screen.push(notice) - await protocol.screen.broadcast("RelationshipTerminate", relationship="Member") - elif ev_type.endswith("Kick"): - operator_data = data.content.pop("operator") - operator = protocol.include_monomer("member", operator_data) - if not group.get_child(operator.metadata.identifier): - group.set_child(group) - operator.metadata.group_id = group.metadata.identifier - notice = Notice().create(member, {"group": group}, ev_type) - notice.operator = operator - await protocol.screen.push(notice) - await protocol.screen.broadcast("RelationshipSevered", relationship="Member") - else: - member = protocol.include_monomer("member", member_data) - if not group.get_child(member.metadata.identifier): - group.set_child(member) - member.metadata.group_id = group.metadata.identifier - - if ev_type.endswith("MemberJoinEvent"): - notice = Notice().create(member, data.content, ev_type) - await protocol.screen.push(notice) - await protocol.screen.broadcast("RelationshipSetup", relationship="Member") - - async def to_docker(self, protocol: MAHProtocol, data: DictMedium): - action = self.io.metadata.select_type - if action.endswith("Remove"): - target = cast(MahEntity, data.content.get("target")) - relationship = data.content.get("relationship") - if relationship == "Member": - group_id = target.metadata.group_id - # 该群成员被移除 - target.get_parent(group_id).relation["children"].remove(target.identifier) - # 该群成员与群组的关系解除 - target.relation["parents"].remove(protocol.encode_unique_identifier(group_id)) - if not target.compare("Friend") and not target.parents: - # 该群成员不是好友,且没有群组 - protocol.current_scene.monomers.remove(target.metadata.identifier) - target.metadata.state = IOStatus.DELETE_WAIT - await protocol.docker.behavior.session_handle( - "post", - "kick", - { - "sessionKey": protocol.docker.metadata.session_keys[ - protocol.current_scene.scene_name - ], - "target": group_id, - "memberId": target.metadata.identifier, - }, - ) - if action.endswith("Get"): - relationship = data.content.get("relationship") - _target: str = data.content.get("target") - rest = data.content.get("rest") - list_all = rest.get("list_all") - if relationship == "Member": - if list_all: - member_list = await protocol.docker.behavior.session_handle( - "get", - "memberList", - { - "sessionKey": protocol.docker.metadata.session_keys[ - protocol.current_scene.scene_name - ], - "target": _target, - }, - ) - res = [] - for _member_data in member_list: - if str(_member_data.get("id")) not in protocol.current_scene.monomers: - _member = protocol.include_monomer("member", _member_data) - protocol.current_scene.monomers.remove(str(_member_data.get("id"))) - del IOManager.storage[_member.identifier] - _member.metadata.group_id = _member_data["group"]["id"] - else: - _member = protocol.current_scene.monomer_map.get( - protocol.encode_unique_identifier(_member_data.get("id")) - ) - if _member.prime_tag == "Friend": - _member.metadata.alias = _member.metadata.name - _member.metadata.name = _member_data.get("memberName") - _member.set_prime_tag("Member") - res.append(_member) - data.send_response(res) - else: - group = rest.get("group") - info = await protocol.docker.behavior.session_handle( - "get", - "memberInfo", - { - "sessionKey": protocol.docker.metadata.session_keys[ - protocol.current_scene.scene_name - ], - "target": group.metadata.identifier if isinstance(group, MahEntity) else group, - "memberId": _target, - }, - ) - if not isinstance(group, MahEntity): - group = protocol.include_monomer("group", info.get("group")) - member = protocol.include_monomer("member", info) - if not group.get_child(member.metadata.identifier): - group.set_child(member) - member.metadata.group_id = group.metadata.identifier - data.send_response(member) - if relationship == "Friend": - if list_all: - friend_list = await protocol.docker.behavior.session_handle( - "get", - "friendList", - { - "sessionKey": protocol.docker.metadata.session_keys[ - protocol.current_scene.scene_name - ] - }, - ) - res = [] - for _friend_data in friend_list: - if str(_friend_data.get("id")) not in protocol.current_scene.monomers: - _friend = protocol.include_monomer("friend", _friend_data) - protocol.current_scene.monomers.remove(str(_friend_data.get("id"))) - del IOManager.storage[_friend.identifier] - else: - _friend = protocol.current_scene.monomer_map.get( - protocol.encode_unique_identifier(_friend_data.get("id")) - ) - if _friend.prime_tag == "Member": - _friend.set_parent(protocol.current_scene.protagonist) - _friend.metadata.name = _friend_data.get("nickname") - _friend.metadata.alias = _friend_data.get("remark") - _friend.set_prime_tag("Friend") - res.append(_friend) - data.send_response(res) - return - if rest.get("detail", False): - friend_list = await protocol.docker.behavior.session_handle( - "get", - "friendList", - { - "sessionKey": protocol.docker.metadata.session_keys[ - protocol.current_scene.scene_name - ] - }, - ) - for frid in friend_list: - if str(frid.get("id")) == _target: - friend = protocol.include_monomer("friend", frid) - data.send_response(friend) - return - else: - profile = await protocol.docker.behavior.session_handle( - "get", - "friendProfile", - { - "sessionKey": protocol.docker.metadata.session_keys[ - protocol.current_scene.scene_name - ], - "target": _target, - }, - ) - profile.setdefault("id", _target) - friend = protocol.include_monomer("friend", profile) - data.send_response(friend) - if relationship == "Group": - group_list = await protocol.docker.behavior.session_handle( - "get", - "groupList", - {"sessionKey": protocol.docker.metadata.session_keys[protocol.current_scene.scene_name]}, - ) - if list_all: - res = [] - for _group_data in group_list: - if str(_group_data.get("id")) not in protocol.current_scene.monomers: - _group = protocol.include_monomer("group", _group_data) - protocol.current_scene.monomers.remove(str(_group_data.get("id"))) - del IOManager.storage[_group.identifier] - else: - _group = protocol.current_scene.monomer_map.get( - protocol.encode_unique_identifier(_group_data.get("id")) - ) - _group.metadata.name = _group_data.get("name") - _group.metadata.permission = _group_data.get("permission") - res.append(_group) - data.send_response(res) - return - for _group in group_list: - if str(_group.get("id")) == _target: - group = protocol.include_monomer("group", _group) - data.send_response(group) - return - - -@MAHProtocol.register_parser -class RelationshipOperateParser(BaseDataParser): - prefab_metadata = RelationshipOperateMeta - prefab_behavior = RelationshipOperateBehavior diff --git a/old/arclet/edoves/mah/parsers/notice/bot.py b/old/arclet/edoves/mah/parsers/notice/bot.py deleted file mode 100644 index a213e2b..0000000 --- a/old/arclet/edoves/mah/parsers/notice/bot.py +++ /dev/null @@ -1,150 +0,0 @@ -from typing import cast - -from arclet.edoves.builtin.medium import DictMedium, Notice -from arclet.edoves.main.interact.parser import BaseDataParser, ParserBehavior, ParserMetadata -from arclet.edoves.main.utilles import IOStatus - -from ...monomers import MahEntity -from ...protocol import MAHProtocol - - -class BotBaseNoticeMeta(ParserMetadata): - parser_targets = [ - "BotOnlineEvent", - "BotOfflineEventActive", - "BotOfflineEventForce", - "BotOfflineEventDropped", - "BotReloginEvent", - ] - - -class BotStatusMeta(ParserMetadata): - parser_targets = ["BotGroupPermissionChangeEvent", "BotMuteEvent", "BotUnmuteEvent"] - - -class BotRelationshipMeta(ParserMetadata): - parser_targets = ["BotJoinGroupEvent", "BotLeaveEventActive", "BotLeaveEventKick", "RelationshipRemove"] - - -class BotNoticeMeBehavior(ParserBehavior): - async def from_docker(self, protocol: MAHProtocol, data: DictMedium): - notice = Notice().create(protocol.current_scene.protagonist, {}, self.io.metadata.select_type) - await protocol.screen.push(notice) - await protocol.screen.broadcast("NoticeMe") - - async def to_docker(self, protocol: MAHProtocol, data: DictMedium): - pass - - -class BotStatusUpdateBehavior(ParserBehavior): - async def from_docker(self, protocol: MAHProtocol, data: DictMedium): - ev_type = self.io.metadata.select_type - if ev_type == "BotGroupPermissionChangeEvent": - group = protocol.include_monomer("group", data.content.pop("group")) - protocol.current_scene.protagonist.metadata.group_id = group.metadata.identifier - notice = Notice().create(protocol.current_scene.protagonist, data.content, ev_type) - await protocol.screen.push(notice) - await protocol.screen.broadcast("MonomerStatusUpdate") - elif ev_type in ("BotMuteEvent", "BotUnmuteEvent"): - operator_data = data.content.pop("operator") - operator = protocol.include_monomer("member", operator_data) - group = protocol.include_monomer("group", operator_data["group"]) - if not group.get_child(operator.metadata.identifier): - group.set_child(operator) - operator.metadata.group_id = group.metadata.identifier - notice = Notice().create(protocol.current_scene.protagonist, data.content, ev_type) - notice.operator = operator - await protocol.screen.push(notice) - await protocol.screen.broadcast( - "MonomerStatusUpdate", action=f"set{ev_type.replace('Bot', '').replace('Event', '')}" - ) - - async def to_docker(self, protocol: MAHProtocol, data: DictMedium): - pass - - -class BotRelationshipOperateBehavior(ParserBehavior): - - async def from_docker(self, protocol: MAHProtocol, data: DictMedium): - ev_type = self.io.metadata.select_type - if ev_type == "BotJoinGroupEvent": - group = protocol.include_monomer("group", data.content.pop("group")) - protocol.current_scene.protagonist.metadata.group_id = group.metadata.identifier - notice = Notice().create(protocol.current_scene.protagonist, data.content, ev_type) - await protocol.screen.push(notice) - await protocol.screen.broadcast("RelationshipSetup", relationship="Group") - elif ev_type == "BotLeaveEventActive": - group = protocol.exclude_monomer("group", data.content.pop("group")) - notice = Notice().create(protocol.current_scene.protagonist, {"group": group}, ev_type) - await protocol.screen.push(notice) - await protocol.screen.broadcast("RelationshipTerminate", relationship="Group") - elif ev_type == "BotLeaveEventKick": - group = protocol.exclude_monomer("group", data.content.pop("group")) - operator = protocol.exclude_monomer( - "member", data.content.pop("operator"), group_id=group.metadata.identifier - ) - operator.metadata.group_id = group.metadata.identifier - notice = Notice().create(protocol.current_scene.protagonist, {"group": group}, ev_type) - notice.operator = operator - await protocol.screen.push(notice) - await protocol.screen.broadcast("RelationshipSevered", relationship="Group") - - async def to_docker(self, protocol: MAHProtocol, data: DictMedium): - target = cast(MahEntity, data.content.get("target")) - relationship = data.content.get("relationship") - if relationship == "Friend": - # 删除好友 - protocol.current_scene.protagonist.relation["children"].remove(target.identifier) - if not target.compare("Member"): - # 解除所有关系 - protocol.current_scene.monomers.remove(target.metadata.identifier) - target.metadata.state = IOStatus.DELETE_WAIT - else: - target.remove_tags("Friend") # 删除好友标签 - await protocol.docker.behavior.session_handle( - "post", - "deletaFriend", - { - "sessionKey": protocol.docker.metadata.session_keys[protocol.current_scene.scene_name], - "target": target.metadata.identifier, - }, - ) - if relationship == "Group": - protocol.current_scene.protagonist.relation["parents"].remove( - target.identifier - ) # 群组与bot的关系解除 - for mo in target.children: - mo.relation["parents"].remove(target.identifier) # 群组与群组成员的关系解除 - if len(mo.parents) == 0: - protocol.current_scene.monomers.remove( - mo.metadata.identifier - ) # 群组成员与bot的所有关系解除 - elif mo.compare("Friend") and len(mo.parents) == 1: - mo.remove_tags("Member") # 群组成员与bot的群友关系解除 - - await protocol.docker.behavior.session_handle( - "post", - "quit", - { - "sessionKey": protocol.docker.metadata.session_keys[protocol.current_scene.scene_name], - "target": target.metadata.identifier, - }, - ) - - -@MAHProtocol.register_parser -class BotBaseEventParser(BaseDataParser): - prefab_metadata = BotBaseNoticeMeta - prefab_behavior = BotNoticeMeBehavior - - -@MAHProtocol.register_parser -class BotStatusUpdateEventParser(BaseDataParser): - prefab_metadata = BotStatusMeta - prefab_behavior = BotStatusUpdateBehavior - - -@MAHProtocol.register_parser -class BotRelationshipEventParser(BaseDataParser): - prefab_metadata = BotRelationshipMeta - prefab_behavior = BotRelationshipOperateBehavior diff --git a/old/arclet/edoves/mah/parsers/notice/friend.py b/old/arclet/edoves/mah/parsers/notice/friend.py deleted file mode 100644 index 49e3c72..0000000 --- a/old/arclet/edoves/mah/parsers/notice/friend.py +++ /dev/null @@ -1,25 +0,0 @@ -from arclet.edoves.builtin.medium import DictMedium, Notice -from arclet.edoves.main.interact.parser import BaseDataParser, ParserBehavior, ParserMetadata - -from ...protocol import MAHProtocol - - -class FriendEventMeta(ParserMetadata): - parser_targets = ("FriendInputStatusChangedEvent", "FriendNickChangedEvent") - - -class FriendEventBehavior(ParserBehavior): - async def from_docker(self, protocol: MAHProtocol, data: DictMedium): - friend = protocol.include_monomer("friend", data.content.pop("friend")) - notice = Notice().create(friend, data.content, self.io.metadata.select_type) - await protocol.screen.push(notice) - await protocol.screen.broadcast("MonomerMetadataUpdate") - - async def to_docker(self, protocol: MAHProtocol, data: DictMedium): - pass - - -@MAHProtocol.register_parser -class FriendEventParser(BaseDataParser): - prefab_metadata = FriendEventMeta - prefab_behavior = FriendEventBehavior diff --git a/old/arclet/edoves/mah/parsers/notice/group.py b/old/arclet/edoves/mah/parsers/notice/group.py deleted file mode 100644 index 60b59d9..0000000 --- a/old/arclet/edoves/mah/parsers/notice/group.py +++ /dev/null @@ -1,101 +0,0 @@ -from typing import cast - -from arclet.edoves.builtin.medium import DictMedium, Notice -from arclet.edoves.main.interact.parser import BaseDataParser, ParserBehavior, ParserMetadata - -from ...monomers import MahEntity -from ...protocol import MAHProtocol - - -class GroupStatusMeta(ParserMetadata): - parser_targets = ( - "GroupAllowConfessTalkEvent", - "GroupMuteAllEvent", - "GroupAllowAnonymousChatEvent", - "GroupAllowMemberInviteEvent", - "ChangeMonomerStatus", - ) - - -class GroupDataUpdateMeta(ParserMetadata): - parser_targets = ( - "GroupNameChangeEvent", - "GroupEntranceAnnouncementChangeEvent", - ) - - -class GroupChangeStatusBehavior(ParserBehavior): - async def from_docker(self, protocol: MAHProtocol, data: DictMedium): - group = protocol.include_monomer("group", data.content.pop("group")) - ev_type = self.io.metadata.select_type - if ev_type == "GroupAllowConfessTalkEvent": - notice = Notice().create(group, data.content, ev_type) - await protocol.screen.push(notice) - await protocol.screen.broadcast("MonomerStatusUpdate", action="AllowConfessTalk") - else: - operator_data = data.content.pop("operator") - operator = protocol.include_monomer("member", operator_data) - if not group.get_child(operator.metadata.identifier): - group.set_child(group) - operator.metadata.group_id = group.metadata.identifier - notice = Notice().create(group, data.content, ev_type) - notice.operator = operator - await protocol.screen.push(notice) - await protocol.screen.broadcast("MonomerStatusUpdate") - - async def to_docker(self, protocol: MAHProtocol, data: DictMedium): - target = cast(MahEntity, data.content.get("target")) - rest = data.content.get("rest") - if data.content.get("status") == "mute" and target.prime_tag == "Group": - if rest.get("mute"): - await protocol.docker.behavior.session_handle( - "post", - "muteAll", - { - "sessionKey": protocol.docker.metadata.session_keys[ - protocol.current_scene.scene_name - ], - "target": target.metadata.identifier, - }, - ) - else: - await protocol.docker.behavior.session_handle( - "post", - "unmuteAll", - { - "sessionKey": protocol.docker.metadata.session_keys[ - protocol.current_scene.scene_name - ], - "target": target.metadata.identifier, - }, - ) - - -class GroupChangeDataBehavior(ParserBehavior): - async def from_docker(self, protocol: MAHProtocol, data: DictMedium): - group_data = data.content.pop("group") - group = protocol.include_monomer("group", group_data) - operator_data = data.content.pop("operator") - operator = protocol.include_monomer("member", operator_data) - if not group.get_child(operator.metadata.identifier): - group.set_child(group) - operator.metadata.group_id = group.metadata.identifier - notice = Notice().create(group, data.content, self.io.metadata.select_type) - notice.operator = operator - await protocol.screen.push(notice) - await protocol.screen.broadcast("MonomerMetadataUpdate") - - async def to_docker(self, protocol: MAHProtocol, data: DictMedium): - pass - - -@MAHProtocol.register_parser -class GroupStatusUpdateParser(BaseDataParser): - prefab_metadata = GroupStatusMeta - prefab_behavior = GroupChangeStatusBehavior - - -@MAHProtocol.register_parser -class GroupMetadataUpdateParser(BaseDataParser): - prefab_metadata = GroupDataUpdateMeta - prefab_behavior = GroupChangeDataBehavior diff --git a/old/arclet/edoves/mah/parsers/notice/member.py b/old/arclet/edoves/mah/parsers/notice/member.py deleted file mode 100644 index 0df6723..0000000 --- a/old/arclet/edoves/mah/parsers/notice/member.py +++ /dev/null @@ -1,112 +0,0 @@ -from typing import cast - -from arclet.edoves.builtin.medium import DictMedium, Notice -from arclet.edoves.main.interact.parser import BaseDataParser, ParserBehavior, ParserMetadata - -from ...monomers import MahEntity -from ...protocol import MAHProtocol - - -class MemberStatusMeta(ParserMetadata): - parser_targets = ( - "MemberPermissionChangeEvent", - "MemberMuteEvent", - "MemberUnmuteEvent", - "ChangeMonomerStatus", - ) - - -class MemberDataUpdateMeta(ParserMetadata): - parser_targets = ("MemberCardChangeEvent", "MemberSpecialTitleChangeEvent", "MemberHonorChangeEvent") - - -class MemberChangeStatusBehavior(ParserBehavior): - async def from_docker(self, protocol: MAHProtocol, data: DictMedium): - member_data = data.content.pop("member") - group_data = member_data.pop("group") - group = protocol.include_monomer("group", group_data) - - member = protocol.include_monomer("member", member_data) - if not group.get_child(member.metadata.identifier): - group.set_child(member) - member.metadata.group_id = group.metadata.identifier - ev_type = self.io.metadata.select_type - if ev_type == "MemberPermissionChangeEvent": - notice = Notice().create(member, data.content, ev_type) - await protocol.screen.push(notice) - await protocol.screen.broadcast("MonomerStatusUpdate") - else: - if operator_data := data.content.pop("operator"): - operator = protocol.include_monomer("member", operator_data) - if not group.get_child(operator.metadata.identifier): - group.set_child(group) - operator.metadata.group_id = group.metadata.identifier - else: - operator = protocol.current_scene.protagonist - notice = Notice().create(member, data.content, ev_type) - notice.operator = operator - await protocol.screen.push(notice) - await protocol.screen.broadcast( - "MonomerStatusUpdate", active=False, action=ev_type.replace("Member", "").replace("Event", "") - ) - - async def to_docker(self, protocol: MAHProtocol, data: DictMedium): - target = cast(MahEntity, data.content.get("target")) - rest = data.content.get("rest") - if data.content.get("status") == "mute" and target.prime_tag == "Member": - if rest.get("mute"): - mute_time: int = rest.get("mute_time") or 60 - await protocol.docker.behavior.session_handle( - "post", - "mute", - { - "sessionKey": protocol.docker.metadata.session_keys[ - protocol.current_scene.scene_name - ], - "target": target.metadata.group_id, - "memberId": target.metadata.identifier, - "time": mute_time, - }, - ) - else: - await protocol.docker.behavior.session_handle( - "post", - "unmute", - { - "sessionKey": protocol.docker.metadata.session_keys[ - protocol.current_scene.scene_name - ], - "target": target.metadata.group_id, - "memberId": target.metadata.identifier, - }, - ) - - -class MemberChangeDataBehavior(ParserBehavior): - async def from_docker(self, protocol: MAHProtocol, data: DictMedium): - member_data = data.content.pop("member") - group_data = member_data.pop("group") - group = protocol.include_monomer("group", group_data) - - member = protocol.include_monomer("member", member_data) - if not group.get_child(member.metadata.identifier): - group.set_child(member) - member.metadata.group_id = group.metadata.identifier - notice = Notice().create(member, data.content, self.io.metadata.select_type) - await protocol.screen.push(notice) - await protocol.screen.broadcast("MonomerMetadataUpdate") - - async def to_docker(self, protocol: MAHProtocol, data: DictMedium): - pass - - -@MAHProtocol.register_parser -class MemberStatusUpdateParser(BaseDataParser): - prefab_metadata = MemberStatusMeta - prefab_behavior = MemberChangeStatusBehavior - - -@MAHProtocol.register_parser -class MemberMetadataUpdateParser(BaseDataParser): - prefab_metadata = MemberDataUpdateMeta - prefab_behavior = MemberChangeDataBehavior diff --git a/old/arclet/edoves/mah/parsers/request.py b/old/arclet/edoves/mah/parsers/request.py deleted file mode 100644 index f668a16..0000000 --- a/old/arclet/edoves/mah/parsers/request.py +++ /dev/null @@ -1,45 +0,0 @@ -from arclet.edoves.builtin.medium import DictMedium, Request -from arclet.edoves.main.interact.parser import BaseDataParser, ParserBehavior, ParserMetadata - -from ..protocol import MAHProtocol - - -class RequestOperateMeta(ParserMetadata): - parser_targets = [ - "NewFriendRequestEvent", - "MemberJoinRequestEvent", - "BotInvitedJoinGroupRequestEvent", - "Accept", - "Reject", - ] - - -class RequestOperateParserBehavior(ParserBehavior): - async def from_docker(self, protocol: MAHProtocol, data: DictMedium): - friend = protocol.include_temporary_monomer(data.content.pop("nick"), data.content.pop("fromId")) - request = Request().create( - friend, data.content, self.io.metadata.select_type, event=str(data.content.pop("eventId")) - ) - await protocol.screen.push(request) - await protocol.screen.broadcast("RequestReceived") - - async def to_docker(self, protocol: MAHProtocol, data: DictMedium): - rtype = data.content.get("type") - await protocol.docker.behavior.session_handle( - "post", - f"resp/{rtype[0].lower() + rtype[1:]}", - { - "sessionKey": protocol.docker.metadata.session_keys[protocol.current_scene.scene_name], - "eventId": data.content.get("eventId"), - "fromId": data.content["content"].get("fromId"), - "groupId": data.content["content"].get("groupId"), - "operate": data.content.get("operate"), - "message": data.content.get("msg"), - }, - ) - - -@MAHProtocol.register_parser -class RequestEventParser(BaseDataParser): - prefab_metadata = RequestOperateMeta - prefab_behavior = RequestOperateParserBehavior diff --git a/old/arclet/edoves/mah/protocol.py b/old/arclet/edoves/mah/protocol.py deleted file mode 100644 index f67f6c3..0000000 --- a/old/arclet/edoves/mah/protocol.py +++ /dev/null @@ -1,176 +0,0 @@ -from typing import Any, Dict, Literal, Optional, cast - -from arclet.edoves.builtin.medium import Message -from arclet.edoves.main.medium import BaseMedium -from arclet.edoves.main.protocol import AbstractProtocol -from arclet.edoves.main.utilles import IOStatus -from arclet.edoves.main.utilles.data_source_info import DataSourceInfo - -from .monomers import MahEntity -from .server_docker import MAHServerDocker - - -class MAHProtocol(AbstractProtocol): - docker: MAHServerDocker - regular_metas = ["permission", "specialTitle", "joinTimestamp", "lastSpeakTimestamp", "mutetimeRemaining"] - regular_monomer = MahEntity - - def record_event(self, medium: BaseMedium, event: str): - log_level: str = "INFO" - group_message_log_format: str = ( - "{scene_name} >>> {bot_id}: [{group_name}({group_id})] {member_name}({member_id}) -> {message_string}" - ) - friend_message_log_format: str = ( - "{scene_name} >>> {bot_id}: [{friend_name}({friend_id})] -> {message_string}" - ) - other_client_message_log_format: str = ( - "{scene_name} >>> {bot_id}: [{platform_name}({platform_id})] -> {message_string}" - ) - - if self.current_scene.config.use_event_record: - if event == "MessageReceived": - medium: Message = cast(Message, medium) - purveyor: MahEntity = cast(MahEntity, medium.purveyor) - if medium.purveyor.prime_tag == "Member": - self.screen.edoves.logger.log( - log_level, - group_message_log_format.format( - scene_name=self.current_scene.scene_name, - group_name=purveyor.current_group.metadata.name, - group_id=purveyor.current_group.metadata.identifier, - member_id=purveyor.metadata.identifier, - member_name=purveyor.metadata.name, - bot_id=self.current_scene.protagonist.metadata.identifier, - message_string=medium.content.to_text().__repr__(), - ), - ) - elif medium.purveyor.prime_tag == "Friend": - self.screen.edoves.logger.log( - log_level, - friend_message_log_format.format( - scene_name=self.current_scene.scene_name, - friend_id=purveyor.metadata.identifier, - friend_name=purveyor.metadata.name, - bot_id=self.current_scene.protagonist.metadata.identifier, - message_string=medium.content.to_text().__repr__(), - ), - ) - - async def put_metadata(self, meta: str, target: MahEntity, **kwargs): - pass - - async def set_metadata(self, meta: str, value: Any, target: MahEntity, **kwargs): - pass - - def include_temporary_monomer(self, name: str, identifier: str, alias: str = ""): - friend = MahEntity(self, name, identifier, alias) - friend.metadata.state = IOStatus.DELETE_WAIT - friend.set_parent(self.current_scene.protagonist) - return friend - - def include_monomer( - self, - mono_type: Literal["friend", "member", "group"], - monomer_data: Optional[Dict[str, Any]] = None, - target: Optional[str] = None, - **kwargs, - ) -> MahEntity: - mono_id = str(monomer_data.get("id")) - mono_identifier = f"{mono_id}@{self.identifier}" - if mono_type == "friend": - if not (friend := self.current_scene.monomer_map.get(mono_identifier)): - friend = MahEntity(self, monomer_data.get("nickname"), mono_id, monomer_data.get("remark")) - friend.set_parent(self.current_scene.protagonist) - self.current_scene.monomers.append(mono_id) - if friend.prime_tag == "Member": - friend.set_parent(self.current_scene.protagonist) - friend.metadata.name = monomer_data.get("nickname") - friend.metadata.alias = monomer_data.get("remark") - friend.set_prime_tag("Friend") - return friend - if mono_type == "member": - if not (member := self.current_scene.monomer_map.get(mono_identifier)): - member = MahEntity(self, monomer_data.get("memberName"), mono_id) - self.dispatch_metadata(member, monomer_data) - self.current_scene.monomers.append(mono_id) - elif member.prime_tag == "Member": - member.metadata.name = monomer_data.get("memberName") - self.dispatch_metadata(member, monomer_data) - elif member.prime_tag == "Friend": - member.metadata.alias = member.metadata.name - member.metadata.name = monomer_data.get("memberName") - member.set_prime_tag("Member") - return member - if mono_type == "group": - if not (group := self.current_scene.monomer_map.get(mono_identifier)): - group = MahEntity(self, monomer_data.get("name"), mono_id) - self.dispatch_metadata(group, monomer_data) - self.current_scene.monomers.append(mono_id) - group.set_child(self.current_scene.protagonist) - else: - group.metadata.name = monomer_data.get("name") - self.dispatch_metadata(group, monomer_data) - group.set_prime_tag("Group") - return group - - def exclude_monomer( - self, mono_type: Literal["friend", "member", "group"], monomer_data: Dict[str, Any], **kwargs - ): - mono_id = str(monomer_data.get("id")) - mono_identifier = self.encode_unique_identifier(mono_id) - if mono_type == "friend": - if not (friend := self.current_scene.monomer_map.get(mono_identifier)): - friend = MahEntity(self, monomer_data.get("nickname"), mono_id, monomer_data.get("remark")) - else: - self.current_scene.protagonist.relation["children"].remove(friend.identifier) - if not friend.compare("Member"): - friend.metadata.state = IOStatus.DELETE_WAIT - self.current_scene.monomers.remove(friend.metadata.identifier) - friend.set_prime_tag("Friend") - return friend - if mono_type == "member": - if not (member := self.current_scene.monomer_map.get(mono_identifier)): - member = MahEntity(self, monomer_data.get("memberName"), mono_id) - self.dispatch_metadata(member, monomer_data) - else: - group_id = kwargs.get("group_id") - member.get_parent(group_id).relation["children"].remove(member.identifier) - member.relation["parents"].remove(self.encode_unique_identifier(group_id)) - if not member.compare("Friend") and not member.parents: - member.metadata.state = IOStatus.DELETE_WAIT - self.current_scene.monomers.remove(member.metadata.identifier) - member.set_prime_tag("Member") - return member - if mono_type == "group": - if not (group := self.current_scene.monomer_map.get(mono_identifier)): - group = MahEntity(self, monomer_data.get("name"), mono_id) - self.dispatch_metadata(group, monomer_data) - else: - self.current_scene.monomers.remove(mono_id) - self.current_scene.protagonist.relation["parents"].remove(mono_identifier) - group.metadata.state = IOStatus.DELETE_WAIT - - for m in group.children: - # 群组与群组成员的关系解除 - m.relation["parents"].remove(mono_identifier) - if len(m.parents) == 0: - # 群组成员与bot的所有关系解除 - m.metadata.state = IOStatus.DELETE_WAIT - self.current_scene.monomers.remove(m.metadata.identifier) - elif m.compare("Friend") and len(m.parents) == 1: - m.remove_tags("Member") # 群组成员与bot的群友关系解除 - group.set_prime_tag("Group") - return group - - def event_type_predicate(self, content) -> str: - return content.get("type") - - async def ensure_self(self): - profile = await self.docker.behavior.session_handle( - "get", - "botProfile", - {"sessionKey": self.docker.metadata.session_keys[self.current_scene.scene_name]}, - ) - self.current_scene.protagonist.metadata.name = profile.get("nickname") - - source_information = DataSourceInfo(platform="Tencent", name="mirai-api-http", version="Default") diff --git a/old/arclet/edoves/mah/server_docker.py b/old/arclet/edoves/mah/server_docker.py deleted file mode 100644 index 5d6e2bf..0000000 --- a/old/arclet/edoves/mah/server_docker.py +++ /dev/null @@ -1,229 +0,0 @@ -import asyncio -from asyncio import Event -import json -from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union - -from aiohttp import FormData, WebSocketError, WSMsgType -from arclet.edoves.builtin.client import AiohttpClient, AiohttpWSConnection -from arclet.edoves.builtin.event.network import DockerOperate -from arclet.edoves.builtin.medium import DictMedium -from arclet.edoves.main.interact.server_docker import ( - BaseDockerMetaComponent, - BaseServerDocker, - DockerBehavior, -) -from arclet.edoves.main.network import NetworkStatus -from arclet.edoves.main.utilles import DatetimeEncoder, IOStatus, error_check -from arclet.edoves.main.utilles.logger import Logger -from arclet.edoves.main.utilles.security import MIRAI_API_HTTP_DEFAULT - -if TYPE_CHECKING: - from .protocol import MAHProtocol - - -class MAHDockerMeta(BaseDockerMetaComponent): - identifier = "edoves.mirai-api-http.docker" - verify_code: str = MIRAI_API_HTTP_DEFAULT - client: AiohttpClient - session_keys: Dict[str, int] = {} - - -class MAHBehavior(DockerBehavior): - data: MAHDockerMeta - conn_ev: Event - logger: Logger.logger - - async def ws_connection(self) -> Union[AiohttpWSConnection, None]: - self.logger = self.io.protocol.current_scene.edoves.logger - retry_count = 0 - while self.data.state in (NetworkStatus.CONNECTING, NetworkStatus.RETRYING): - if retry_count >= self.io.protocol.current_scene.config.ensure_retries: - self.logger.warning( - f"{self.io.protocol.current_scene.scene_name}: MAHServerDocker connect failed" - ) - self.io.protocol.current_scene.sig_exit.set() - return - try: - try: - return await self.connect() - except Exception as e: - self.logger.warning(f"{self.io.protocol.current_scene.scene_name}: {e}") - await self.quit() - self.logger.warning( - f"{self.io.protocol.current_scene.scene_name}: MAHServerDocker stopped" - ) - await asyncio.sleep(5.0) - self.logger.info( - f"{self.io.protocol.current_scene.scene_name}: MAHServerDocker restarting..." - ) - self.data.state = NetworkStatus.RETRYING - retry_count += 1 - except asyncio.CancelledError: - self.data.state = NetworkStatus.DISMISS - await self.quit() - - async def connect(self): - self.logger.info(f"{self.io.protocol.current_scene.scene_name}: MAHServerDocker connecting...") - async with self.data.client.ensure_network( - self.io.protocol.current_scene.config.url( - "all", - qq=str(self.io.protocol.current_scene.config.account), - verifyKey=str(self.io.protocol.current_scene.config.verify_token), - ) - ) as resp: - self.logger.info(f"{self.io.protocol.current_scene.scene_name}: MAHServerDocker connected.") - self.data.state = NetworkStatus.CONNECTED - return resp - - def activate(self): - async def wait_start(operate: DictMedium): - while not operate.content.get("start"): - await asyncio.sleep(0.01) - self.io.protocol.current_scene.edoves.logger.info( - f"{self.io.protocol.current_scene.scene_name}: MAHServerDocker start!" - ) - self.data.state = NetworkStatus.CONNECTING - response = await self.ws_connection() - self.io.protocol.screen.set_call(operate.mid, response) - - def quit_listen(operate: DictMedium): - if operate.content.get("stop"): - self.data.state = IOStatus.CLOSE_WAIT - - self.io.add_handler(DockerOperate, wait_start, quit_listen) - - async def session_fetch(self) -> Optional[DictMedium]: - ws_conn: AiohttpWSConnection = self.io.protocol.current_scene.protagonist["connect_info"] - try: - ws_message = await ws_conn.receive(timeout=60.0) - except asyncio.TimeoutError: - self.data.state = NetworkStatus.TIMEOUT - try: - try: - self.logger.debug( - f"{self.io.protocol.current_scene.scene_name}: WSConnection trying ping..." - ) - await ws_conn.ping() - self.data.state = NetworkStatus.CONNECTED - except Exception as e: - self.logger.exception( - f"{self.io.protocol.current_scene.scene_name}: WSConnection ping failed: {e!r}" - ) - else: - return - except asyncio.CancelledError: - self.logger.warning( - f"{self.io.protocol.current_scene.scene_name}: WSConnection cancelled, stop" - ) - self.data.state = IOStatus.CLOSE_WAIT - else: - if ws_message.type is WSMsgType.TEXT: - received_data: dict = json.loads(ws_message.data) - raw_data = received_data["data"] - if "session" in raw_data: - self.data.session_keys[self.io.protocol.current_scene.scene_name] = raw_data["session"] - self.logger.success(f"{self.io.protocol.current_scene.scene_name}: get session key") - await self.io.protocol.ensure_self() - return - try: - error_check(raw_data) - return DictMedium().create( - self.io.protocol.current_scene.protagonist, raw_data, "DataReceived" - ) - except Exception as e: - self.logger.error( - f"{self.io.protocol.current_scene.scene_name}: WSConnection's data has error: {e}" - ) - elif ws_message.type is WSMsgType.CLOSE: - self.logger.warning( - f"{self.io.protocol.current_scene.scene_name}: server close WSConnection." - ) - self.data.state = IOStatus.CLOSE_WAIT - return - elif ws_message.type is WSMsgType.CLOSED: - self.logger.warning( - f"{self.io.protocol.current_scene.scene_name}: WSConnection has been closed." - ) - self.data.state = IOStatus.CLOSED - raise WebSocketError(1, "WSConnection closed.") - elif ws_message.type is WSMsgType.PONG: - self.logger.debug( - f"{self.io.protocol.current_scene.scene_name}: WSConnection received pong from remote" - ) - elif ws_message.type == WSMsgType.ERROR: - self.logger.error( - f"{self.io.protocol.current_scene.scene_name}: WSConnection error: {ws_message.data}" - ) - return - else: - self.logger.warning( - f"{self.io.protocol.current_scene.scene_name}: " - f"WSConnection detected a unknown message type: {ws_message.type}" - ) - return - - async def quit(self): - try: - await self.data.client.close() - await self.io.protocol.current_scene.protagonist["connect_info"].close() - self.logger.info(f"{self.io.protocol.current_scene.scene_name}: WSConnection disconnected") - except TypeError: - return - - async def session_handle( - self, - method: str, - action: str, - content: Optional[Union[Dict[str, Any], str, FormData]] = None, - **kwargs, - ): - content = content or {} - if not self.data.client: - raise RuntimeError(f"{self.io.protocol.current_scene.scene_name}: Unable to get client!") - - if method in {"GET", "get"}: - if isinstance(content, str): - content = json.loads(content) - async with self.data.client.get( - self.io.protocol.current_scene.config.url(action, **content) - ) as response: - resp_json: dict = await response.read_json() - - elif method in {"POST", "Update", "post"}: - if not isinstance(content, str): - content = json.dumps(content, cls=DatetimeEncoder) - async with self.data.client.post( - self.io.protocol.current_scene.config.url(action), data=content - ) as response: - resp_json: dict = await response.read_json() - - else: # MULTIPART - if isinstance(content, FormData): - form = content - elif isinstance(content, dict): - form = FormData(quote_fields=False) - for k, v in content.items(): - v: Union[str, bytes, Tuple[Any, dict]] - if isinstance(v, tuple): - form.add_field(k, v[0], **v[1]) - else: - form.add_field(k, v) - else: - raise ValueError - async with self.data.client.post( - self.io.protocol.current_scene.config.url(action), data=form - ) as response: - resp_json: dict = await response.read_json() - resp = resp_json["data"] if "data" in resp_json else resp_json - error_check(resp_json.get("code")) - return resp - - -class MAHServerDocker(BaseServerDocker): - prefab_metadata = MAHDockerMeta - prefab_behavior = MAHBehavior - metadata: MAHDockerMeta - protocol: "MAHProtocol" - - async def wait_connect(self): - await self.await_status(NetworkStatus.CONNECTED) diff --git a/old/arclet/edoves/main/__init__.py b/old/arclet/edoves/main/__init__.py deleted file mode 100644 index 26132b2..0000000 --- a/old/arclet/edoves/main/__init__.py +++ /dev/null @@ -1,159 +0,0 @@ -import asyncio -import importlib.metadata -import time -from typing import Dict, Optional, Tuple, Type, Union - -from arclet.letoderea import EventSystem - -from .config import TemplateConfig -from .context import edoves_instance -from .exceptions import DataMissing, ValidationFailed -from .interact import IOManager -from .interact.module import BaseModule -from .interact.monomer import Monomer, MonoMetaComponent -from .interact.server_docker import BaseServerDocker -from .network import NetworkStatus -from .protocol import AbstractProtocol -from .scene import EdovesScene -from .screen import Screen -from .utilles import SceneStatus -from .utilles.logger import Logger, replace_traceback -from .utilles.security import check_name - -AE_LOGO = "\n".join( - ( - r" _ _ _ ___ _ ", - r" /_\ _ __ ____| | ____| |_ / __\ __| | _____ ______ ___ ", - r" //_\\| '_// ___| |/ __ \ __| / __\ /__` |/ _ \ \ / / __ \/ __| ", - r" / _ \ | | (___| | ___/ |_ / /__ |(__| | (_) \ V / ___\___ \ ", - r" \_/ \_/_| \____|_|\____|\__| \___/ \__,_|\___ /\_/ \____|____/ ", - "", - ) -) - - -class Edoves: - __instance: bool = False - event_system: EventSystem - logger: Logger.logger - screen: Screen - scene_list: Dict[str, EdovesScene] = {} - protocol_list: Dict[Type[AbstractProtocol], AbstractProtocol] = {} - - def __init__( - self, - *, - configs: Dict[str, Union[TemplateConfig, Tuple[Type[TemplateConfig], Dict]]], - event_system: Optional[EventSystem] = None, - debug: bool = False, - ): - if self.__instance: - return - self.event_system: EventSystem = event_system or EventSystem() - self.loop = self.event_system.loop - self.logger = Logger(level="DEBUG" if debug else "INFO").logger - self.screen = Screen(self) - replace_traceback(self.event_system.loop) - - for name, t_config in configs.items(): - try: - check_name(name) - cur_scene = EdovesScene( - name, - self.screen, - t_config[0].parse_obj(t_config[1]) if isinstance(t_config, tuple) else t_config, - ) - self.scene_list.setdefault(name, cur_scene) - except ValidationFailed as e: - self.logger.error(e) - except ValueError as e: - self.logger.critical(f"{e}: {name}") - exit() - self.__instance = True - - @classmethod - def current(cls) -> "Edoves": - return edoves_instance.get() - - @classmethod - def get_scene(cls, name: str) -> EdovesScene: - return cls.scene_list.get(name) - - async def launch_task(self): - self.logger.opt(colors=True, raw=True).info( - "=--------------------------------------------------------=\n" - ) - self.logger.opt(colors=True, raw=True).info(f"{AE_LOGO}") - official = [] - for dist in importlib.metadata.distributions(): - name: str = dist.metadata["Name"] - version: str = dist.version - if name.startswith("arclet-"): - official.append((" ".join(name.split("-")[1:]).title(), version)) - - for name, version in official: - self.logger.opt(colors=True, raw=True).info(f"{name} version: {version}\n") - self.logger.opt(colors=True, raw=True).info( - "=--------------------------------------------------------=\n" - ) - all_time: float = 0 - self.logger.info("Edoves Application Start...") - for name, cur_scene in self.scene_list.items(): - start_time = time.time() - running_task = self.event_system.loop.create_task( - cur_scene.start_running(), name=f"Edoves_{name}_Start_Task" - ) - await running_task - if cur_scene.status == SceneStatus.RUNNING: - self.logger.debug(f"{name} Start Success") - all_time += time.time() - start_time - if all_time > 0: - self.logger.info(f"Edoves Application Start Success, Total Running Time: {all_time:.2f}s") - else: - self.logger.error("Edoves Application Start Failed") - raise KeyboardInterrupt - - async def daemon_task(self): - self.logger.info("Edoves Application Running...") - update_task = [] - for name, cur_scene in self.scene_list.items(): - running_task = self.event_system.loop.create_task( - cur_scene.update(), name=f"Edoves_{name}_Running_Task" - ) - update_task.append(running_task) - self.logger.debug(f"{name} Running...") - await asyncio.gather(*update_task) - # await self.quit_task() - - async def quit_task(self): - self.logger.info("Edoves Application Stop...") - start_task = [] - for name, cur_scene in self.scene_list.items(): - running_task = self.event_system.loop.create_task( - cur_scene.stop_running(), name=f"Edoves_{name}_Stop_Task" - ) - start_task.append(running_task) - await asyncio.gather(*start_task) - self.logger.success("Edoves shutdown. Have a nice day!") - IOManager.storage.clear() - - def run(self): - try: - self.event_system.loop.run_until_complete(self.start()) - except KeyboardInterrupt: - self.logger.warning("Interrupt detected, Edoves stopping ...") - self.event_system.loop.run_until_complete(self.quit_task()) - - async def start(self): - await self.launch_task() - await self.daemon_task() - - def __getitem__(self, item: str) -> EdovesScene: - return self.scene_list.get(item) - - def __getattr__(self, item): - if item in self.scene_list: - return self.__getitem__(item) - if item in self.protocol_list: - return self.protocol_list[item] - raise ValueError diff --git a/old/arclet/edoves/main/action.py b/old/arclet/edoves/main/action.py deleted file mode 100644 index 536aef3..0000000 --- a/old/arclet/edoves/main/action.py +++ /dev/null @@ -1,51 +0,0 @@ -from abc import ABCMeta, abstractmethod -from typing import TYPE_CHECKING - -from .context import ctx_monomer -from .interact.monomer import Monomer, at_mono - -if TYPE_CHECKING: - from .medium import BaseMedium - - -class ExecutiveAction(metaclass=ABCMeta): - action: str - target: "Monomer" - data: "BaseMedium" - - def __init__(self, action: str): - self.action = action - self.target = ctx_monomer.get() - - def to(self, target: "Monomer"): - self.target = target - return self - - @abstractmethod - async def execute(self): - raise NotImplementedError - - -class ExecAs: - monomer: "Monomer" - action: "ExecutiveAction" - - @classmethod - def of(cls, **kwargs): - if monomers := at_mono(**kwargs): - return cls(monomers[0]) - return cls(ctx_monomer.get()) - - def __init__(self, target: "Monomer"): - self.monomer = target - - def run(self, action: "ExecutiveAction"): - self.action = action - self.action.to(self.monomer) - return self - - def __await__(self): - return self.action.execute().__await__() - - -exec_as = ExecAs diff --git a/old/arclet/edoves/main/component/__init__.py b/old/arclet/edoves/main/component/__init__.py deleted file mode 100644 index b08b16d..0000000 --- a/old/arclet/edoves/main/component/__init__.py +++ /dev/null @@ -1,117 +0,0 @@ -from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Type, Union - -from ..utilles import IOStatus - -if TYPE_CHECKING: - from ..interact import TC, InteractiveObject - -_seminal = type("seminal", (), {}) - - -class ComponentMeta(type): - - def __call__(cls, *args, **kwargs): - obj: "Component" = cls.__new__(cls, *args, **kwargs) # type: ignore - obj.__init__(*args, **kwargs) - _ignore = [] - _limit = [] - for m in cls.__mro__[-2::-1]: - _ignore.extend(getattr(m, "__ignore__", [])) - _limit.extend(getattr(m, "__limit__", [])) - obj.__ignore__ = list(set(_ignore)) - obj.__limit__ = list(set(_limit)) - - return obj - - -class Component: - __enable: bool - io: "InteractiveObject" - additions: Dict[str, Any] - - __ignore__ = ["io"] - __limit__ = [] - __required__ = [] - - def __init__(self, io: "InteractiveObject"): - self.__enable = True - self.io = io - self.additions = {} - - @property - def interactive_object(self): - return self.io - - @property - def is_enable(self): - return self.__enable - - def set_active(self, value: bool): - self.__enable = value - - def get_component(self, __t: Union[str, Type["TC"]]) -> "TC": - return self.interactive_object.get_component(__t) - - def __getitem__(self, item: str): - return self.additions.get(item, None) or self.__dict__.get(item, None) - - def __getattr__(self, item): - if item in ("is_enable", "set_active", "_Component__enable"): - return self.__getattribute__(item) - if (attr := self.additions.get(item, _seminal)) != _seminal: - return attr - if self.__enable: - return self.__getattribute__(item) - else: - raise AttributeError(f"Component {self.__class__.__name__} is not enable") - - def __setattr__(self, key, value): - if key == "_Component__enable": - super(Component, self).__setattr__(key, value) - elif self.__enable: - if ( - key not in ("__limit__", "__ignore__", "additions") - and self.__limit__ - and key not in self.__ignore__ + self.__limit__ - ): - self.additions[key] = value - else: - super(Component, self).__setattr__(key, value) - else: - raise AttributeError(f"Component {self.__class__.__name__} is not enable") - - def __delattr__(self, item): - if not self.__enable: - raise AttributeError(f"Component {self.__class__.__name__} is not enable") - if self.__limit__ and item not in self.__ignore__ + self.__limit__: - del self.additions[item] - else: - super().__delattr__(item) - - def __repr__(self): - attrs = [ - f"{k}={v}" for k, v in vars(self).items() if k not in self.__ignore__ and not k.startswith("_") - ] - return f"[{self.__class__.__name__}: " f"{' '.join(attrs)}]" - - -class MetadataComponent(Component, metaclass=ComponentMeta): - tags: List[str] - identifier: str - state: IOStatus - - __limit__ = ["tags", "identifier", "state"] - - def __init__(self, io: "InteractiveObject"): - super().__init__(io) - self.tags = [] - - def add_tags(self, tags: Iterable[str]): - if self.is_enable: - self.tags.extend(set(tags).difference(self.tags)) - - def remove_tags(self, tags: Iterable[str]): - if self.is_enable: - for t in tags: - if t in self.tags: - self.tags.remove(t) diff --git a/old/arclet/edoves/main/component/behavior.py b/old/arclet/edoves/main/component/behavior.py deleted file mode 100644 index d972571..0000000 --- a/old/arclet/edoves/main/component/behavior.py +++ /dev/null @@ -1,50 +0,0 @@ -from abc import ABCMeta, abstractmethod -from typing import TYPE_CHECKING, Optional - -from . import Component - -if TYPE_CHECKING: - from ..interact import InteractiveObject - from ..medium import BaseMedium - - -class BaseBehavior(Component, metaclass=ABCMeta): - - def __init__(self, io: "InteractiveObject"): - super().__init__(io) - self.activate() - - async def set_active(self, value: bool): - super(BaseBehavior, self).set_active(value) - if self.is_enable: - await self.on_enable() - else: - await self.on_disable() - - async def handler_medium( - self, medium: Optional["BaseMedium"] = None, medium_type: Optional["BaseMedium"] = None, **kwargs - ): - if not medium: - await self.io.protocol.get_medium(medium_type, **kwargs) - pass - - @abstractmethod - def activate(self): - """Behavior 初始化时调用""" - - async def start(self): - """当主程序激活时调用""" - pass - - async def update(self): - """主程序会以固定时间间隔调用该方法""" - pass - - async def quit(self): - """当主程序关闭时调用""" - - async def on_enable(self): - pass - - async def on_disable(self): - pass diff --git a/old/arclet/edoves/main/config.py b/old/arclet/edoves/main/config.py deleted file mode 100644 index b264e15..0000000 --- a/old/arclet/edoves/main/config.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Type - -from yarl import URL - -from .interact.server_docker import BaseServerDocker -from .network import NetworkClient -from .typings import TProtocol -from .utilles import DataStructure - - -class TemplateConfig(DataStructure): - protocol: Type[TProtocol] - client: Type[NetworkClient] - docker_type: Type["BaseServerDocker"] - verify_token: str - account: int - host: str = "http://localhost" - port: str - update_interval: float = 0.02 - modules_base_path: str = "./edoves_modules" - ensure_retries: int = 5 - use_event_record: bool = True - - def get(self, key: str): - return self.__dict__.get(key) - - def url(self, api: str, **kwargs: str): - return (self.connection_url() / api).with_query(kwargs) if kwargs else self.connection_url() / api - - def connection_url(self): - return URL(f"{self.host}:{self.port}") diff --git a/old/arclet/edoves/main/context.py b/old/arclet/edoves/main/context.py deleted file mode 100644 index 303dd06..0000000 --- a/old/arclet/edoves/main/context.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import TYPE_CHECKING, Dict - -from arclet.letoderea.utils import ContextModel - -if TYPE_CHECKING: - from . import Edoves - from .event import EdovesBasicEvent - from .interact.module import BaseModule - from .interact.monomer import Monomer - from .scene import EdovesScene - -edoves_instance: "ContextModel[Edoves]" = ContextModel("edoves") -current_scene: "ContextModel[EdovesScene]" = ContextModel("current_scene") -ctx_module: "ContextModel[BaseModule]" = ContextModel("module") -ctx_monomer: "ContextModel[Monomer]" = ContextModel("purveyor") -ctx_event: "ContextModel[EdovesBasicEvent]" = ContextModel("event") - - -context_map: Dict[str, ContextModel] = { - "Edoves": edoves_instance, - "Scene": current_scene, - "Event": ctx_event, - "Module": ctx_module, - "Monomer": ctx_monomer, -} diff --git a/old/arclet/edoves/main/context.pyi b/old/arclet/edoves/main/context.pyi deleted file mode 100644 index 2d37394..0000000 --- a/old/arclet/edoves/main/context.pyi +++ /dev/null @@ -1,24 +0,0 @@ -from typing import TYPE_CHECKING, Dict - -from arclet.letoderea.utils import ContextModel - -if TYPE_CHECKING: - from . import Edoves - from .event import EdovesBasicEvent - from .interact.module import BaseModule - from .interact.monomer import Monomer - from .scene import EdovesScene - -edoves_instance: "ContextModel[Edoves]" = ContextModel("edoves") -current_scene: "ContextModel[EdovesScene]" = ContextModel("current_scene") -ctx_module: "ContextModel[BaseModule]" = ContextModel("module") -ctx_monomer: "ContextModel[Monomer]" = ContextModel("purveyor") -ctx_event: "ContextModel[EdovesBasicEvent]" = ContextModel("event") - -context_map: Dict[str, ContextModel] = { - "Edoves": edoves_instance, - "Scene": current_scene, - "Event": ctx_event, - "Module": ctx_module, - "Monomer": ctx_monomer, -} diff --git a/old/arclet/edoves/main/event.py b/old/arclet/edoves/main/event.py deleted file mode 100644 index e3113f3..0000000 --- a/old/arclet/edoves/main/event.py +++ /dev/null @@ -1,27 +0,0 @@ -from arclet.letoderea.entities.event import StructuredEvent - -from .context import ctx_module, ctx_monomer, edoves_instance -from .medium import BaseMedium - - -class EdovesBasicEvent(StructuredEvent): - medium: BaseMedium - - def medium_vars(self): - return {k: v for k, v in self.medium.__dict__.items() if k in self.medium.__export__} - - def get_params(self): - return self.param_export( - edoves=edoves_instance.get(), - sender=ctx_monomer.get(), - module=ctx_module.get(), - protocol=self.medium.purveyor.protocol, - medium=self.medium, - **self.medium_vars(), - ) - - def __repr__(self) -> str: - return ( - f"Event:{self.__class__.__name__}; " - f"{' '.join([f'{k}={v.__repr__()}' for k, v in self.medium_vars().items()])}>" - ) diff --git a/old/arclet/edoves/main/exceptions.py b/old/arclet/edoves/main/exceptions.py deleted file mode 100644 index c92c63e..0000000 --- a/old/arclet/edoves/main/exceptions.py +++ /dev/null @@ -1,46 +0,0 @@ -class ValidationFailed(Exception): - """识别码验证失败""" - - -class DataMissing(Exception): - """一个或多个数据没有填写""" - - -class InvalidEventTypeDefinition(Exception): - """不合法的事件类型定义.""" - - -class InvalidVerifyKey(Exception): - """无效的 verifyKey 或其配置.""" - - -class AccountNotFound(Exception): - """指定的Bot不存在""" - - -class InvalidSession(Exception): - """Session失效或不存在""" - - -class UnVerifiedSession(Exception): - """Session未认证(未激活)""" - - -class UnknownTarget(Exception): - """发送消息目标不存在(指定对象不存在)""" - - -class AccountMuted(Exception): - """Bot被禁言,指Bot当前无法向指定群发送消息.""" - - -class MessageTooLong(Exception): - """消息过长, 尝试分段发送或报告问题.""" - - -class InvalidArgument(Exception): - """错误的访问,如参数错误等.""" - - -class UnknownError(Exception): - """其他错误""" diff --git a/old/arclet/edoves/main/interact/__init__.py b/old/arclet/edoves/main/interact/__init__.py deleted file mode 100644 index 1fe5a2a..0000000 --- a/old/arclet/edoves/main/interact/__init__.py +++ /dev/null @@ -1,260 +0,0 @@ -import asyncio -from inspect import isclass -from typing import Callable, Coroutine, Dict, List, Optional, Type, TypedDict, TypeVar, Union - -from ..component import Component, MetadataComponent -from ..component.behavior import BaseBehavior -from ..typings import TProtocol -from ..utilles import IOStatus - -TC = TypeVar("TC", bound=Component) -TI = TypeVar("TI") - - -class Relationship(TypedDict): - parents: List[str] - children: List[str] - - -class IOManager: - storage: Dict[str, "InteractiveObject"] = {} - - @classmethod - def filter(cls, item: Type[TI]) -> List[TI]: - return [i for i in cls.storage.values() if isinstance(i, item)] - - -class InteractiveMeta(type): - __check_tags__ = [] - - def __getitem__(self, item): - self.__check_tags__.append([item] if isinstance(item, str) else [*item]) - return self - - def __instancecheck__(self, instance): - if super(InteractiveMeta, self).__instancecheck__(instance): - if self.__check_tags__: - tag = self.__check_tags__.pop(0) - if not instance.compare(*tag): - return False - return True - return False - - -class InteractiveObject(metaclass=InteractiveMeta): - prefab_behavior: Type[BaseBehavior] = BaseBehavior - prefab_metadata: Type[MetadataComponent] = MetadataComponent - _components: Dict[str, Component] - protocol: TProtocol - metadata: prefab_metadata - behavior: prefab_behavior - relation: Relationship - - __slots__ = "metadata", "behavior", "_components", "relation", "protocol" - - __ignore__ = ["_components", "relation", "protocol"] - - def __new__(cls, *args, **kwargs): - __anno = {} - for a in [m.__annotations__ for m in cls.__mro__[-2::-1]]: - __anno.update(a) - for __k in cls.__slots__: - if __k in cls.__ignore__: - continue - if not issubclass(__anno[__k], Component): - raise ValueError - return super().__new__(cls) - - def __init__( - self, - metadata: Optional[Union[prefab_metadata, Type[prefab_metadata]]] = None, - behavior: Optional[Union[prefab_behavior, Type[prefab_behavior]]] = None, - ): - self._components = {} - self.relation = {"parents": [], "children": []} - self.metadata = ( - (metadata(self) if isclass(metadata) else metadata) if metadata else self.prefab_metadata(self) - ) - self.metadata.state = IOStatus.ACTIVATE_WAIT - self.behavior = ( - (behavior(self) if isclass(behavior) else behavior) if behavior else self.prefab_behavior(self) - ) - IOManager.storage[self.identifier] = self - - def compare(self, *tag: str): - """对比输入的tag是否存在于该IO的tags中""" - return set(self.metadata.tags).issuperset(tag) - - def add_tags(self, *tag: str): - """为该IO添加tag""" - self.metadata.add_tags(tag) - - def remove_tags(self, *tag: str): - """移除该IO的tag""" - self.metadata.remove_tags(tag) - - def set_prime_tag(self, tag: str): - """设置首要tag""" - if self.compare(tag): - index = self.metadata.tags.index(tag) - self.metadata.tags[0], self.metadata.tags[index] = ( - self.metadata.tags[index], - self.metadata.tags[0], - ) - else: - self.metadata.tags.append(tag) - self.metadata.tags[0], self.metadata.tags[-1] = self.metadata.tags[-1], self.metadata.tags[0] - - @property - def identifier(self) -> str: - """ - 当自身存在protocol参数时, 返回经过protocol处理的identifier - - 否则返回自身的identifier - """ - if hasattr(self, "protocol"): - return self.protocol.encode_unique_identifier(self.metadata.identifier) - return self.metadata.identifier - - @property - def prime_tag(self) -> Optional[str]: - if self.metadata.tags: - return self.metadata.tags[0] - - async def await_status(self, status: int): - """ - 等待IO的状态变为指定status - """ - while self.metadata.state.value != status: - await asyncio.sleep(0.01) - - @property - def parents(self) -> List["InteractiveObject"]: - return [IOManager.storage[i] for i in self.relation["parents"]] - - @property - def children(self) -> List["InteractiveObject"]: - return [IOManager.storage[i] for i in self.relation["children"]] - - def get_parent(self, identifier: str) -> Optional["InteractiveObject"]: - may_parent = IOManager.storage.get(self.protocol.encode_unique_identifier(identifier)) - if may_parent and may_parent.identifier in self.relation["parents"]: - return may_parent - - def get_child(self, identifier: str) -> Optional["InteractiveObject"]: - may_child = IOManager.storage.get(self.protocol.encode_unique_identifier(identifier)) - if may_child and may_child.identifier in self.relation["children"]: - return may_child - - def filter_parents(self, *tag): - """根据tag来对该IO的父级IO过滤""" - return list(filter(lambda x: x.compare(*tag), self.parents)) - - def filter_children(self, *tag): - """根据tag来对该IO的子级IO过滤""" - return list(filter(lambda x: x.compare(*tag), self.children)) - - def set_parent(self, parent: "InteractiveObject"): - parent.relation["children"].append(self.identifier) - self.relation["parents"].append(parent.identifier) - self.relation["parents"] = list(set(self.relation["parents"])) - parent.relation["children"] = list(set(parent.relation["children"])) - - def set_child(self, child: "InteractiveObject"): - child.relation["parents"].append(self.identifier) - self.relation["children"].append(child.identifier) - self.relation["children"] = list(set(self.relation["children"])) - child.relation["parents"] = list(set(child.relation["parents"])) - - def find_parent(self, *tag: str): - p_table = set() - - def __parent_generator(parent: "InteractiveObject"): - for _i in parent.relation["parents"]: - if _i in p_table: - continue - p_table.add(_i) - _p = IOManager.storage[_i] - yield _p - if _p.parents: - yield from __parent_generator(_p) - - for p in __parent_generator(self): - if p.compare(*tag): - return p - - def find_child(self, *tag: str): - c_table = set() - - def __child_generator(child: "InteractiveObject"): - for _i in child.relation["children"]: - if _i in c_table: - continue - c_table.add(_i) - _c = IOManager.storage[_i] - yield _c - if _c.children: - yield from __child_generator(_c) - - for c in __child_generator(self): - if c.compare(*tag): - return c - - @property - def all_components(self): - return self._components.values() - - def get_component(self, __t: Union[str, Type[TC]]) -> TC: - if isinstance(__t, str): - return self._components.get(__t) - for __c in filter(lambda x: isinstance(x, __t), list(self._components.values())): - return __c - - def get_components(self, __t: Type[TC]) -> List[TC]: - return list(filter(lambda x: isinstance(x, __t), list(self._components.values()))) - - def get_component_in_parent(self, __t: Union[str, Type[TC]]) -> TC: - for __i in self.parents: - if __c := __i.get_component(__t): - return __c - - def get_component_in_children(self, __t: Union[str, Type[TC]]) -> TC: - for __i in self.children: - if __c := __i.get_component(__t): - return __c - - def action(self, method_name: str) -> Callable[..., Coroutine]: - for func in [getattr(c, method_name, None) for c in self.all_components]: - if not func: - continue - return func - - def __getattr__(self, item): - if item in self.__ignore__: - return self.__getattribute__(item) - return self._components.get(item) - - def __setattr__(self, key, value: Union[Dict[str, Component], Component]): - if key in self.__ignore__: - super(InteractiveObject, self).__setattr__(key, value) - else: - self._components.setdefault(key, value) - - def __repr__(self): - return ( - f"<{self.__class__.__name__}; " f"{', '.join([f'{k}={v}' for k, v in self._components.items()])}>" - ) - - def __getstate__(self): - return { - "metadata": { - k: v for k, v in self.metadata.__dict__.items() if k not in self.metadata.__ignore__ - }, - "behavior": self.prefab_behavior, - } - - def __eq__(self, other: "InteractiveObject"): - return self.__getstate__() == other.__getstate__() - - def __hash__(self): - return hash(tuple(self.__getstate__().items())) diff --git a/old/arclet/edoves/main/interact/module.py b/old/arclet/edoves/main/interact/module.py deleted file mode 100644 index 09dcd42..0000000 --- a/old/arclet/edoves/main/interact/module.py +++ /dev/null @@ -1,233 +0,0 @@ -import asyncio -from typing import Callable, Dict, List, Optional, Set, Type, Union - -from arclet.letoderea.entities.auxiliary import BaseAuxiliary -from arclet.letoderea.entities.delegate import EventDelegate, Subscriber -from arclet.letoderea.utils import TEvent, run_always_await, search_event - -from . import InteractiveObject -from ..component import Component, MetadataComponent -from ..component.behavior import BaseBehavior -from ..context import ctx_event, ctx_module -from ..event import EdovesBasicEvent -from ..medium import BaseMedium -from ..typings import TProtocol -from ..utilles import IOStatus -from ..utilles.security import UNKNOWN, VerifyCodeChecker - - -class ModuleMetaComponent(MetadataComponent, VerifyCodeChecker): - io: "BaseModule" - verify_code: str = UNKNOWN - name: str - usage: str - description: str - author: str - - -class MediumHandlers(Component): - io: "BaseModule" - primary_event_delegate: EventDelegate - delegates: List[EventDelegate] - - def __init__(self, io: "BaseModule"): - super(MediumHandlers, self).__init__(io) - self.delegates = [] - - def set_primary_delegate(self, delegate: EventDelegate) -> None: - raise NotImplementedError - - def add_delegate(self, delegate: EventDelegate) -> None: - """ - 将delegate添加到delegates中 - 应当保证同priority同event的delegate只能有一个 - """ - if self.delegates: - last_delegate = self.delegates[-1] - if ( - last_delegate.bind_event == delegate.bind_event - and last_delegate.priority == delegate.priority - ): - last_delegate += delegate.subscribers - self.delegates.append(delegate) - - def remove_delegate(self, target: Union[TEvent, EventDelegate]) -> None: - if isinstance(target, EventDelegate): - self.delegates.remove(target) - else: - delegates = self.require(target) - for delegate in delegates: - self.delegates.remove(delegate) - - def require( - self, - event: Union[str, TEvent], - priority: Optional[int] = None, - ) -> Optional[Union[EventDelegate, List[EventDelegate]]]: - """ - 依据event名称或者event对象,返回对应的delegate - 在每个publisher中可以存在多个delegate,利用priority进行排序 - 但是同priority同event的delegate只能有一个 - """ - _delegates = [delegate for delegate in self.delegates if delegate.bind_event == event] - if not _delegates: - return None - if priority: - for delegate in filter(lambda d: d.priority == priority, _delegates): - return delegate - return _delegates - - def add_handlers(self, event_type: Type[EdovesBasicEvent], handlers: List[Subscriber]): - delegate = EventDelegate(event_type) - delegate += handlers - self.add_delegate(delegate) - - def remove_handler(self, event_type: Type[EdovesBasicEvent]): - self.remove_handler(event_type) - - def __repr__(self): - return f"[{self.__class__.__name__}: " + f"{len(self.delegates)} delegates]" - - -class ModuleBehavior(BaseBehavior): - io: "BaseModule" - invoke_list: Set[str] = {} - - def activate(self): - data = self.get_component(ModuleMetaComponent) - self.interactive_object.protocol.verify(self.interactive_object) - data.state = IOStatus.ESTABLISHED - - async def invoke(self, method_name: str, time: float): - await asyncio.sleep(time) - await run_always_await(self.__getattribute__(method_name)) - - async def invoke_repeating(self, method_name: str, time: float, repeating_rate: float): - if method_name not in self.invoke_list: - self.invoke_list.add(method_name) - await asyncio.sleep(time) - while method_name in self.invoke_list: - await run_always_await(self.__getattribute__(method_name)) - await asyncio.sleep(repeating_rate) - - def cancel_invoke(self, method_name: Optional[str] = None): - if not method_name: - self.invoke_list.clear() - else: - self.invoke_list.remove(method_name) - - def is_invoke(self, method_name: str): - return method_name in self.invoke_list - - def add_handlers( - self, - event_type: Type[EdovesBasicEvent], - *reaction: Callable, - auxiliaries: Optional[List[BaseAuxiliary]] = None, - ): - handlers = self.get_component(MediumHandlers) - - def __wrapper(_reaction): - if isinstance(_reaction, Callable): - handlers.add_handlers(event_type, [Subscriber(_reaction, auxiliaries=auxiliaries)]) - else: - handlers.add_handlers( - event_type, - [Subscriber(_r, auxiliaries=auxiliaries) for _r in _reaction], - ) - - if not reaction: - return __wrapper - __wrapper(reaction) - - async def handler_medium( - self, - medium: Optional[BaseMedium] = None, - medium_type: Optional[BaseMedium] = None, - event_type: Optional[Union[str, Type[EdovesBasicEvent]]] = None, - **kwargs, - ): - if not medium: - medium = await self.io.protocol.screen.get(medium_type, **kwargs) - if event_type: - if isinstance(event_type, str): - event = search_event(event_type)(medium=medium, **kwargs) - else: - event = event_type(medium=medium, **kwargs) - else: - event = ctx_event.get() - await self.handler_event(event) - - async def handler_event(self, event: EdovesBasicEvent): - delegates = self.get_component(MediumHandlers).require(event.__class__) - if not delegates: - return - self.io.metadata.state = IOStatus.PROCESSING - with ctx_module.use(self.io): - await self.io.protocol.screen.edoves.event_system.delegate_exec(delegates, event) - self.io.metadata.state = IOStatus.ESTABLISHED - - -class BaseModule(InteractiveObject): - prefab_metadata = ModuleMetaComponent - prefab_behavior = ModuleBehavior - prefab_handlers = MediumHandlers - metadata: prefab_metadata - behavior: prefab_behavior - handlers: prefab_handlers - local_storage: Dict = {} - - __slots__ = ["handlers"] - - def __init__(self, protocol: TProtocol): - self.protocol = protocol - metadata = self.prefab_metadata(self) - if not hasattr(metadata, "identifier"): - metadata.identifier = f"{self.__class__.__module__}.{self.__class__.__qualname__}" - super().__init__(metadata) - self.handlers = MediumHandlers(self) - if self.local_storage.get(self.__class__): - hds = self.local_storage.pop(self.__class__) - for hd in hds: - self.get_component(self.prefab_behavior).add_handlers(hd[0], *hd[1][0], auxiliaries=hd[1][1]) - - @property - def name(self): - return getattr(self.metadata, "name", self.__class__.__name__) - - @property - def usage(self): - return getattr(self.metadata, "usage", None) - - @property - def description(self): - return getattr(self.metadata, "description", None) - - def change_behavior(self, behavior: Type[prefab_behavior]): - self.behavior = behavior(self) - - @classmethod - def inject_handler( - __module_self__, - event_type: Type[EdovesBasicEvent], - *reaction: Callable, - auxiliaries: Optional[List[BaseAuxiliary]] = None, - ): - if not __module_self__.local_storage.get(__module_self__): - __module_self__.local_storage.setdefault(__module_self__, []) - __module_self__.local_storage[__module_self__].append([event_type, [reaction, auxiliaries]]) - - def add_handler( - __module_self__, - event_type: Type[EdovesBasicEvent], - *reaction: Callable, - auxiliaries: Optional[List[BaseAuxiliary]] = None, - ): - try: - return __module_self__.behavior.add_handlers(event_type, *reaction, auxiliaries=auxiliaries) - except AttributeError: - if not __module_self__.local_storage.get(__module_self__.__class__): - __module_self__.local_storage.setdefault(__module_self__.__class__, []) - __module_self__.local_storage[__module_self__.__class__].append( - [event_type, [reaction, auxiliaries]] - ) diff --git a/old/arclet/edoves/main/interact/monomer.py b/old/arclet/edoves/main/interact/monomer.py deleted file mode 100644 index 5ab844a..0000000 --- a/old/arclet/edoves/main/interact/monomer.py +++ /dev/null @@ -1,118 +0,0 @@ -from abc import abstractmethod -import inspect -from typing import Any, Coroutine, List, Optional, Union - -from . import InteractiveObject, IOManager -from ..component import MetadataComponent -from ..component.behavior import BaseBehavior -from ..typings import TProtocol -from ..utilles import IOStatus - - -class MonoMetaComponent(MetadataComponent): - io: "Monomer" - name: str - alias: str - - __limit__ = ["name", "alias"] - - def __getitem__(self, item: str) -> Union[Any, Coroutine[Any, Any, Any]]: - res = self.additions.get(item, None) or self.__dict__.get(item, None) - if res is None: - return self.io.protocol.put_metadata(item, self.io) - return res - - -class BaseMonoBehavior(BaseBehavior): - io: "Monomer" - - @abstractmethod - def activate(self): ... - - @abstractmethod - async def change_metadata(self, meta: str, value: Any, target: Optional["Monomer"] = None, **addition): - await self.io.protocol.set_metadata(meta, value, target or self.io, **addition) - raise NotImplementedError - - async def update(self): - pass - raise NotImplementedError - - -class Monomer(InteractiveObject): - prefab_metadata = MonoMetaComponent - prefab_behavior = BaseMonoBehavior - metadata: MonoMetaComponent - behavior: BaseMonoBehavior - - def __init__( - self, - protocol: TProtocol, - name: str, - identifier: Union[int, str], - alias: Optional[str] = None, - ): - self.protocol = protocol - data = self.prefab_metadata(self) - data.identifier = str(identifier) - data.name = name - data.alias = alias or "" - super(Monomer, self).__init__(data) - self.metadata.state = IOStatus.ESTABLISHED - - def __getitem__(self, item: str): - parts = item.split(".") - if len(parts) == 1: - return self.metadata.__getitem__(item) - tag, attr = parts[0], parts[1] - if self.compare(tag.lower()): - return self.metadata.__getitem__(item) - - def __setstate__(self, state): - f = inspect.currentframe() - lcs = f.f_back.f_back.f_locals - self.__init__( - lcs["self"].protocol, - state["metadata"]["name"], - state["metadata"]["identifier"], - state["metadata"]["alias"], - ) - self.add_tags(*state["metadata"]["tags"]) - - -def at_mono(**kwargs): - monomers: List["Monomer"] = IOManager.filter(Monomer) - conditions = [] - for key, value in kwargs.items(): - value = str(value) - if key in ("id", "uid", "identifier"): - - def _(monomer: "Monomer", _value=value): - return monomer.metadata.identifier == _value - - elif key == "tag": - - def _(monomer: "Monomer", _value=value): - return monomer.prime_tag == _value - - elif key == "type": - - def _(monomer: "Monomer", _value=value): - return monomer.__class__.__name__ == _value - - else: - - def _(monomer: "Monomer", _key=key, _value=value): - return getattr(monomer.metadata, _key, None) == _value - - conditions.append(_) - return list(filter(lambda x: all([condition(x) for condition in conditions]), monomers)) - - -class _EntitySelect: - - def __getitem__(self, item) -> List["Monomer"]: - return at_mono(**{sl.start: sl.stop for sl in filter(lambda x: isinstance(x, slice), item)}) - - -select = _EntitySelect() diff --git a/old/arclet/edoves/main/interact/parser.py b/old/arclet/edoves/main/interact/parser.py deleted file mode 100644 index 1436bb0..0000000 --- a/old/arclet/edoves/main/interact/parser.py +++ /dev/null @@ -1,64 +0,0 @@ -from abc import abstractmethod -from typing import Sequence - -from . import BaseBehavior, InteractiveObject, MetadataComponent -from ..exceptions import DataMissing -from ..medium import BaseMedium -from ..protocol import AbstractProtocol - - -class ParserMetadata(MetadataComponent): - parser_targets: Sequence[str] - io: "BaseDataParser" - __select_type: str - - __limit__ = ["parser_targets", "__select_type", "_ParserMetadata__select_type"] - - def __init__(self, io: "BaseDataParser"): - super(ParserMetadata, self).__init__(io) - self.__select_type = "UNKNOWN" - self.identifier = io.__class__.__name__ + str( - len(self.parser_targets) + sum(len(t) for t in self.parser_targets) - ) - - @property - def select_type(self): - return self.io.metadata.__select_type - - def chosen_parser(self, target: str): - for p in self.parser_targets: - if p == target: - self.__select_type = p - return p - - def __new__(cls, *args, **kwargs): - if not getattr(cls, "parser_targets", None): - raise DataMissing(f"{cls.__name__} is missing parser_targets") - return super().__new__(cls) - - -class ParserBehavior(BaseBehavior): - io: "BaseDataParser" - - def activate(self): - pass - - @abstractmethod - async def from_docker(self, protocol: AbstractProtocol, data: BaseMedium): - """将server端传入的原始medium处理""" - pass - - @abstractmethod - async def to_docker(self, protocol: AbstractProtocol, data: BaseMedium): - """将来自其他模块的medium传出给server""" - pass - - -class BaseDataParser(InteractiveObject): - prefab_metadata = ParserMetadata - prefab_behavior = ParserBehavior - metadata: ParserMetadata - behavior: prefab_behavior - - def __repr__(self): - return f"<{self.__class__.__name__}>" diff --git a/old/arclet/edoves/main/interact/server_docker.py b/old/arclet/edoves/main/interact/server_docker.py deleted file mode 100644 index dc21ac7..0000000 --- a/old/arclet/edoves/main/interact/server_docker.py +++ /dev/null @@ -1,70 +0,0 @@ -from abc import abstractmethod -from typing import Any, Dict, Optional, Union - -from aiohttp import FormData - -from ..medium import BaseMedium -from ..network import HTTP_METHODS, NetworkClient, NetworkStatus -from ..typings import TProtocol -from ..utilles import IOStatus -from ..utilles.security import UNKNOWN -from .module import BaseModule, ModuleBehavior, ModuleMetaComponent - - -class BaseDockerMetaComponent(ModuleMetaComponent): - io: "BaseServerDocker" - client: NetworkClient - verify_code: str = UNKNOWN - state: Union[IOStatus, NetworkStatus] - - -class DockerBehavior(ModuleBehavior): - io: "BaseServerDocker" - data: BaseDockerMetaComponent - - def __init__(self, io: "BaseServerDocker"): - super().__init__(io) - self.data = self.get_component(BaseDockerMetaComponent) - - @abstractmethod - async def session_fetch(self) -> Optional[BaseMedium]: - raise NotImplementedError - - @abstractmethod - async def connect(self): - async with self.data.client.ensure_network(self.io.protocol.current_scene.config.get("host")): - raise NotImplementedError - - @abstractmethod - async def session_handle( - self, - method: HTTP_METHODS, - action: str, - content: Optional[Union[Dict[str, Any], str, FormData]] = None, - **kwargs, - ): - async with self.data.client.request( - method, self.io.protocol.current_scene.config.url(action, **content), **kwargs - ): - raise NotImplementedError - - async def update(self): - data = await self.session_fetch() - if not data: - return - for p in self.io.protocol.parsers: - if p.metadata.chosen_parser(self.io.protocol.event_type_predicate(data.content)): - await p.behavior.from_docker(self.io.protocol, data) - - -class BaseServerDocker(BaseModule): - prefab_metadata = BaseDockerMetaComponent - prefab_behavior = DockerBehavior - behavior: DockerBehavior - metadata: BaseDockerMetaComponent - - def __init__(self, protocol: TProtocol, client: NetworkClient): - super().__init__(protocol) - data = self.get_component(BaseDockerMetaComponent) - data.state = IOStatus.MEDIUM_GET_WAIT - data.client = client diff --git a/old/arclet/edoves/main/medium.py b/old/arclet/edoves/main/medium.py deleted file mode 100644 index 22d296c..0000000 --- a/old/arclet/edoves/main/medium.py +++ /dev/null @@ -1,136 +0,0 @@ -from asyncio import AbstractEventLoop, Future, wait_for -from datetime import datetime -from typing import Any, Callable, Coroutine, Generic, Optional, Set, TypeVar - -from .interact.monomer import Monomer -from .typings import TMeta -from .utilles import MediumStatus - - -class MediumObserver: - __fut: Optional[Future] - __medium: "BaseMedium" - - def __init__(self, medium: "BaseMedium", loop: AbstractEventLoop) -> None: - self.__fut = loop.create_future() - self.__medium = medium - - @property - def target_status(self) -> "MediumStatus": - return self.__medium.status - - def set_result(self, result: Any) -> None: - self.__medium.status = MediumStatus.FINISHED - if isinstance(result, Exception): - self.__fut.set_exception(result) - else: - self.__fut.set_result(result) - - async def wait_response(self, timeout: float = 0) -> Any: - if timeout > 0: - await wait_for(self.__fut, timeout=timeout) - else: - await self.__fut - return self.__fut.result() - - def __del__(self): - if self.__fut is not None: - self.__fut.cancel() - - def __repr__(self): - return f"" - - -class MediumIdManager: - """内置的 Medium ID 管理器, 不应在外部使用. 非线程安全.""" - - allocated: Set[int] = {0} - - @classmethod - def head(cls) -> int: - return min(cls.allocated) - - @classmethod - def allocate(cls) -> int: - """分配一个新的 Medium ID. - - Returns: - int: 生成的 Medium ID. 注意使用 done() 方法标记本 Medium ID. - """ - new_id = max(cls.allocated) + 1 - cls.allocated.add(new_id) - return new_id - - @classmethod - def done(cls, medium_id: int) -> None: - """标记一个 Medium ID 的任务完成. 本 Medium ID 随后可被复用. - - Args: - medium_id (int): 标记的 Medium ID. - """ - if medium_id in cls.allocated: - cls.allocated.remove(medium_id) - - -class MediumMeta(type): - - def __call__(cls, *args, **kwargs): - obj: "BaseMedium" = cls.__new__(cls, *args, **kwargs) # type: ignore - _export = [] - for m in cls.__mro__[-2::-1]: - _export.extend(getattr(m, "__export__", [])) - obj.__export__ = list(set(_export)) - obj.__init__(*args, **kwargs) # type: ignore - return obj - - -T = TypeVar("T") - - -class BaseMedium(Generic[T], metaclass=MediumMeta): - purveyor: Monomer - type: str - content: TMeta - mid: int - time: datetime - status: MediumStatus - - __export__ = ["content", "purveyor"] - - def __init__(self): - self.mid = MediumIdManager.allocate() - self.time = datetime.now() - self.status = MediumStatus.CREATED - - def __str__(self): - return self.__repr__() - - def __hash__(self): - return hash(self.mid) - - def __eq__(self, other): - return self.mid == other.mid - - def __lt__(self, other): - return self.mid < other.mid - - def send_response(self, result: Any): - """发送对medium发起者的响应.""" - self.purveyor.protocol.screen.set_call(self.mid, result) - - def create(self, purveyor: Monomer, content: Any, medium_type: Optional[str] = None, **kwargs) -> T: - self.purveyor = purveyor - self.type = medium_type or self.__class__.__name__ - self.content = content - for k, v in kwargs.items(): - setattr(self, k, v) - return self - - def action(self, method_name: str) -> Callable[..., Coroutine]: - for func in [getattr(c, method_name, None) for c in self.purveyor.all_components]: - if not func: - continue - return func - - def __repr__(self): - return f"<{self.__class__.__name__}; {', '.join([f'{k}={v}' for k, v in vars(self).items() if v])}>" diff --git a/old/arclet/edoves/main/network.py b/old/arclet/edoves/main/network.py deleted file mode 100644 index f011906..0000000 --- a/old/arclet/edoves/main/network.py +++ /dev/null @@ -1,137 +0,0 @@ -from abc import ABCMeta, abstractmethod -from contextlib import asynccontextmanager -from enum import Enum -from typing import Any, AsyncGenerator, Dict, Literal, Union - -from yarl import URL - - -class NetworkStatus(int, Enum): - CONNECTING = 14343000 - CONNECTED = 26348311 - TIMEOUT = 33306913 - RETRYING = 29841712 - DISMISS = 15433954 - - -HTTP_METHODS = Union[ - Literal["get"], - Literal["post"], - Literal["put"], - Literal["delete"], - Literal["patch"], -] - - -class NetworkResponse(metaclass=ABCMeta): - url: URL - - @abstractmethod - async def read(self) -> bytes: ... - - @abstractmethod - async def cookies(self) -> Dict[str, str]: ... - - @abstractmethod - async def headers(self) -> Dict[str, str]: ... - - @abstractmethod - async def close(self): ... - - @property - @abstractmethod - def status(self) -> int: ... - - @abstractmethod - def raise_for_status(self): ... - - -class NetworkConnection(metaclass=ABCMeta): - server_mode: bool - - @abstractmethod - async def accept(self) -> None: - pass - - @abstractmethod - async def send(self, data: bytes) -> None: ... - - @abstractmethod - async def receive(self) -> bytes: ... - - @abstractmethod - async def ping(self) -> None: ... - - @abstractmethod - async def pong(self) -> None: ... - - @abstractmethod - async def close(self, code: int = 1000, message: bytes = b"") -> None: ... - - @abstractmethod - def status(self) -> int: ... - - @abstractmethod - def raise_for_code(self): ... - - -class NetworkClient(metaclass=ABCMeta): - - @abstractmethod - async def close(self): - """关闭可能存在的连接""" - - @abstractmethod - @asynccontextmanager - async def ensure_network( - self, url: Union[str, URL], *, method: str = "GET", timeout: float = 10.0, **kwargs: Any - ) -> AsyncGenerator[NetworkConnection, None]: - raise NotImplementedError - - @abstractmethod - @asynccontextmanager - async def request( - self, - method: "HTTP_METHODS", - url: Union[str, URL], - headers: Dict[str, str] = None, - data: Union[str, bytes] = None, - **kwargs: Any, - ) -> AsyncGenerator[NetworkResponse, None]: - """Perform HTTP request.""" - raise NotImplementedError - - @abstractmethod - @asynccontextmanager - async def get( - self, url: Union[str, URL], headers: Dict[str, str] = None, **kwargs: Any - ) -> AsyncGenerator[NetworkResponse, None]: - return await self.request("get", url, headers, **kwargs) - - @abstractmethod - @asynccontextmanager - async def post( - self, url: Union[str, URL], data: Union[str, bytes], headers: Dict[str, str] = None, **kwargs: Any - ) -> AsyncGenerator[NetworkResponse, None]: - return await self.request("post", url, headers, data, **kwargs) - - @abstractmethod - @asynccontextmanager - async def put( - self, url: Union[str, URL], data: Union[str, bytes], headers: Dict[str, str] = None, **kwargs: Any - ) -> AsyncGenerator[NetworkResponse, None]: - return await self.request("put", url, headers, data, **kwargs) - - @abstractmethod - @asynccontextmanager - async def delete( - self, url: Union[str, URL], headers: Dict[str, str] = None, **kwargs: Any - ) -> AsyncGenerator[NetworkResponse, None]: - return await self.request("delete", url, headers, **kwargs) - - @abstractmethod - @asynccontextmanager - async def patch( - self, url: Union[str, URL], data: Union[str, bytes], headers: Dict[str, str] = None, **kwargs: Any - ) -> AsyncGenerator[NetworkResponse, None]: - return await self.request("patch", url, headers, data, **kwargs) diff --git a/old/arclet/edoves/main/protocol.py b/old/arclet/edoves/main/protocol.py deleted file mode 100644 index e6a9d10..0000000 --- a/old/arclet/edoves/main/protocol.py +++ /dev/null @@ -1,186 +0,0 @@ -from abc import ABCMeta, abstractmethod -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union - -from .exceptions import DataMissing, ValidationFailed -from .medium import BaseMedium -from .typings import TData -from .utilles import IOStatus -from .utilles.data_source_info import DataSourceInfo -from .utilles.security import EDOVES_DEFAULT - -if TYPE_CHECKING: - from .interact.monomer import Monomer - from .interact.parser import BaseDataParser - from .interact.server_docker import BaseServerDocker - from .scene import EdovesScene - from .screen import Screen - - -class AbstractProtocol(metaclass=ABCMeta): - parsers: List["BaseDataParser"] - source_information: DataSourceInfo - docker: "BaseServerDocker" - screen: "Screen" - - regular_metas: List[str] - regular_monomer: Type["Monomer"] - - __scenes: List[str] - __identifier: str - __current_scene: "EdovesScene" - - if TYPE_CHECKING: - from .interact.module import BaseModule - - verify_check_list: Type[Union[str, "BaseModule"]] = Union[str, "BaseModule"] - else: - verify_check_list = str - - def __init_subclass__(cls, **kwargs): - cls.parsers = [] - for base in reversed(cls.__bases__): - if issubclass(base, AbstractProtocol): - cls.parsers.extend(getattr(base, "parsers", [])) - - def __new__(cls, *args, **kwargs): - if not hasattr(cls, "source_information"): - raise DataMissing(f"{cls.__name__} missing its Source Information") - if not hasattr(cls, "regular_metas"): - raise DataMissing(f"{cls.__name__} missing its Regular Metas") - if not hasattr(cls, "regular_monomer"): - raise DataMissing(f"{cls.__name__} missing its Regular Monomer") - return super(AbstractProtocol, cls).__new__(cls) - - def __init__(self, scene: "EdovesScene", verify_code: str = None): - self.screen = scene.edoves.screen - self.__scenes = [scene.scene_name] - self.__current_scene = scene - self.__identifier = verify_code or self.source_information.instance_identifier - - @property - def identifier(self): - return self.__identifier - - @property - def current_scene(self) -> "EdovesScene": - return self.__current_scene - - @current_scene.setter - def current_scene(self, scene: "EdovesScene"): - if scene.scene_name in self.__scenes: - self.__current_scene = scene - else: - raise ValidationFailed(f"Scene:{scene.scene_name} is not accepted by {self.__class__.__name__}") - - def put_scene(self, scene: "EdovesScene"): - self.__current_scene = scene - self.__scenes.append(scene.scene_name) - - def verify(self, other: verify_check_list): - if isinstance(other, str) and other != self.identifier: - raise ValidationFailed(f"{self.__class__.__name__} verify failed") - if other.metadata.verify_code == EDOVES_DEFAULT: - return - if other.metadata.verify_code != self.identifier: - other.metadata.state = IOStatus.CLOSED - raise ValidationFailed(f"{other.__class__.__name__} verify failed") - - def __repr__(self): - return ( - f"[{self.__class__.__name__}: " - f"server_docker={self.docker.__class__.__name__}, " - f"parsers={len(self.parsers)}-parsers" - f"]" - ) - - async def execution_handle( - self, - ) -> Optional[BaseMedium]: - if self.docker.metadata.state in (IOStatus.CLOSED, IOStatus.CLOSE_WAIT): - return - medium = await self.screen.get() - for p in self.parsers: - if p.metadata.chosen_parser(medium.type): - await p.behavior.to_docker(self, medium) - - def encode_unique_identifier(self, origin: Union[str, int]) -> str: - """ - 用某种方式在原始id中标注协议信息 - """ - return f"{origin}@{self.__identifier}" - - def decode_unique_identifier(self, encoded: str) -> str: - """ - 根据传入id返回原始id - """ - parts = encoded.split("@") - if len(parts) == 1: - self.screen.edoves.logger.warning( - f"{self.__class__.__name__} dose not detect proto-identifier in {encoded}. Maybe it is not encoded" - ) - return parts[0] - if parts[1] != self.__identifier: - raise ValidationFailed("该IO并非该协议的约束对象") - return parts[0] - - @classmethod - def register_parser(cls, parser: Type["BaseDataParser"]): - p = parser() - cls.parsers.append(p) - return p - - @abstractmethod - def record_event(self, medium: BaseMedium, event: str): - """ - 当前协议的事件记录, 通常调用logger - """ - raise NotImplementedError - - @abstractmethod - async def ensure_self(self): - """ - 通过api获取的Bot资料来修改Bot自身的metadata, 主要是name - """ - raise NotImplementedError - - @abstractmethod - def event_type_predicate(self, content: TData) -> str: - """ - 根据api返回原始数据中的event类型字段 - - Example: - return content.get("type") - """ - raise NotImplementedError - - @abstractmethod - async def put_metadata(self, meta: str, target: "Monomer", **kwargs): - """ - 请求更新某个monomer的metadata - """ - raise NotImplementedError - - @abstractmethod - async def set_metadata(self, meta: str, value: Any, target: "Monomer", **kwargs): - """ - 改变某个monomer的metadata - """ - raise NotImplementedError - - @abstractmethod - def include_monomer(self, **kwargs): - """ - 为当前scene添加一个monomer - """ - raise NotImplementedError - - @abstractmethod - def exclude_monomer(self, **kwargs): - """ - 为当前scene排除一个monomer - """ - raise NotImplementedError - - def dispatch_metadata(self, monomer: "Monomer", data: Dict): - for key in filter(lambda x: x in self.regular_metas, data): - monomer.metadata.__setattr__(key, data[key]) diff --git a/old/arclet/edoves/main/scene.py b/old/arclet/edoves/main/scene.py deleted file mode 100644 index a3708df..0000000 --- a/old/arclet/edoves/main/scene.py +++ /dev/null @@ -1,327 +0,0 @@ -import asyncio -from contextlib import contextmanager, suppress -import importlib -from inspect import getmembers, isclass -from os import PathLike -from pathlib import Path -import shelve -from typing import TYPE_CHECKING, Dict, Generic, List, Optional, Type, TypeVar, Union, cast - -from ..builtin.behavior import MiddlewareBehavior -from .context import current_scene -from .exceptions import ValidationFailed -from .interact import InteractiveObject, IOManager -from .interact.module import BaseModule -from .interact.monomer import Monomer, MonoMetaComponent -from .typings import TConfig, TProtocol -from .utilles import IOStatus, SceneStatus - -TMde = TypeVar("TMde", bound=BaseModule) - -if TYPE_CHECKING: - from .screen import Screen - - -class EdovesMetadata(MonoMetaComponent): - io: "EdovesSelf" - - -class EdovesMainBehavior(MiddlewareBehavior): - io: "EdovesSelf" - - async def start(self): - from ..builtin.medium import DictMedium - - self.protocol = self.io.protocol - pak = DictMedium().create(self.io, {"start": True}) - connected = await self.protocol.screen.post(pak, self.protocol.docker, event_type="DockerOperate") - self.get_component(EdovesMetadata).connect_info = await connected.wait_response() - await self.protocol.docker.behavior.session_fetch() - - def activate(self): - self.io.protocol.current_scene.monomers.append(self.io.metadata.identifier) - self.io.add_tags("bot", self.io.protocol.source_information.name, "app") - - -class EdovesSelf(Monomer): - prefab_metadata = EdovesMetadata - prefab_behavior = EdovesMainBehavior - - -class EdovesScene(Generic[TProtocol]): - __name: str - protagonist: EdovesSelf - config: TConfig - modules: List[Type["BaseModule"]] - monomers: List[str] - status: SceneStatus - sig_exit: asyncio.Event - - def __init__(self, name: str, screen: "Screen", config: TConfig): - self.edoves = screen.edoves - self.modules = [] # 存储模块类型 - self.monomers = [] # 不存储protocol的标识符 - self.status = SceneStatus.STOPPED - self.__name = name - self.config = config - self.sig_exit = asyncio.Event() - - if self.config.protocol not in screen.edoves.protocol_list: - screen.edoves.protocol_list[config.protocol] = config.protocol(self) - try: - self.protocol.docker = self.config.docker_type(self.protocol, self.config.client()) - self.edoves.logger.debug(f"{self.config.docker_type.__name__} activate successful") - except ValidationFailed: - self.edoves.logger.warning( - f"{self.config.docker_type.__name__} does not supply the dock server you chosen" - ) - else: - screen.edoves.protocol_list[config.protocol].put_scene(self) - - self.cache_path = f"./edoves_cache/{self.protocol.source_information.name}/{self.scene_name}/" - with self.context() as self_scene: - self_scene.require_modules(path=config.modules_base_path) - self_scene.protagonist = EdovesSelf( - self_scene.protocol, "Edoves Application", self_scene.config.get("account"), "edoves" - ) - - @property - def protocol(self) -> TProtocol: - return self.edoves.protocol_list[self.config.protocol] - - @property - def scene_name(self) -> str: - return self.__name - - @property - def server_docker(self): - return self.protocol.docker - - @property - def all_io(self) -> Dict[str, "InteractiveObject"]: - return {**self.monomer_map, "server": self.protocol.docker, **self.module_map} - - @contextmanager - def context(self): - token = current_scene.set(self) - self.protocol.current_scene = self - yield self - current_scene.reset(token) - - @classmethod - def current(cls) -> "EdovesScene": - return cast(EdovesScene, current_scene.get()) - - @property - def monomer_map(self) -> Dict[str, "Monomer"]: - """带有protocol标识符""" - return { - mono.identifier: mono - for mono in IOManager.filter(Monomer) - if mono.metadata.identifier in self.monomers - } - - @property - def module_map(self) -> Dict[str, "BaseModule"]: - """以模块名字为key""" - return { - module.__class__.__qualname__: module - for module in IOManager.filter(BaseModule) - if module.__class__ in self.modules - } - - def save_snap(self): - path = Path(self.cache_path) - if not path.exists(): - path.mkdir(parents=True) - monomers = self.monomer_map - relation_table = { - i: {"parents": m.relation["parents"], "children": m.relation["children"]} - for i, m in monomers.items() - } - monomer = { - v.metadata.identifier: v - for v in monomers.values() - if v.metadata.identifier != str(self.config.account) - } - with shelve.open(f"{self.cache_path}/monomerSnap.db") as db: - db["rtable"] = relation_table - db["monomer"] = monomer - self.edoves.logger.debug(f"{self.scene_name}: save monomerSnap.db in {self.cache_path}") - - def load_snap(self): - try: - db = shelve.open(f"{self.cache_path}/monomerSnap.db") - try: - monomers = cast(Dict, db["monomer"]) - self.monomers.extend(list(monomers.keys())) - r_table = db["rtable"] - except (KeyError, ModuleNotFoundError): - db.close() - Path(f"{self.cache_path}/monomerSnap.db").unlink() - return - for i, r in r_table.items(): - m = self.monomer_map.get(i) - for ri in r["parents"]: - m.set_parent(self.monomer_map.get(ri)) - for ri in r["children"]: - m.set_child(self.monomer_map.get(ri)) - db.close() - self.edoves.logger.debug(f"{self.scene_name}: load monomerSnap.db in {self.cache_path}") - except FileNotFoundError: - return - - def require(self, path: str) -> Optional[BaseModule]: - """ - 以导入路径方式加载模块 - - Args: - path (str): 模块路径 - """ - with suppress(ModuleNotFoundError): - imported_module = importlib.import_module(path, path) - for _, m in getmembers( - imported_module, lambda x: isclass(x) and issubclass(x, BaseModule) and x is not BaseModule - ): - return self.require_module(m) - - def require_module(self, module_type: Type[TMde]) -> Optional[TMde]: - """激活单个模块并返回 - - Args: - module_type: 要激活的模块类型, 若模块已激活则返回激活完成的模块 - Returns: - new_module: 激活完成的模块 - """ - _name = module_type.__qualname__ - _path = f"{module_type.__module__}.{_name}" - if m := self.module_map.get(module_type.__qualname__): - return m - try: - new_module = module_type(self.protocol) - if new_module.metadata.state in (IOStatus.CLOSED, IOStatus.UNKNOWN): - return - self.modules.append(module_type) - self.edoves.logger.debug(f"{self.scene_name}: {_name} activate successful") - return new_module - except ValidationFailed: - self.edoves.logger.warning( - f"{self.scene_name}: {_name} does not supply the dock server you chosen" - ) - - def require_modules( - self, *module_type: Type[BaseModule], path: Optional[Union[str, PathLike, Path]] = None - ) -> None: - """激活多个模块 - - Args: - module_type: 要激活的多个模块类型, 若有重复则重新激活 - path: 文件路径, 可以是文件夹路径 - """ - - count = 0 - for mt in module_type: - _name = module_type.__qualname__ - _path = f"{module_type.__module__}.{_name}" - if self.module_map.get(mt.__qualname__): - continue - try: - nm = mt(self.protocol) - self.modules.append(mt) - if nm.metadata.state in (IOStatus.CLOSED, IOStatus.UNKNOWN): - continue - self.edoves.logger.debug(f"{self.scene_name}: {_name} activate successful") - count += 1 - except ValidationFailed: - self.edoves.logger.warning( - f"{self.scene_name}: {_name} does not supply the dock server you chosen" - ) - if count > 0: - self.edoves.logger.info(f"{self.scene_name}: {count} modules activate successful") - if path: - path = path if isinstance(path, Path) else Path(path) - if path.is_dir(): - for p in path.iterdir(): - if p.suffix in (".py", "") and p.stem not in {"__init__", "__pycache__"}: - self.require(".".join(p.parts[:-1:1]) + "." + p.stem) - elif path.is_file(): - self.require(".".join(path.parts[:-1:1]) + "." + path.stem) - - async def start_running(self): - if self.status is not SceneStatus.STOPPED: - return - with self.context(): - self.status = SceneStatus.STARTING - self.load_snap() - self.edoves.logger.info( - f"{self.scene_name} Using DataSource: {self.protocol.source_information.info}" - ) - tasks = [ - asyncio.create_task(v.behavior.start(), name=f"{self.scene_name}_IO_Start @AllIO[{i}]") - for i, v in enumerate(self.all_io.values()) - if v.metadata.state not in (IOStatus.CLOSED, IOStatus.UNKNOWN) - ] - - try: - results = await asyncio.gather(*tasks) - for task in results: - if task and task.exception() == NotImplementedError: - self.edoves.logger.warning(f"{task}'s behavior in {self.scene_name} start failed.") - except TimeoutError: - await self.stop_running() - return - if self.sig_exit.is_set(): - await self.stop_running() - return - await asyncio.sleep(0.001) - self.edoves.logger.success( - f"{len(self.all_io)} InteractiveObjects in {self.scene_name} has been loaded." - ) - self.status = SceneStatus.RUNNING - - async def update(self): - while self.status is SceneStatus.RUNNING: - with self.context(): - await asyncio.sleep(self.config.update_interval) - tasks = [] - for i, v in enumerate(self.all_io.values()): - if v.metadata.state == IOStatus.CLOSE_WAIT: - v.metadata.state = IOStatus.CLOSED - if v.metadata.state not in (IOStatus.CLOSED, IOStatus.UNKNOWN): - tasks.append( - asyncio.create_task( - v.behavior.update(), name=f"{self.scene_name}_IO_Update @AllIO[{i}]" - ) - ) - with suppress(NotImplementedError): - await asyncio.gather(*tasks) - - async def stop_running(self): - if self.status not in (SceneStatus.STARTING, SceneStatus.RUNNING): - return - with self.context(): - self.status = SceneStatus.STOPPING - for k, v in self.all_io.items(): - if v.metadata.state not in (IOStatus.CLOSED, IOStatus.UNKNOWN): - try: - v.metadata.state = IOStatus.CLOSED - await v.behavior.quit() - except NotImplementedError: - self.edoves.logger.warning(f"{k}'s behavior in {self.scene_name} quit failed.") - self.status = SceneStatus.CLEANUP - await self.clean_up() - self.status = SceneStatus.STOPPED - - async def clean_up(self): - self.modules.clear() - self.save_snap() - for t in asyncio.all_tasks(self.edoves.loop): - if t is asyncio.current_task(self.edoves.loop): - continue - coro = t.get_coro() - try: - if coro.__qualname__.startswith(f"{self.scene_name}_IO_"): - t.cancel() - self.edoves.logger.debug(f"Cancelling {t.get_name()} wrapping {coro.__qualname__}") - except Exception as e: - self.edoves.logger.warning(f"{self.scene_name}: {(e.__class__, e.args)}") diff --git a/old/arclet/edoves/main/screen.py b/old/arclet/edoves/main/screen.py deleted file mode 100644 index 27ecb83..0000000 --- a/old/arclet/edoves/main/screen.py +++ /dev/null @@ -1,84 +0,0 @@ -from asyncio import PriorityQueue -from contextlib import ExitStack -from typing import TYPE_CHECKING, Any, Dict, Optional, Type, TypeVar, Union - -from arclet.letoderea.utils import search_event - -from .context import ctx_event, ctx_monomer, current_scene, edoves_instance -from .event import EdovesBasicEvent -from .medium import BaseMedium, MediumObserver -from .utilles import IOStatus, MediumStatus - -TM = TypeVar("TM", bound=BaseMedium) - -if TYPE_CHECKING: - from . import Edoves - from .interact import InteractiveObject - - -class Screen: - edoves: "Edoves" - medium_call_list: Dict[int, MediumObserver] - medium_done_list: Dict[int, MediumObserver] - medium_queue: PriorityQueue - - def __init__(self, edoves: "Edoves"): - self.edoves = edoves - self.medium_call_list = {} - self.medium_done_list = {} - self.medium_queue = PriorityQueue() - - async def push(self, medium: TM, in_time: bool = False): - await self.medium_queue.put(medium) - medium.status = MediumStatus.POSTING - call = MediumObserver(medium, self.edoves.loop) - self.medium_call_list[medium.mid] = call - if in_time: - await self.broadcast(medium.type) - return call - - async def get(self, medium_type: Optional[Type[TM]] = None, **kwargs) -> TM: - medium: TM = await self.medium_queue.get() - if medium_type and not isinstance(medium, medium_type): - medium = medium_type().create(medium.purveyor, medium.content, **kwargs) - self.medium_done_list.setdefault(medium.mid, self.medium_call_list.pop(medium.mid)) - medium.status = MediumStatus.HANDLING - return medium - - async def post(self, medium: TM, target: "InteractiveObject", **kwargs): - call = MediumObserver(medium, self.edoves.loop) - medium.status = MediumStatus.HANDLING - self.medium_done_list.setdefault(medium.mid, call) - await target.behavior.handler_medium(medium, **kwargs) - return call - - def set_call(self, mid: int, result: Any): - if mid in self.medium_done_list: - call = self.medium_done_list.pop(mid) - call.set_result(result) - - async def broadcast( - self, event_type: Union[str, Type[EdovesBasicEvent]], medium_type: Optional[Type[TM]] = None, **kwargs - ): - evt = event_type if isinstance(event_type, str) else event_type.__class__.__name__ - medium = await self.get(medium_type=medium_type, event_type=evt) - protocol = medium.purveyor.protocol - io_list = list(protocol.current_scene.all_io.values()) - if isinstance(event_type, str): - event = search_event(event_type)(medium=medium, **kwargs) - else: - event = event_type(medium=medium, **kwargs) - with ExitStack() as stack: - stack.enter_context(edoves_instance.use(self.edoves)) - stack.enter_context(current_scene.use(protocol.current_scene)) - stack.enter_context(ctx_event.use(event)) - stack.enter_context(ctx_monomer.use(event.medium.purveyor)) - self.edoves.event_system.event_publish(event) - protocol.record_event(medium, event.__class__.__name__) - for io in filter( - lambda x: x.metadata.state in (IOStatus.ESTABLISHED, IOStatus.MEDIUM_GET_WAIT), io_list - ): - self.edoves.loop.create_task( - io.behavior.handler_medium(medium=medium, medium_type=medium_type, **kwargs), - name=f"POST_TO_{io.metadata.identifier}<{medium.mid}>", - ) diff --git a/old/arclet/edoves/main/typings.py b/old/arclet/edoves/main/typings.py deleted file mode 100644 index 7a40cdb..0000000 --- a/old/arclet/edoves/main/typings.py +++ /dev/null @@ -1,12 +0,0 @@ -from datetime import datetime, timedelta -from typing import TYPE_CHECKING, Dict, List, TypeVar, Union - -if TYPE_CHECKING: - from .config import TemplateConfig - from .protocol import AbstractProtocol - -TMeta = Union[str, bool, int, float, datetime, timedelta, None, List, Dict] -TData = Union[TMeta, List[TMeta], Dict[str, TMeta]] - -TProtocol = TypeVar("TProtocol", bound="AbstractProtocol") -TConfig = TypeVar("TConfig", bound="TemplateConfig") diff --git a/old/arclet/edoves/main/utilles/__init__.py b/old/arclet/edoves/main/utilles/__init__.py deleted file mode 100644 index 263b9c7..0000000 --- a/old/arclet/edoves/main/utilles/__init__.py +++ /dev/null @@ -1,163 +0,0 @@ -from datetime import datetime -from enum import Enum -import json -from typing import TYPE_CHECKING, Any, Dict, Generator, Type, TypeVar, Union - -from pydantic import BaseConfig, BaseModel, Extra - -from ..exceptions import ( - AccountMuted, - AccountNotFound, - InvalidArgument, - InvalidSession, - InvalidVerifyKey, - MessageTooLong, - UnknownError, - UnknownTarget, - UnVerifiedSession, -) - -if TYPE_CHECKING: - from pydantic.typing import AbstractSetIntStr, DictStrAny, MappingIntStrAny - -T = TypeVar("T") - - -def gen_subclass(target: Type[T]) -> Generator[Type[T], None, None]: - yield target - for sub_cls in target.__subclasses__(): - yield from gen_subclass(sub_cls) - - -class DataStructure(BaseModel): - """ - 一切数据模型的基类. - """ - - def dict( - self, - *, - include: Union["AbstractSetIntStr", "MappingIntStrAny"] = None, - exclude: Union["AbstractSetIntStr", "MappingIntStrAny"] = None, - by_alias: bool = False, - skip_defaults: bool = None, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - ) -> "DictStrAny": - return super().dict( - include=include, - exclude=exclude, - by_alias=True, - skip_defaults=skip_defaults, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=True, - ) - - class Config(BaseConfig): - extra = Extra.allow - - -class SceneStatus(str, Enum): - """指示 Scene 状态的枚举类""" - - STOPPED = "stopped" - """已停止""" - - STARTING = "starting" - """正在启动""" - - RUNNING = "running" - """正常运行""" - - STOPPING = "stopping" - """刚开始关闭""" - - CLEANUP = "cleanup" - """清理残留任务""" - - -class MediumStatus(str, Enum): - """指示 Medium 状态的枚举类""" - - CREATED = "created" - """已创建""" - - POSTING = "posting" - """正在发布""" - - HANDLING = "handling" - """正在处理""" - - FINISHED = "finished" - """已完成""" - - FAILED = "failed" - """失败""" - - -class IOStatus(int, Enum): - """描述IO的状态""" - - ACTIVATE_WAIT = 19852134 - """等待载入Protocol中""" - - ESTABLISHED = 29241211 - """激活成功, 可以传入medium""" - - MEDIUM_GET_WAIT = 17147634 - """主动请求传入medium, 优先级高于ESTABLISHED""" - - PROCESSING = 9403566 - """正在处理medium, 无法接受medium""" - - MEDIUM_POST_WAIT = 1874712 - """主动请求传出medium, 优先级低于ESTABLISHED""" - - CLOSE_WAIT = 21876493 - """主动请求从Protocol中载出""" - - CLOSED = 26106042 - """该IO已载出""" - - DELETE_WAIT = 16312319 - """等待从IO名单中删除""" - - DELETED = 21047882 - """该IO已从IO名单中删除""" - - UNKNOWN = 17342590 - """未知状态""" - - -code_exceptions_mapping: Dict[int, Type[Exception]] = { - 1: InvalidVerifyKey, - 2: AccountNotFound, - 3: InvalidSession, - 4: UnVerifiedSession, - 5: UnknownTarget, - 6: FileNotFoundError, - 10: PermissionError, - 20: AccountMuted, - 30: MessageTooLong, - 400: InvalidArgument, -} - - -def error_check(code: Union[dict, int]): - origin = code - code = code.get("code") if isinstance(code, dict) else code - if not isinstance(code, int) or code == 200 or code == 0: - return - if exc_cls := code_exceptions_mapping.get(code): - raise exc_cls(exc_cls.__doc__) - else: - raise UnknownError(f"{origin}") - - -class DatetimeEncoder(json.JSONEncoder): - """可以编码 datetime 的 JSONEncoder""" - - def default(self, o: Any) -> Any: - return int(o.timestamp()) if isinstance(o, datetime) else super().default(o) diff --git a/old/arclet/edoves/main/utilles/combiner.py b/old/arclet/edoves/main/utilles/combiner.py deleted file mode 100644 index 5888b41..0000000 --- a/old/arclet/edoves/main/utilles/combiner.py +++ /dev/null @@ -1,35 +0,0 @@ -from inspect import isclass -from typing import Optional, Type, TypeVar, Union - -from ..component.behavior import BaseBehavior - -TB = TypeVar("TB", bound=BaseBehavior) - - -def combine_behaviors(*behavior: Union[Type[TB], TB]) -> Optional[Type[BaseBehavior]]: - """ - 混合传入的行为器, 要求传入的所有行为器的父类与io属性类型一致 - - Behavior_B(BehaviorA){io: Monomer}, Behavior_C(BehaviorA){io: Monomer} # 通过 - - Behavior_B(BehaviorA){io: Monomer}, Behavior_C(BehaviorD){io: Monomer} # 不通过 - - Behavior_B(BehaviorA){io: Module}, Behavior_C(BehaviorA){io: Monomer} # 不通过 - - """ - if not behavior: - return - base = behavior[0].__base__ if isclass(behavior[0]) else behavior[0].__class__.__base__ - io = behavior[0].__annotations__.get("io") if isclass(behavior[0]) else behavior[0].io.__class__ - for be in behavior: - if isclass(be): - if be.__base__ != base or be.__annotations__.get("io") != io: - return - else: - if be.__class__.__base__ != base or be.io.__class__ != io: - return - - class CombineBehavior(*behavior): - pass - - return CombineBehavior diff --git a/old/arclet/edoves/main/utilles/data_model.py b/old/arclet/edoves/main/utilles/data_model.py deleted file mode 100644 index c9c03f4..0000000 --- a/old/arclet/edoves/main/utilles/data_model.py +++ /dev/null @@ -1,41 +0,0 @@ -from types import FunctionType -from typing import Any, Dict, Optional, Tuple, Type, TypeVar - -_T = TypeVar("_T") - - -def _create_init(needy_args, optional_args): - header = " def __init__" - args = ["self"] - body = [] - local_param = {"Optional": Optional} - for k, v in needy_args.items(): - args.append(f"{k}: {v.__name__}") - body.append(f"self.{k} = {k}") - local_param.setdefault(v.__name__, v) - - for k, v in optional_args.items(): - args.append(f"{k}: Optional[{v[0].__name__}] = None") - body.append(f"self.{k} = {k} or {v[1]}") - local_param.setdefault(v[0].__name__, v[0]) - - args = ", ".join(args) - local_vars = ", ".join(local_param.keys()) - header = f"{header}({args}):" - body = "\n".join(f" {b}" for b in body) - txt = f"{header}\n{body}" - txt = f"def __create_fn__({local_vars}):\n{txt}\n return __init__" - ns = {} - exec(txt, {}, ns) - return ns["__create_fn__"](**local_param) - - -def create_model(cls: Type[_T]) -> Type[_T]: - if not isinstance(cls.__init__, FunctionType): - needy_data: Dict = cls.__annotations__ - optional_data: Dict[str, Tuple[Type, Any]] = {} - for k in cls.__dict__: - if k in cls.__annotations__: - optional_data.setdefault(k, (needy_data.pop(k), cls.__dict__[k])) - cls.__init__ = _create_init(needy_data, optional_data) - return cls diff --git a/old/arclet/edoves/main/utilles/data_source_info.py b/old/arclet/edoves/main/utilles/data_source_info.py deleted file mode 100644 index 2bc1579..0000000 --- a/old/arclet/edoves/main/utilles/data_source_info.py +++ /dev/null @@ -1,20 +0,0 @@ -from . import DataStructure -from .security import generate_identifier - - -class DataSourceInfo(DataStructure): - platform: str - name: str - version: str - - def __init__(self, platform: str, name: str, version: str): - super().__init__(platform=platform, name=name, version=version) - self.__identifier = generate_identifier(f"{self.platform}_{self.name}_{self.version}") - - @property - def instance_identifier(self): - return self.__identifier - - @property - def info(self): - return f"{self.platform}/{self.name}:<{self.version}>" diff --git a/old/arclet/edoves/main/utilles/event_filter.py b/old/arclet/edoves/main/utilles/event_filter.py deleted file mode 100644 index c3617fd..0000000 --- a/old/arclet/edoves/main/utilles/event_filter.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Type - -from arclet.letoderea.entities.auxiliary import BaseAuxiliary - -from ..event import EdovesBasicEvent - - -class EventFilter(BaseAuxiliary): - """用以对传入的事件进行预处理, 比如限定medium内容""" - - target_event: Type[EdovesBasicEvent] - pass diff --git a/old/arclet/edoves/main/utilles/logger.py b/old/arclet/edoves/main/utilles/logger.py deleted file mode 100644 index a18921a..0000000 --- a/old/arclet/edoves/main/utilles/logger.py +++ /dev/null @@ -1,76 +0,0 @@ -from asyncio import AbstractEventLoop -import os -import sys -import traceback - -import loguru - - -def loguru_excepthook(cls, val, tb, *_, **__): - """loguru 异常回调 - - Args: - cls (Type[Exception]): 异常类 - val (Exception): 异常的实际值 - tb (TracebackType): 回溯消息 - """ - loguru.logger.opt(exception=(cls, val, tb)).error("Exception:") - - -def loguru_async_handler(_, ctx: dict): - """loguru 异步异常回调 - - Args: - _ (AbstractEventLoop): 异常发生的事件循环 - ctx (dict): 异常上下文 - """ - if "exception" in ctx: - loguru.logger.opt(exception=ctx["exception"]).error("Exception:") - else: - loguru.logger.error(f"Exception: {ctx}") - - -def replace_traceback(loop: AbstractEventLoop = None): - """使用 loguru 模块替换默认的 traceback.print_exception 与 sys.excepthook""" - traceback.print_exception = loguru_excepthook - sys.excepthook = loguru_excepthook - if loop: - loop.set_exception_handler(loguru_async_handler) - - -info_format = ( - "{time:YYYY-MM-DD HH:mm:ss.S} | {level: <8} | " - "{name} - {message}" -) -debug_format = ( - "{time:YYYY-MM-DD HH:mm:ss.SSSS} | {level: <9} | " - "{name}:{function}:{line} - {message} " -) - - -class Logger: - logger: loguru.logger = loguru.logger - - def __init__(self, level="INFO"): - log_format = debug_format if level == "DEBUG" else info_format - self.logger.remove() - self.logger.add( - os.path.join(os.getcwd(), "logs", "edoves_latest.log"), - format=log_format, - level=level, - enqueue=True, - rotation="00:00", - compression="zip", - encoding="utf-8", - backtrace=True, - diagnose=True, - colorize=False, - ) - self.logger.add( - sys.stderr, - level=level, - format=log_format, - backtrace=True, - diagnose=True, - colorize=True, - ) diff --git a/old/arclet/edoves/main/utilles/security.py b/old/arclet/edoves/main/utilles/security.py deleted file mode 100644 index 51dc8ce..0000000 --- a/old/arclet/edoves/main/utilles/security.py +++ /dev/null @@ -1,38 +0,0 @@ -from random import Random -import re - -UNKNOWN = "17156595" -UNDEFINED = "30502508" -EDOVES_DEFAULT = "80503" -MIRAI_API_HTTP_DEFAULT = "23330442" -ONEBOT_DEFAULT = "18207135" -GO_CQHTTP_DEFAULT = "1731916" - - -def generate_identifier(extremal_data_source_id: str): - tls = [(c, i) for i, c in enumerate(extremal_data_source_id.upper().replace("-", "_"))] - rand = Random() - rand.seed(sum([(ord(c) // (i + 1) + i + 1) for c, i in tls])) - rand.shuffle(tls) - h = 0 - for i, c in enumerate(tls): - h = 0x1F * (h + c[1] - i) + ord(c[0]) ^ h - return str(h % 0x1FFFFFF) - - -class VerifyCodeChecker: - __slots__ = () - - def __new__(cls, *args, **kwargs): - if "verify_code" not in cls.__annotations__: - raise ValueError("You must define 'verify_code' in your class") - if not cls.__dict__.get("verify_code") or not isinstance(cls.__dict__.get("verify_code"), str): - raise ValueError("verify_code must be a string") - return super().__new__(cls) - - -def check_name(name: str): - if name == "": - raise ValueError("Scene的名字不能为空") - if re.match(r"^[-`~?/.,<>;\':\"|!@#$%^&*()_+=\[\]}{]+.*$", name): - raise ValueError("该Scene的名字含有非法字符") diff --git a/pdm.lock b/pdm.lock index 57651c3..cbcfd27 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,26 +5,26 @@ groups = ["default", "cli", "cron", "dev", "dotenv", "full", "msgspec", "pydantic", "reload", "rich", "toml", "yaml"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:ff5971fff00cca2f21031effa0c543be4be9b16d453f356335f0dbdc7d66a559" +content_hash = "sha256:e99d84914204138faf4ad9e03ebea715f3440304d77161d8775f18336ff51322" [[metadata.targets]] -requires_python = ">=3.10" +requires_python = ">=3.11" [[package]] name = "aiohappyeyeballs" -version = "2.6.1" -requires_python = ">=3.9" +version = "2.7.1" +requires_python = ">=3.10" summary = "Happy Eyeballs for asyncio" groups = ["default"] files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472"}, + {file = "aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d"}, ] [[package]] name = "aiohttp" -version = "3.13.5" -requires_python = ">=3.9" +version = "3.14.5" +requires_python = ">=3.10" summary = "Async http client/server framework (asyncio)" groups = ["default"] dependencies = [ @@ -33,114 +33,155 @@ dependencies = [ "async-timeout<6.0,>=4.0; python_version < \"3.11\"", "attrs>=17.3.0", "frozenlist>=1.1.1", - "multidict<7.0,>=4.5", + "multidict<8.0,>=4.5", "propcache>=0.2.0", - "yarl<2.0,>=1.17.0", + "typing-extensions>=4.4; python_version < \"3.13\"", + "yarl<2.0,>=1.25.1", ] files = [ - {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b"}, - {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5"}, - {file = "aiohttp-3.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f546a4dc1e6a5edbb9fd1fd6ad18134550e096a5a43f4ad74acfbd834fc6670"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c86969d012e51b8e415a8c6ce96f7857d6a87d6207303ab02d5d11ef0cad2274"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b6f6cd1560c5fa427e3b6074bb24d2c64e225afbb7165008903bd42e4e33e28a"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:636bc362f0c5bbc7372bc3ae49737f9e3030dbce469f0f422c8f38079780363d"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a7cbeb06d1070f1d14895eeeed4dac5913b22d7b456f2eb969f11f4b3993796"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca9ef7517fd7874a1a08970ae88f497bf5c984610caa0bf40bd7e8450852b95"}, - {file = "aiohttp-3.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:019a67772e034a0e6b9b17c13d0a8fe56ad9fb150fc724b7f3ffd3724288d9e5"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f34ecee82858e41dd217734f0c41a532bd066bcaab636ad830f03a30b2a96f2a"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4eac02d9af4813ee289cd63a361576da36dba57f5a1ab36377bc2600db0cbb73"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4beac52e9fe46d6abf98b0176a88154b742e878fdf209d2248e99fcdf73cd297"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c180f480207a9b2475f2b8d8bd7204e47aec952d084b2a2be58a782ffcf96074"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2837fb92951564d6339cedae4a7231692aa9f73cbc4fb2e04263b96844e03b4e"}, - {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9010032a0b9710f58012a1e9c222528763d860ba2ee1422c03473eab47703e7"}, - {file = "aiohttp-3.13.5-cp310-cp310-win32.whl", hash = "sha256:7c4b6668b2b2b9027f209ddf647f2a4407784b5d88b8be4efcc72036f365baf9"}, - {file = "aiohttp-3.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:cd3db5927bf9167d5a6157ddb2f036f6b6b0ad001ac82355d43e97a4bde76d76"}, - {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ab7229b6f9b5c1ba4910d6c41a9eb11f543eadb3f384df1b4c293f4e73d44d6"}, - {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f14c50708bb156b3a3ca7230b3d820199d56a48e3af76fa21c2d6087190fe3d"}, - {file = "aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2567b72e1ffc3ab25510db43f355b29eeada56c0a622e58dcdb19530eb0a3cb"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fb0540c854ac9c0c5ad495908fdfd3e332d553ec731698c0e29b1877ba0d2ec6"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9883051c6972f58bfc4ebb2116345ee2aa151178e99c3f2b2bbe2af712abd13"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2294172ce08a82fb7c7273485895de1fa1186cc8294cfeb6aef4af42ad261174"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc"}, - {file = "aiohttp-3.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6d0d932e0f39c02b80744273cd5c388a2d9bc07760a03164f229c8e02662f6"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60869c7ac4aaabe7110f26499f3e6e5696eae98144735b12a9c3d9eae2b51a49"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26d2f8546f1dfa75efa50c3488215a903c0168d253b75fba4210f57ab77a0fb8"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1162a1492032c82f14271e831c8f4b49f2b6078f4f5fc74de2c912fa225d51d"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8b14eb3262fad0dc2f89c1a43b13727e709504972186ff6a99a3ecaa77102b6c"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ca9ac61ac6db4eb6c2a0cd1d0f7e1357647b638ccc92f7e9d8d133e71ed3c6ac"}, - {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7996023b2ed59489ae4762256c8516df9820f751cf2c5da8ed2fb20ee50abab3"}, - {file = "aiohttp-3.13.5-cp311-cp311-win32.whl", hash = "sha256:77dfa48c9f8013271011e51c00f8ada19851f013cde2c48fca1ba5e0caf5bb06"}, - {file = "aiohttp-3.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8"}, - {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9"}, - {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416"}, - {file = "aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1"}, - {file = "aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe"}, - {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14"}, - {file = "aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3"}, - {file = "aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1"}, - {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61"}, - {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832"}, - {file = "aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665"}, - {file = "aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6"}, - {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c"}, - {file = "aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc"}, - {file = "aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83"}, - {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c"}, - {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be"}, - {file = "aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b"}, - {file = "aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1"}, - {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b"}, - {file = "aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3"}, - {file = "aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162"}, - {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a"}, - {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254"}, - {file = "aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a"}, - {file = "aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500"}, - {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9"}, - {file = "aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8"}, - {file = "aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9"}, - {file = "aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1"}, + {file = "aiohttp-3.14.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d51db97c96384fbfcaf8f4c65922183a68b94f891c3c10c862ef5f6df2adbb1f"}, + {file = "aiohttp-3.14.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ae53924aa853a7a2ca20ed4142c7c6b56338e4d4cd999e2980075b9efc2e257a"}, + {file = "aiohttp-3.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2c473a355f9239efcb72c92d5abfd8fcdb0cc78c8e9af607e72ca12dbb36593"}, + {file = "aiohttp-3.14.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e8fa6644e541fcd7e02430588c7fc93b602c1778ea0bc345505db76b61cfb4"}, + {file = "aiohttp-3.14.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:579f97d5120f2971876d2ddca2968135f6944d00c44c3a6590ad7d86ca9b403f"}, + {file = "aiohttp-3.14.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:24409db442e2fb6e766bc7f3943851a8381dec3098140e43bb2e843b79e31b12"}, + {file = "aiohttp-3.14.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5e8f97c0488ffda3082766ac0f2c8150a9a58c4d05788330e479cfd449b37939"}, + {file = "aiohttp-3.14.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50a195903119008fe9cc68710535eb37f556ffffd6a7759afe70a2c145587045"}, + {file = "aiohttp-3.14.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0133c3c3b54a0bf1e71fa5c1ad95c93f07fd54e24ef1fe182f5122e1573d2bf1"}, + {file = "aiohttp-3.14.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c172db893e516e1358e65a95ee20b7ce7173963eefe318b6ab2a2220688b999e"}, + {file = "aiohttp-3.14.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f2a7966bda23dd85051f1661ce0ace38d6890e05ec6c357ecae9d2479cba377e"}, + {file = "aiohttp-3.14.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4887d130a7bbfed3a85493bb5a25e5b5b558d40c1d986dd16970d2bb26d63793"}, + {file = "aiohttp-3.14.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:225c579c23b68b343cccea27a7e06e3bd8ec23a09c30b427eb3f1e4ca6239b20"}, + {file = "aiohttp-3.14.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ab52d8f1fc1b64821c1fbad64a647ed6203627004059a6d1ed4f0858a1499703"}, + {file = "aiohttp-3.14.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb131d775a1573c1aee66656bd78b023577bbdb6cb8349a07773bd4f73e68a6e"}, + {file = "aiohttp-3.14.5-cp311-cp311-win32.whl", hash = "sha256:e87046c8ff77a8decdb6a41d8ab25824b47531b2da933aeab0c1e21c7acff329"}, + {file = "aiohttp-3.14.5-cp311-cp311-win_amd64.whl", hash = "sha256:6f275c11d1aa6d4c458e05a68be084efe3c55a113d99e3f46a318098e52948fc"}, + {file = "aiohttp-3.14.5-cp311-cp311-win_arm64.whl", hash = "sha256:b032a0023eb41d768ce77d83210ab2a3c389bc0b09313273c7e1eca48c10a755"}, + {file = "aiohttp-3.14.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df37b620684e19b5e25724412518ccafc3b1a49cdac706fdbd2f983fad943450"}, + {file = "aiohttp-3.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef60869969180ec2464f1349aff07138ae35ca2200f0946cb3552e49e8f301a8"}, + {file = "aiohttp-3.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d079c0a0135c36e7beb6f1c88087c8f108dc5891cdd0b5eafa778421bda70ed2"}, + {file = "aiohttp-3.14.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abfda5cb094a829f7bc25216a32f7db2e85cc65bd59910f8e7b40b3d9b224764"}, + {file = "aiohttp-3.14.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9cc882cf8619109583c906b4d4a85d6a111a98afa34b7a450d1e08118d016820"}, + {file = "aiohttp-3.14.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7457580535e019e1247ea35d6a02bf081ad30c26d0cbc210c93f6c3ab67a0835"}, + {file = "aiohttp-3.14.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5ed596aedb9c42afd3fe0aae3117725378ac73d2cc5ddc735056fbdb96c5d02"}, + {file = "aiohttp-3.14.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20f085697d7e911f1f73c43ed03fafbed1e7121797e2eb5428efa80398060584"}, + {file = "aiohttp-3.14.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:74b0a9c8270f9b0a11410e124ff8d4f18bfc1f1837440ec84da5ae7b50927b5d"}, + {file = "aiohttp-3.14.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:19e2ba471507c34f8252402ab50f5ab512398b9ea8c8f1cb26beb3f75793ba30"}, + {file = "aiohttp-3.14.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d418ce2af40c6bb685b3f663e9e8de27cb0a22431d8e88a167348d7f01878073"}, + {file = "aiohttp-3.14.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:70cb4008ac2ed1e0ca9e824deb4b53d3aa0d939109698ebf1e723a84337bd794"}, + {file = "aiohttp-3.14.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:a23fe35d776bc03cb495938b9594450d047e3bc08c5255315a82323e9cb7d2dd"}, + {file = "aiohttp-3.14.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3e0eb43bed3c6801a6cee315195377789e90b2a72c2277a475b578535312488d"}, + {file = "aiohttp-3.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be7dd397d64ca3e1869626fa9318aaebb54b7bf93bc72d7a205448d83e4f748"}, + {file = "aiohttp-3.14.5-cp312-cp312-win32.whl", hash = "sha256:eb324e2009fb54db30a071dad7caf6998ee2879c4704007efb244514dad1fec1"}, + {file = "aiohttp-3.14.5-cp312-cp312-win_amd64.whl", hash = "sha256:2cc38a4f2b516bef1714e690df87a0e043faf1a7693c82d860091684453d5111"}, + {file = "aiohttp-3.14.5-cp312-cp312-win_arm64.whl", hash = "sha256:a63afd1f757de949028387e65a7127b61ad0f775432dbb0e62816ae619fe69ac"}, + {file = "aiohttp-3.14.5-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:9ad7e6aa38c20da1be697874349c4c273c8a03b7887169665081706398d0439a"}, + {file = "aiohttp-3.14.5-cp313-cp313-android_24_x86_64.whl", hash = "sha256:f59c7673465908cbe506117176156c127f29f917677afceada34957179221d91"}, + {file = "aiohttp-3.14.5-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1b5416552740edf07234cc9437d0706f2acb67b93c198670b1a68e1b2b587dec"}, + {file = "aiohttp-3.14.5-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:43351bdb5e4c3cb7d1772368e988534e869a74db7778079a83782c11c69535c7"}, + {file = "aiohttp-3.14.5-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:c8c4478bef6d57fcfda15dae461ea3c9f06aa7b257c58df3f2300174ccbb185a"}, + {file = "aiohttp-3.14.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c2c30484dd1417ef98b51021ffa2cc0d7f3c78918adaaaab7e70817335ab3e02"}, + {file = "aiohttp-3.14.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dab9ac5a67c8d1f070c00fa8fccb7cbd1b8dcc1a8d6b42f37540df9b3d4cc603"}, + {file = "aiohttp-3.14.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e1cc2bfaee8c214f06080a7c7d5772419b8a1108e8e5349236189811823fb02a"}, + {file = "aiohttp-3.14.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74efb69332b85675b1eabd760a8cfc2e2cf42c60607c66f88014c1bdfb40942d"}, + {file = "aiohttp-3.14.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:42b5e616946dbaf505e2bff18c9af2cd4ef9e7ef300ee58a6e951a5b7cf147ae"}, + {file = "aiohttp-3.14.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f9033b43f511f27547c557dcaba0177649e10a3725336ccd2cce0fdc1dc4850d"}, + {file = "aiohttp-3.14.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15a310d3c71398e3d7bfc93a1a73fbe664315cd9e9016b8efc1cff85eeab7155"}, + {file = "aiohttp-3.14.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ffa3a523a36d8628f98c06492ae16a31a23d14c0b4ec721757b477319f656d6"}, + {file = "aiohttp-3.14.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5fb6a6e919bfb703227bc1ce6579281b84b1a2ba57deb9794dfdbec7dcd1e40c"}, + {file = "aiohttp-3.14.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:43e1b7994a8b038125f722bff07492ef501110722c2727c408995d9fb864c421"}, + {file = "aiohttp-3.14.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5f3e96071686755d9cd3600c3880183eb94b012178e92746d68101800f0ed8a3"}, + {file = "aiohttp-3.14.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:cd88b01f3d37b7a2a34f91d98f14720206f1ea3d540843fab2d649dd5fb91fec"}, + {file = "aiohttp-3.14.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f2ed8b64dc0c651c0f5a9c926777719770021251b8f336d97c4b80b660836ce1"}, + {file = "aiohttp-3.14.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a9918e58faf62ba2c7147927d06057aec78f42475aff5048047ec47e7265a600"}, + {file = "aiohttp-3.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:42f320d4a5b00b9af0bddcfec5407dc6f2d9816f006b2f79ebbaa31f16895df3"}, + {file = "aiohttp-3.14.5-cp313-cp313-win32.whl", hash = "sha256:3ae800a20947e2c2e53088047d021e6bf7d51560cc49f6a0737a1f79d2e3a13c"}, + {file = "aiohttp-3.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:d05e94cdfe0d15d0206f970722d2554780ce562787b21b218b275447f8751319"}, + {file = "aiohttp-3.14.5-cp313-cp313-win_arm64.whl", hash = "sha256:f001b571ead90ca1770f1e616db255351a1703317f20374c361ef22f12c06d09"}, + {file = "aiohttp-3.14.5-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:939042d5cda21d41a6f512e7cc8b8e33a2aebff863352251da495fbd91b673b5"}, + {file = "aiohttp-3.14.5-cp314-cp314-android_24_x86_64.whl", hash = "sha256:6da32b5ff3fd78d244e37300463434c7145162bfd2b6e9e915ab164da37f7343"}, + {file = "aiohttp-3.14.5-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1b438b73c38111818d0c9d6a5c2bfed8584c8e503a49ef085d70e874ec846738"}, + {file = "aiohttp-3.14.5-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:755933b107ea7a6a9ac916f635a70595a5b1a32fac10a8ff0b9f2ab88555550c"}, + {file = "aiohttp-3.14.5-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:b3cc509327c7b27f6f4727a8830f4004f6df7766e179f2f4b8e54e65c0bec5d3"}, + {file = "aiohttp-3.14.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7bd8ac754ebd6733a3e2a0dd1674c4d8ab086196803fd8dcd776f07b4e2607d9"}, + {file = "aiohttp-3.14.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e724a7b6091f0b1ac064f9d1b15ff9ec52e6033a86cdae649e5f086e32a3c0db"}, + {file = "aiohttp-3.14.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c32e26310cc10e547f53cd13d39a369034f69dcb7d749d5cb0e5f67bc196b6ba"}, + {file = "aiohttp-3.14.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1eb8167961ec4dfcc8cb9dd50bd0ee72519f7ef496be95203e49e27b01618382"}, + {file = "aiohttp-3.14.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c1d60eafd9c7e8e74abd03a5b00df44e7febfe6d9b89b559c0a6551eef0699d4"}, + {file = "aiohttp-3.14.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:137351bf20bbed9a65e839f4a4452ac377389bdb2f2857d2acffef38f5e9f2d1"}, + {file = "aiohttp-3.14.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fba47bc2c3d7303c3d027c6cf4d07626c37b1314ac81f5820c31032e0ca1f677"}, + {file = "aiohttp-3.14.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94684b879ac1d71e4238850c99b62dc1b28d9086b156a2555f082010b85a865c"}, + {file = "aiohttp-3.14.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56572c42e3ecd636de8d2c3dd54cf5fc939cb5c32eb56297f176a0d366fac622"}, + {file = "aiohttp-3.14.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a95529a92a446db351675f4aab518feaf5e99842f63f5dd17160c2b74f382db3"}, + {file = "aiohttp-3.14.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:3edbece0379b8b4aaa67619b8aa2399bb66fce372cd5911098a434ea77220aa0"}, + {file = "aiohttp-3.14.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:56d9828f204331a5ca8850fcfe2bcce95a149f1f223f60cc7216e5524978e480"}, + {file = "aiohttp-3.14.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:20064a177a070d789ee64a50b01a9161d3468e989baacfc6c714aa685c4b332f"}, + {file = "aiohttp-3.14.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:81c2b3dfd56c62bee6108e4852d5970b4cf9086390b6983f52b666e878c1f115"}, + {file = "aiohttp-3.14.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:09ec102b4b8c9a920275733bbc11fdbb615efe6f9231a06007c0218d336fb77a"}, + {file = "aiohttp-3.14.5-cp314-cp314-win32.whl", hash = "sha256:9c428eb2bd8817588d16a0ab898aa4eb5d141f896aa2b394cc79a4cf61d9a8e2"}, + {file = "aiohttp-3.14.5-cp314-cp314-win_amd64.whl", hash = "sha256:6f967dde489ca6a8c02d093ab245d2cbf50ccb5c36adf0188b17b0ca39d24b67"}, + {file = "aiohttp-3.14.5-cp314-cp314-win_arm64.whl", hash = "sha256:1d2d981b53dd09a319e3570ef8cc3bbc3ef86f5a7abef0f6b2bff3867db3a9e7"}, + {file = "aiohttp-3.14.5-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9ce66feae6ac65327379460380549bf1b8df8e17c4e25df2a2bcf168272e3bed"}, + {file = "aiohttp-3.14.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:27c2322e03f66101acb09869ce1cf1efc04994ee95e1735b69827bf8c8b9d781"}, + {file = "aiohttp-3.14.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff75a7537413a86e7cafe98e0e1d6e3dc4b15c6349896e7d5c6b881bfdb6d550"}, + {file = "aiohttp-3.14.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c061aa954daaf57d2a4b8374f9fca621ef0e1b603584431c220c22458c59b6d"}, + {file = "aiohttp-3.14.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1612fa5857b37bf32e5c1eaeefb96e3b01e9c70679eec81f0934e8a600080863"}, + {file = "aiohttp-3.14.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:adbeee7d6fd4cf5fe0aece2fb3edc4243615d3180430ba8149d01a90670cac99"}, + {file = "aiohttp-3.14.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b2966998927d7bed9db12c0a4647b0c7b179755878fc9c357fe1ffd3e3b0c1a5"}, + {file = "aiohttp-3.14.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e317e0fb6b16212c881d2205a7d87414c29acd69320b3aa6dce9d9c7b86fe4f"}, + {file = "aiohttp-3.14.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50343c1757b4b6f6708eeaf24534b32f19dfb99fb1b762c00420867a62fc81e0"}, + {file = "aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:083673c7a94c3ea035caaa5ca04288bdb44887abfe1f5ba23294e6a4b03efd2d"}, + {file = "aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2528cb4c6b92008c76ac9ac6298624069bb2db91ff4929905512d1d84485f658"}, + {file = "aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b1b8ece1e71132d2afba4dbc0c3d62c766e25165990b25db1196c04969eb3d84"}, + {file = "aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fce9523df31cea6284f3e2c479876750d7687cf671d7b25d32b19effc0e86441"}, + {file = "aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:09e0eb18c7e0c8777e2f9149de63799195b9b3ca1b5c81ba6f32f2c6b8628210"}, + {file = "aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6774814fd5c338e72ee0da5cbb9432816df450e69c019f72b5d29bdec2a1792d"}, + {file = "aiohttp-3.14.5-cp314-cp314t-win32.whl", hash = "sha256:33f706574e32c6e694f352a856e05caf18f7f2c871b3e87b41c55ea452b409ab"}, + {file = "aiohttp-3.14.5-cp314-cp314t-win_amd64.whl", hash = "sha256:5ba14a839fbe87cf7c12a6b5661c05f324a296eb8363141edb3944ba63d4c9d3"}, + {file = "aiohttp-3.14.5-cp314-cp314t-win_arm64.whl", hash = "sha256:1061b364556e8172e8d46b0b183adeeb73e8c42d30ebc745591e1bd89acad52e"}, + {file = "aiohttp-3.14.5-cp315-cp315-android_24_arm64_v8a.whl", hash = "sha256:788ecaa9c10533b786ce5ba70c4f2df78ad41819fd00a6c99d92b66f9a32e1da"}, + {file = "aiohttp-3.14.5-cp315-cp315-android_24_x86_64.whl", hash = "sha256:5c76f1802bab718a68ac3cce447160605c734551f95c67ae90fa1132b215cb29"}, + {file = "aiohttp-3.14.5-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:a6d02b4c38de03d9c7617813433e6a0fb6b522797974177d69d9dad431900833"}, + {file = "aiohttp-3.14.5-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:20726f9782d5c2744c1c66255842d1d163bb3edcf768b8de25216bf47f7b6ccf"}, + {file = "aiohttp-3.14.5-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:248d779ad720b49d4fb355720e60c9e5f444f95887bc16974fea48fc56c41789"}, + {file = "aiohttp-3.14.5-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:0a8ea271867e360ac985ae607f4a23ad9a38414b9aca1d49ec98839ae660e49f"}, + {file = "aiohttp-3.14.5-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:823c910f046f23f4c713b8d99a2242dc65f591cb45ee86418fa11762a3c2963c"}, + {file = "aiohttp-3.14.5-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:9b42db919715e91eb76acf3bc492a9a7ccd8bd9adc6745c1412b689735269f14"}, + {file = "aiohttp-3.14.5-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3112585250b199296c26ca6e0131640b6a8d01bab8b232d2eb3763ed469de11"}, + {file = "aiohttp-3.14.5-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c147451b4a58e7050f7f7394e6c467867c84161560001f9ad4fb2d1446743946"}, + {file = "aiohttp-3.14.5-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bf163cc701f3d4ac43ba7d97771bf5fd955220ef5500ef3ee847bc0ecfbf4ec1"}, + {file = "aiohttp-3.14.5-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8d40ce41991e9d56fab4f5dc4a51fe59bc3b5c77c27f4b148963064d00232e8"}, + {file = "aiohttp-3.14.5-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:276a4fc00b1d9ae492b802763a789c5b86328b989c5ea169f2faa447d6a11c7c"}, + {file = "aiohttp-3.14.5-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50983e3be33d8c0942ab88cec3905b10602f64c469b20153c48c5d4e558dd016"}, + {file = "aiohttp-3.14.5-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:16c8abd5bca220a47efe667d26f8460124c81810787e79ee87b242677563d9dd"}, + {file = "aiohttp-3.14.5-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:0790ec66fa4013e83c53b9025a45d454723da1a2fce28b3208c9b32d08af162f"}, + {file = "aiohttp-3.14.5-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:cb11a971a3aea10f9b8373be628f1df932964fc6c6b174516d318a48c3ac4412"}, + {file = "aiohttp-3.14.5-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:932ce7e694bbc29b2bf6f64f2343c27d148d4997c771d01bdade4639b6749ff4"}, + {file = "aiohttp-3.14.5-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:a7d470cf7b206e6359fc77b1b860632fde400d5a2ed59cd0181b93a686bc81ee"}, + {file = "aiohttp-3.14.5-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:6e1d8637cf73eebc92eba2e11d4cfff98a3b562f2505bd75bba766d908926e8d"}, + {file = "aiohttp-3.14.5-cp315-cp315-win32.whl", hash = "sha256:fbdc5ec49f9ca3cd24955cf3520b10a4d4c901ba2572094c84274e9e7eb30534"}, + {file = "aiohttp-3.14.5-cp315-cp315-win_amd64.whl", hash = "sha256:a9d3983bd6ab7aa1cfd573544ae98df9b6cb6912a5185a198263e024a636861d"}, + {file = "aiohttp-3.14.5-cp315-cp315-win_arm64.whl", hash = "sha256:e29347c142cf6e99e0dff5e2995ead1d50fa3b51bf37a7c726a7ccfe5419745a"}, + {file = "aiohttp-3.14.5-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:9bf1d5dcc15204d9ec8b8ea4c18fd66e6b80e5de1f4ecbafb3a2f2740f8039d4"}, + {file = "aiohttp-3.14.5-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:14f04769cfefe4734016a856a83af36133cd17779cef9ae817f812b8ba9d6d51"}, + {file = "aiohttp-3.14.5-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:6e4251c0ba4624a68a2c11471a1ac54c3306876c21f0ae86de085cc9241c8905"}, + {file = "aiohttp-3.14.5-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4f5cf4dc72a71c4cfa9751b4950be22f733626670230d46e7d606592aa22d59"}, + {file = "aiohttp-3.14.5-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dbf53ae2601b7fd5a93c3944deea3a78d40f495226d582c35ef7a433425ce2b2"}, + {file = "aiohttp-3.14.5-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:657291433bf4dd3142f3abac495764cd47d0c7c92087751e6666c6447e65fcef"}, + {file = "aiohttp-3.14.5-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e51a27980c3788e6e6b3325d694fdd4898087fa8a86b2763af77b39353da41e"}, + {file = "aiohttp-3.14.5-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82c7583cd3dfdc7dcc927835b4f6c7faae7ecc1ba3ca5879321621ae2e6f8e84"}, + {file = "aiohttp-3.14.5-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6fdcd6af7e2e51d1ba1b4bea16e97b074bcb7b5dd0246a9d8201341bb28085a0"}, + {file = "aiohttp-3.14.5-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:c8859a013ae0de1074660992139a1a440df3e6b219b86cf0d3f11c2692bb4fe3"}, + {file = "aiohttp-3.14.5-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:8966ecac808dd5f473c9c4cefd10cd3ffda71c18a4d3493b7c7d2ae1803bf2cc"}, + {file = "aiohttp-3.14.5-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:3093b72c215bda16ce961a6d073f6e71d46e022962a9d5d457c5d4d421c78b57"}, + {file = "aiohttp-3.14.5-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:149fb56caf7acb67073126f675d0958d9c4b3125fcd3f6d4877df98aa8a97ce9"}, + {file = "aiohttp-3.14.5-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:293d3ae7c6a0ed176a42e59a1b5fde825ead65c835360f734148e96729f928d2"}, + {file = "aiohttp-3.14.5-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:3f2dcc00191fd563e9075181a14ec31d7dd63223ced7582cc70a15a499de0c79"}, + {file = "aiohttp-3.14.5-cp315-cp315t-win32.whl", hash = "sha256:7779cd97e61ebe583ec2f1c5616cdd038aa08a4453b1848c67842176d054948e"}, + {file = "aiohttp-3.14.5-cp315-cp315t-win_amd64.whl", hash = "sha256:0e6f16f5e49c4b8267988c05ab07760d7064cea57d077c3d068d04b0fbb992cb"}, + {file = "aiohttp-3.14.5-cp315-cp315t-win_arm64.whl", hash = "sha256:1aead151c3abbac6b32942e452020cb66d7efc099d253cc6c20f748e926c858b"}, + {file = "aiohttp-3.14.5-py3-none-any.whl", hash = "sha256:efc21a454892828368b11c2c780de0ff8bc991f73f6b99c6b66e56205470929b"}, + {file = "aiohttp-3.14.5.tar.gz", hash = "sha256:5558a7f5a05af9ecf744af91e5baefc436f93c9333e656c27ec253f9a6bbe178"}, ] [[package]] @@ -160,32 +201,29 @@ files = [ [[package]] name = "annotated-types" -version = "0.7.0" -requires_python = ">=3.8" +version = "0.8.0" +requires_python = ">=3.10" summary = "Reusable constraint types to use with typing.Annotated" groups = ["dev", "pydantic"] -dependencies = [ - "typing-extensions>=4.0.0; python_version < \"3.9\"", -] files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, + {file = "annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0"}, + {file = "annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7"}, ] [[package]] name = "anyio" -version = "4.13.0" +version = "4.15.1" requires_python = ">=3.10" summary = "High-level concurrency and networking framework on top of asyncio or Trio" groups = ["dev", "full", "reload"] dependencies = [ "exceptiongroup>=1.0.2; python_version < \"3.11\"", "idna>=2.8", - "typing-extensions>=4.5; python_version < \"3.13\"", + "typing-extensions>=4.16.0; python_version < \"3.15\"", ] files = [ - {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, - {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, + {file = "anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101"}, + {file = "anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94"}, ] [[package]] @@ -221,7 +259,7 @@ files = [ [[package]] name = "arclet-letoderea" -version = "0.21.3" +version = "0.22.1" requires_python = "<4.0,>=3.10" summary = "A high-performance, simple-structured event system, relies on asyncio" groups = ["default"] @@ -230,20 +268,8 @@ dependencies = [ "typing-extensions>=4.12.0", ] files = [ - {file = "arclet_letoderea-0.21.3-py3-none-any.whl", hash = "sha256:ea1e235a037a193c734fbbe754969db9242d551290b11a0c0babae4e17d56d9d"}, - {file = "arclet_letoderea-0.21.3.tar.gz", hash = "sha256:4a2588d521afcd18d356804dc4289209be07b885e1d7851c8e37c8c03b954af4"}, -] - -[[package]] -name = "async-timeout" -version = "5.0.1" -requires_python = ">=3.8" -summary = "Timeout context manager for asyncio programs" -groups = ["default"] -marker = "python_version < \"3.11\"" -files = [ - {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, - {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, + {file = "arclet_letoderea-0.22.1-py3-none-any.whl", hash = "sha256:22c6310146c537430ab62a0d6bd89a434737133d7f1c0724fdea77a83fd3e376"}, + {file = "arclet_letoderea-0.22.1.tar.gz", hash = "sha256:575482e575ba0807d65bf38e756c1270ba94fa5f2e68fbcc57b3ccd3d754f9b8"}, ] [[package]] @@ -259,7 +285,7 @@ files = [ [[package]] name = "black" -version = "26.3.1" +version = "26.10.1" requires_python = ">=3.10" summary = "The uncompromising code formatter." groups = ["dev"] @@ -274,33 +300,28 @@ dependencies = [ "typing-extensions>=4.0.1; python_version < \"3.11\"", ] files = [ - {file = "black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2"}, - {file = "black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b"}, - {file = "black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac"}, - {file = "black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a"}, - {file = "black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a"}, - {file = "black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff"}, - {file = "black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c"}, - {file = "black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5"}, - {file = "black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e"}, - {file = "black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5"}, - {file = "black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1"}, - {file = "black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f"}, - {file = "black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7"}, - {file = "black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983"}, - {file = "black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb"}, - {file = "black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54"}, - {file = "black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f"}, - {file = "black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56"}, - {file = "black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839"}, - {file = "black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2"}, - {file = "black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78"}, - {file = "black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568"}, - {file = "black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f"}, - {file = "black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c"}, - {file = "black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1"}, - {file = "black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b"}, - {file = "black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07"}, + {file = "black-26.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a0219b29cd70e49f920acb7081e6ce5025c719008447c521d0200dcad93206a"}, + {file = "black-26.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bdade400bfe24d78a7762896acc2f9a8e1a17fb0fd0536bf6b7c7097cf3eec7"}, + {file = "black-26.10.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ff57f63029aa1353fa8b1b0c8971fd88a6c92dc766608d2eee33ad2deb23270e"}, + {file = "black-26.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3414a0c52901964dceabd98c7c56beac0f964115a116ecedcce7247359b14017"}, + {file = "black-26.10.1-cp311-cp311-win_arm64.whl", hash = "sha256:1935b32f5326028019856e18cb42b4da63db23765dc84464cec723e0de478a9b"}, + {file = "black-26.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe85fc4019bee59bc495c0f2a8ee76c5cd02c7015508d94a967ba2376f39a52c"}, + {file = "black-26.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:182f6c32be38074b16d378498c498b32cb51928178ee611485344972c35ec9c6"}, + {file = "black-26.10.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b5347d760f0c02bb00dd249384cab71c3bf828b4f68d5b401eb116e0390f147d"}, + {file = "black-26.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:4d9a90516db1d99c25dbb20cc0998e0e01531dd903466c7744e56d66f864220a"}, + {file = "black-26.10.1-cp312-cp312-win_arm64.whl", hash = "sha256:2ffbc023a12d0c729408823b8f10514490bd0baa301d0d4e21a7240249f9507f"}, + {file = "black-26.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b6272cfd7e1e8e271f5b0e0207259fe2834687e5cb9b5f620b34a44db9754993"}, + {file = "black-26.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:978113a40223a6aaefc17364176a809a320e6b288683841427fff04c6d7b4130"}, + {file = "black-26.10.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03c0ddd93bb392e71209903a691767eb366fe1a76deb9509ccbaae9e1f14bb52"}, + {file = "black-26.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6dba8138cdc99061ef07b958ac082d2aa057b6961d1936f9717c350f02bab5f"}, + {file = "black-26.10.1-cp313-cp313-win_arm64.whl", hash = "sha256:d42dd2fac7c342ae67e64ee99c9532e20b2a84e92c79ed3317fa2ef54c801d93"}, + {file = "black-26.10.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:8375962579d537364cc0efa19b1474481915d3a793f9fc0774901814c5e5b5f4"}, + {file = "black-26.10.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d8b3a9074a680b3c5749633714e9ae3992a1e5a23343a97ad61cd9b119b444d2"}, + {file = "black-26.10.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:289282aa2e09d3162312a3be1788ff21b08e9ea9cc4a81e656024728b32428fb"}, + {file = "black-26.10.1-cp314-cp314-win_amd64.whl", hash = "sha256:5cd88fd7b444ca51f3fc883b6f6657ea53a258b0b2eef6d9f2dfcfa17ce0e27b"}, + {file = "black-26.10.1-cp314-cp314-win_arm64.whl", hash = "sha256:2520037aa62f8a1454d0811b8f5c88b444445b03a4bfba480d8d220893b64c34"}, + {file = "black-26.10.1-py3-none-any.whl", hash = "sha256:28842f9a8207cc1df6eb983a35a14c5a0dfcd603d214fe82d84bef552afd2e3a"}, + {file = "black-26.10.1.tar.gz", hash = "sha256:5f9f83beae62437e060dafd53d7f1fc327e3d3494f74d72ee5c2b73eb90fc4e7"}, ] [[package]] @@ -321,16 +342,13 @@ files = [ [[package]] name = "click" -version = "8.3.2" +version = "8.5.0" requires_python = ">=3.10" summary = "Composable command line interface toolkit" groups = ["dev"] -dependencies = [ - "colorama; platform_system == \"Windows\"", -] files = [ - {file = "click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d"}, - {file = "click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5"}, + {file = "click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360"}, + {file = "click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34"}, ] [[package]] @@ -360,7 +378,7 @@ files = [ [[package]] name = "croniter" -version = "6.2.2" +version = "6.2.4" requires_python = ">=3.9" summary = "croniter provides iteration for datetime object with cron like format" groups = ["cron", "full"] @@ -368,13 +386,13 @@ dependencies = [ "python-dateutil", ] files = [ - {file = "croniter-6.2.2-py3-none-any.whl", hash = "sha256:a5d17b1060974d36251ea4faf388233eca8acf0d09cbd92d35f4c4ac8f279960"}, - {file = "croniter-6.2.2.tar.gz", hash = "sha256:ba60832a5ec8e12e51b8691c3309a113d1cf6526bdf1a48150ce8ec7a532d0ab"}, + {file = "croniter-6.2.4-py3-none-any.whl", hash = "sha256:8ef3d544107a5c05a150a2d78f8bf5a8eb9c5c4d93405a736b824109574e3f4d"}, + {file = "croniter-6.2.4.tar.gz", hash = "sha256:fc124f751b1b04805c2a04b061898b436b45ab2320b045e1e052ea895de65189"}, ] [[package]] name = "entari-cli" -version = "0.4.1" +version = "0.4.2" requires_python = ">=3.9" summary = "Entari Command Line Tool, extract from arclet.entari.__main__" groups = ["cli", "full"] @@ -388,29 +406,14 @@ dependencies = [ "tomlkit>=0.13.3", ] files = [ - {file = "entari_cli-0.4.1-py3-none-any.whl", hash = "sha256:246c3ca78366b13cf06a2cb48bd7d607ade3929a31319874b600d2c636993731"}, - {file = "entari_cli-0.4.1.tar.gz", hash = "sha256:fb375eb7460bd1894d7364d3e9bf5e2de5c4ec50413a219e06fca54ffc7f9865"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -requires_python = ">=3.7" -summary = "Backport of PEP 654 (exception groups)" -groups = ["dev", "full", "reload"] -marker = "python_version < \"3.11\"" -dependencies = [ - "typing-extensions>=4.6.0; python_version < \"3.13\"", -] -files = [ - {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, - {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, + {file = "entari_cli-0.4.2-py3-none-any.whl", hash = "sha256:c14c8f418450b822152d5698774369a1af29d515c835df0dfae1731ebbd4a486"}, + {file = "entari_cli-0.4.2.tar.gz", hash = "sha256:c01043f8dcdb4c872a983e6b7b270bc72d5b52788e07e1df6cf1ca3b5f5a0ac0"}, ] [[package]] name = "findpython" -version = "0.7.1" -requires_python = ">=3.8" +version = "0.8.0" +requires_python = ">=3.9" summary = "A utility to find python versions on your system" groups = ["cli", "full"] dependencies = [ @@ -418,8 +421,8 @@ dependencies = [ "platformdirs>=4.3.6", ] files = [ - {file = "findpython-0.7.1-py3-none-any.whl", hash = "sha256:1b78b1ff6e886cbddeffe80f8ecdbf2b8b061169bbd18b673070e26b644c51ac"}, - {file = "findpython-0.7.1.tar.gz", hash = "sha256:9f29e6a3dabdb75f2b39c949772c0ed26eab15308006669f3478cdab0d867c78"}, + {file = "findpython-0.8.0-py3-none-any.whl", hash = "sha256:4a61ee1618a8b55014f7d41f59345d322be93f6ce62395bdccccc651b3f7e28a"}, + {file = "findpython-0.8.0.tar.gz", hash = "sha256:53b32264874dfa5990bd09d717819386d8db3149d89fe20f88fe1078de286bae"}, ] [[package]] @@ -429,22 +432,6 @@ requires_python = ">=3.9" summary = "A list-like structure which implements collections.abc.MutableSequence" groups = ["default"] files = [ - {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, - {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, - {file = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"}, - {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"}, - {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"}, - {file = "frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"}, - {file = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"}, - {file = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"}, {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"}, {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"}, {file = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"}, @@ -547,7 +534,7 @@ files = [ [[package]] name = "graia-amnesia" -version = "0.11.5" +version = "0.11.6" requires_python = "<4.0,>=3.10" summary = "a collection of shared components for graia" groups = ["default"] @@ -556,24 +543,24 @@ dependencies = [ "typing-extensions>=4.0.0", ] files = [ - {file = "graia_amnesia-0.11.5-py3-none-any.whl", hash = "sha256:454e58ac34a08d256a83b98bee44eaa64378d5438572914ebf15a00b25f7e67f"}, - {file = "graia_amnesia-0.11.5.tar.gz", hash = "sha256:5cb3ff867205e1ee40dbb71499eb55a1d1475a642d77abddd732b2feb30d8350"}, + {file = "graia_amnesia-0.11.6-py3-none-any.whl", hash = "sha256:af678c15f21e8a5e209fd7925af6366ac340d1e7cddee17abd40eef37eafb2a8"}, + {file = "graia_amnesia-0.11.6.tar.gz", hash = "sha256:56969bdcdc29b96a76a253d4330f57e9e4cf120eac3b4661ec1eb09862b522b7"}, ] [[package]] name = "idna" -version = "3.11" -requires_python = ">=3.8" +version = "3.20" +requires_python = ">=3.9" summary = "Internationalized Domain Names in Applications (IDNA)" groups = ["default", "dev", "full", "reload"] files = [ - {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, - {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, + {file = "idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c"}, + {file = "idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44"}, ] [[package]] name = "importlib-metadata" -version = "9.0.0" +version = "9.0.1" requires_python = ">=3.10" summary = "Read metadata from Python packages" groups = ["default", "cli", "full"] @@ -581,8 +568,8 @@ dependencies = [ "zipp>=3.20", ] files = [ - {file = "importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7"}, - {file = "importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc"}, + {file = "importlib_metadata-9.0.1-py3-none-any.whl", hash = "sha256:bba5600596a7e21f3eef53281cf28d6a5195634d2f2b78ff9501a3272c6eaab0"}, + {file = "importlib_metadata-9.0.1.tar.gz", hash = "sha256:ab830580bc0ef3db61ce8fae716389e5462b67e033018bab6d8f80ef17172f99"}, ] [[package]] @@ -619,7 +606,7 @@ files = [ [[package]] name = "markdown-it-py" -version = "4.0.0" +version = "4.2.0" requires_python = ">=3.10" summary = "Python port of markdown-it. Markdown parsing, done right!" groups = ["dev", "rich"] @@ -627,8 +614,8 @@ dependencies = [ "mdurl~=0.1", ] files = [ - {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, - {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, + {file = "markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a"}, + {file = "markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49"}, ] [[package]] @@ -644,200 +631,246 @@ files = [ [[package]] name = "msgspec" -version = "0.21.1" +version = "0.22.0" requires_python = ">=3.10" summary = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML." groups = ["dev", "msgspec"] files = [ - {file = "msgspec-0.21.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72d9cd03241b8b2edb2e12dcc66c500fa480d8cbd71a8bac105809d468882064"}, - {file = "msgspec-0.21.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed2ab278200e743a1d2610a4e0c8fc74f6cecb8548544cdec43f927bd9265238"}, - {file = "msgspec-0.21.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd677e3001fdfed9186de72eab434da2976303cd5eb9550921d3d0c3e3e168ce"}, - {file = "msgspec-0.21.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f667b90b37fad734a91671abd68e0d7f4d066862771b87e91c53996dcb7a9027"}, - {file = "msgspec-0.21.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:49880fd20fdbcfe1b793f07dd83f12572bab679c9800352c8b2240289aa46a06"}, - {file = "msgspec-0.21.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae0162e22849a5e91eaad907766525107523b0daea3df267a9fcb5ba4e0936ae"}, - {file = "msgspec-0.21.1-cp310-cp310-win_amd64.whl", hash = "sha256:f041a2279f31e3a53319005e4d60ba77c085cfcbe394cdc7ce803c2d01fe9449"}, - {file = "msgspec-0.21.1-cp310-cp310-win_arm64.whl", hash = "sha256:1bf17cbd7b28a5dffc7e764c654eed8ccde5e0f1de7970628608304640d4ce4e"}, - {file = "msgspec-0.21.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b504b6e7f7a22a24b27232b73034421692147865162daaec9f3bf62439007c87"}, - {file = "msgspec-0.21.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4692b7c1609155708c4418f88e92f63c13fdf08aa095c84bae82bad75b53389b"}, - {file = "msgspec-0.21.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3124010b3815451494c85ff345e693cb9fe5889cfcbbef39ed8622e0e72319c"}, - {file = "msgspec-0.21.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6badc03b9725352219cca017bfe71c61f2fbd0fb5982b410ac17c97c213deb30"}, - {file = "msgspec-0.21.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5d2d4116ebe3035a78d9ec76e99a9d64e5fa6d44fe61a9c5de7fd1acf54bcc69"}, - {file = "msgspec-0.21.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0d1009f6715f5bff3b54d4ff5c7428ad96197e0534e1645b8e9b955890c84664"}, - {file = "msgspec-0.21.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6faffe5bb644ec884052679af4dfd776d4b5ca90e4a7ec7e7e319e4e6b93a6e"}, - {file = "msgspec-0.21.1-cp311-cp311-win_arm64.whl", hash = "sha256:ee9e3f11fa94603f7d673bf795cfa31b549c4a2c723bc39b45beb1e7f5a3fb99"}, - {file = "msgspec-0.21.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4248cf0b6129b7d230eacd493c17cc2d4f3989f3bb7f633a928a85b7dcfa251"}, - {file = "msgspec-0.21.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5102c7e9b3acff82178449b85006d96310e690291bb1ea0142f1b24bcb8aabcb"}, - {file = "msgspec-0.21.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:846758412e9518252b2ac9bffd6f0e54d9ff614f5f9488df7749f81ff5c80920"}, - {file = "msgspec-0.21.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21995e74b5c598c2e004110ad66ec7f1b8c20bf2bcf3b2de8fd9a3094422d3ff"}, - {file = "msgspec-0.21.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6129f0cca52992e898fd5344187f7c8127b63d810b2fd73e36fca73b4c6475ee"}, - {file = "msgspec-0.21.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ef3ec2296248d1f8b9231acb051b6d471dfde8f21819e86c9adaaa9f42918521"}, - {file = "msgspec-0.21.1-cp312-cp312-win_amd64.whl", hash = "sha256:d4ab834a054c6f0cbeef6df9e7e1b33d5f1bc7b86dea1d2fd7cad003873e783d"}, - {file = "msgspec-0.21.1-cp312-cp312-win_arm64.whl", hash = "sha256:628aaa35c74950a8c59da330d7e98917e1c7188f983745782027748ee4ca573e"}, - {file = "msgspec-0.21.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:764173717a01743f007e9f74520ed281f24672c604514f7d76c1c3a10e8edb66"}, - {file = "msgspec-0.21.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:344c7cd0eaed1fb81d7959f99100ef71ec9b536881a376f11b9a6c4803365697"}, - {file = "msgspec-0.21.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48943e278b3854c2f89f955ddc6f9f430d3f0784b16e47d10604ee0463cd21f5"}, - {file = "msgspec-0.21.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9aa659ebb0101b1cbc31461212b87e341d961f0ab0772aaf068a99e001ec4aa"}, - {file = "msgspec-0.21.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7b27d1a8ead2b6f5b0c4f2d07b8be1ccfcc041c8a0e704781edebe3ae13c484"}, - {file = "msgspec-0.21.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38fe93e86b61328fe544cb7fd871fad5a27c8734bfda90f65e5dbe288ae50f61"}, - {file = "msgspec-0.21.1-cp313-cp313-win_amd64.whl", hash = "sha256:8bc666331c35fcce05a7cd2d6221adbe0f6058f8e750711413d22793c080ac6a"}, - {file = "msgspec-0.21.1-cp313-cp313-win_arm64.whl", hash = "sha256:42bb1241e0750c1a4346f2aa84db26c5ffd99a4eb3a954927d9f149ff2f42898"}, - {file = "msgspec-0.21.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fab48eb45fdbfbdb2c0edfec00ffc53b6b6085beefc6b50b61e01659f9f8757f"}, - {file = "msgspec-0.21.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3cb779ea0c35bc807ff941d415875c1f69ca0be91a2e907ab99a171811d86a9a"}, - {file = "msgspec-0.21.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68604db36b3b4dd9bf160e436e12798a4738848144cea1aca1cb984011eb160f"}, - {file = "msgspec-0.21.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d6b9dc50948eaf65df54d2fd0ff66e6d8c32f116037209ee861810eb9b676cb"}, - {file = "msgspec-0.21.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:52c5e21930942302394429c5a582ce7e6b62c7f983b3760834c2ce107e0dd6df"}, - {file = "msgspec-0.21.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:abbb39d65681fa24ed394e01af3d59d869068324f900c61d06062b7fb9980f2f"}, - {file = "msgspec-0.21.1-cp314-cp314-win_amd64.whl", hash = "sha256:5666b1b560b97b6ec2eb3fca8a502298ebac56e13bbca1f88523538ce83d01ea"}, - {file = "msgspec-0.21.1-cp314-cp314-win_arm64.whl", hash = "sha256:d8b8578e4c83b14ceea4cef0d0b747e31d9330fe4b03b2b2ad4063866a178f93"}, - {file = "msgspec-0.21.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15f523d51c00ebad412213bfe9f06f0a50ec2b93e0c19e824a2d267cabb48ea2"}, - {file = "msgspec-0.21.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e47390360583ba3d5c6cb44cf0a9f61b0a06a899d3c2c00627cedebb2e2884b"}, - {file = "msgspec-0.21.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f60800e6299b798142dc40b0644da77ceac5ea0568be58228417eae14135c847"}, - {file = "msgspec-0.21.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5f8e9dfcd98419cf7568808470c4317a3fb30bef0e3715b568730a2b272a20d7"}, - {file = "msgspec-0.21.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92d89dfad13bd1ea640dc3e37e724ed380da1030b272bdf5ecafb983c3ad7c75"}, - {file = "msgspec-0.21.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0d03867786e5d7ba25d666df4b11320c27170f4aeafcb8e3a8b0a50a4fb742ca"}, - {file = "msgspec-0.21.1-cp314-cp314t-win_amd64.whl", hash = "sha256:740fbf1c9d59992ca3537d6fbe9ebbf9eaf726a65fbf31448e0ecbc710697a63"}, - {file = "msgspec-0.21.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0d2cc73df6058d811a126ac3a8ad63a4dfa210c82f9cf5a004802eaf4712de90"}, - {file = "msgspec-0.21.1.tar.gz", hash = "sha256:2313508e394b0d208f8f56892ca9b2799e2561329de9763b19619595a6c0f72c"}, + {file = "msgspec-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb1e129b81ac8fcf9ec649b081c6c8da1c7ea6f87cab336d46386abc2cd855c1"}, + {file = "msgspec-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dce29a04966e31abf9b83b697c6d672486526dc5d03fcd6970cb56d5dc1fbeea"}, + {file = "msgspec-0.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b962000e11dd34fb210a5a2c57a8a62b2d92b381c8cb3b05c075a83e38f8d645"}, + {file = "msgspec-0.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6db3806b3b76ca78064255eac6fa101a8a64fe6f698d80fbaf81fdfa21217d4"}, + {file = "msgspec-0.22.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a88d939d3fe4b8c7314645ebcd6e86c8c8a512ea7820d6550355973e803bc0f1"}, + {file = "msgspec-0.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b31746da07cba0e330c6433a94a4699ad77d3aeb9638d1a320a7686b69f6249"}, + {file = "msgspec-0.22.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:6ae370f92f3517f0e6f209ba7cc649c957b444868439197e046be07154667551"}, + {file = "msgspec-0.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a696f23f7c1ffb31fae308502e01a3965c3891d5c400f01d0d1096dbe77519e"}, + {file = "msgspec-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:024138c51afd335d0b4dce401be33902caafac2b64f8c9f2509a378986175d98"}, + {file = "msgspec-0.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:4600dbec738ed74e4c9bd35503e84701200ea7db344cfdeda80677b3ee53eb64"}, + {file = "msgspec-0.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab1e9e7531e353653b906cdd12a0220cc288a1e8e3436aabc65f4508d91b14d9"}, + {file = "msgspec-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b60b43425a47eb9cfe987f6874e354ca7c760e58e295b4e2273ff03574df28a1"}, + {file = "msgspec-0.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5a169b5b03f0f2c7a296c002647db1dab75d2cd501bca34e32b71cab0261b56"}, + {file = "msgspec-0.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c401861c5bb3a57f7d6423ea7ed4352cd57aa3f04f4fbe9f3e3e4564a10f08"}, + {file = "msgspec-0.22.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08826f5e5b0fa2f7a88592c396a243cfcc63d37e19f9d4fbe3b3f1be2fbdc404"}, + {file = "msgspec-0.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21460f54cee9208239b1a8421fdf25bffc77293e1daba88f585711ad839b9758"}, + {file = "msgspec-0.22.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cfc3d9557de9c806318725b702f3e664db33167bb42892079b693c69893fd33b"}, + {file = "msgspec-0.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0b25dcbc108783cb72503ed705b9fbb8c3cb02ee5801923f44b5f038c91cc365"}, + {file = "msgspec-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:6ad64f5c260866b0d543f89f50cee43628989c1433c5de7ce820281fa28a2611"}, + {file = "msgspec-0.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:0922714feff5300aacd8ecd65fa828317ce4bf5212b3139258c0bfc0253cd80e"}, + {file = "msgspec-0.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f13c127a945479bc9db057eb253b8851075c8e1ae07ffc967bfa1c5676203a86"}, + {file = "msgspec-0.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5aa24eb475d070ecbbe5b21080fc3ce4b0b76c60de25cfe0c9678d8fb44bb42f"}, + {file = "msgspec-0.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:627bfdfe5a4b3d916b3360b30f4cddeee3a084f56593e33527c6872fa8322ff9"}, + {file = "msgspec-0.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6c310ef83e7e291b01a63298828f848348bb99e84a1098c4b3923c05674d032"}, + {file = "msgspec-0.22.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c1e76c6bd523141b9c05c2f8a70979cd0efedbd68855a66f292f8892c0b8fc7"}, + {file = "msgspec-0.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc374dedd5f85a5f4de2386dc5f737894ccb8c1ac18e9566ce66fd9839e6285d"}, + {file = "msgspec-0.22.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:feafe612034d49e9144340c0b5168ee4e22c2af4aaa2c1db11ae84e1aac9543b"}, + {file = "msgspec-0.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f48317f05312bfdf78248f53933f830f07ab75cc1c813ac3ca4220cb3b5b019"}, + {file = "msgspec-0.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:0739b068f31f2004a364f97679ba91f2f5ecd6ec2a5b4b890188ab5c57d20672"}, + {file = "msgspec-0.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:508278300dd4efbd21cd3a4b2b016160a5feac98bc880d3673f6c06697baaf62"}, + {file = "msgspec-0.22.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:221cbcbfa4478152b91d37dcfd4830e2be92773e8139e883f43773450ebacef8"}, + {file = "msgspec-0.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dd9568695911055440d2bb7099ed9098fc181d335daa772d0eb3fe8f31ba4efb"}, + {file = "msgspec-0.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f039ef5207b847f075a0a43020ee6140cd47505f890e47e157f2deb485c2dc96"}, + {file = "msgspec-0.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4f7e09cceac7dbf4c0761b8ae7df51c55b5df5e9af7aff2c895aac1ebea015"}, + {file = "msgspec-0.22.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:614e2c827e0a3f934f3cf0cf4ba65210df8132b75a69a8a1f51bb3b2caf0ac5a"}, + {file = "msgspec-0.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3689b9dfcc663358ef23ba4299d7460f01108515b041a7d30d05908ac9c32f"}, + {file = "msgspec-0.22.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d2f950239ff1fc7322c6f9634807310265149cb168270d3ddcdda5b6ada13a28"}, + {file = "msgspec-0.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c789b5ccd07c0a3c09767108ee06e089b2875f2309a4569c2648f30a8d31dfa"}, + {file = "msgspec-0.22.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:a66b1766311e42371e509c996c3933b161c7ae0eabdf361af5316dec197e1022"}, + {file = "msgspec-0.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:749899563d26b211379f142b8ffd7e2d7da149a51717798f0ce994dce50324f0"}, + {file = "msgspec-0.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:10d0d1d464960d99a949f7ca01ef8928e51c472433a5f5ab74b2d695fb830652"}, + {file = "msgspec-0.22.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e79725246291516a7359caad5fb743ddc0ec66ed40d2381fb846325b5031504e"}, + {file = "msgspec-0.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:38f7022fbe91954b31afe3888a0af1b652e0f370fafdeb1d425f4a814d789c9f"}, + {file = "msgspec-0.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6d3ca19a8ff28d0a67a1824e2bff7ec649ec795c80a265f20ade4caa63080de"}, + {file = "msgspec-0.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8b98ae215a102cbf6635f7df45f5c4af12f77fad1f7b71b9808fcf868a5735d"}, + {file = "msgspec-0.22.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0aa0cc3f18c35bab79bd7b87fde95d6274a9deddeebd1ea541f8066a5073165"}, + {file = "msgspec-0.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c8e84789918fbc15a503b92a829115ddd7567ecd3e4778bd418c56abbb86c11"}, + {file = "msgspec-0.22.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:3ca7d4cd69fbb66bd2da6211d3e79d40542d196c16c6d99bf838f76767ad35be"}, + {file = "msgspec-0.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:28f53f3604dd3e70225f7563c831628dbb03299b428f8e62aadb4b628e386874"}, + {file = "msgspec-0.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7293dee54de040cfa225c22151cc3d72f17cd674b5ebcb52f38fb9f5701592e6"}, + {file = "msgspec-0.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c3c510aba9015c085e514b75a9b3f1ed7c4591ae5e379655821b8bba51f30cc7"}, + {file = "msgspec-0.22.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:263e110955ed76fe0af2d79f819903b50a70dc0e7a752eb7aabe79d2e0a084fb"}, + {file = "msgspec-0.22.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:c6f06576eced70462179a4b4638e84cf69fdbba37f44d13a64a21739c131a830"}, + {file = "msgspec-0.22.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d67582478b0eaabb899f2fb255c878ee7de57dff80eb73ab24f1865524ec441"}, + {file = "msgspec-0.22.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71cbbdb39631064e2f2f9e9ac2b1b69931d72276eb5f9da4ed025726296bdbb6"}, + {file = "msgspec-0.22.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f0a5c25516e2034b2db7767081759ff8996e214def9c43b3055f61e1be1caad"}, + {file = "msgspec-0.22.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:a1dab6a99c759d1391ab2993388c1892746a697254f4b5dc6c059ca6e3bfbc8b"}, + {file = "msgspec-0.22.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:a52eba5c9528fd181fcec39d22b67aaa1dccc6cfe8e24d3f5d41130e6d04289d"}, + {file = "msgspec-0.22.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1e547966017265c0d23342bcf2e027305dde40ea042d16694a9b96b4f696a052"}, + {file = "msgspec-0.22.0-cp315-cp315-win_amd64.whl", hash = "sha256:0067057df265795f742658b15dbe53f3b6f21d19dcfa53676db11088cfa41e0a"}, + {file = "msgspec-0.22.0-cp315-cp315-win_arm64.whl", hash = "sha256:05dbc8268e50c9232ec72b9af1c7b13049aade4d1197764e38c427048706e046"}, + {file = "msgspec-0.22.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:b3113ebcceeb7693a915183c73d92c10bf5c62851dd187cab43bd025fb587419"}, + {file = "msgspec-0.22.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0dfadea8bdcfafc614bd031de55a8ede22b43445cfff6d8b77cc0c07d3edc8a8"}, + {file = "msgspec-0.22.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7a738826936c72348c613061d260446f13c82b6fd7d5d7705b6911ab8dca2f3"}, + {file = "msgspec-0.22.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2ddea9d78d09460f06c26a7a508adcd049761c3208776162b8eb79b8a032cff"}, + {file = "msgspec-0.22.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:884c28c80b0a511595b29a9b04a3a230c3797369e4a033e6d5c6d9b5427f8e09"}, + {file = "msgspec-0.22.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f7a923bcde480065c8e25967464cfb2a687ee67000bb43157e2d57e40eca7305"}, + {file = "msgspec-0.22.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:65eea14bc65ccfeb8f3af62cb204841871e2961f002d7fa87dbe0f79dacf1c1c"}, + {file = "msgspec-0.22.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:0666a1520cab86796612e794e71107e0fbf5e8ff3ddcdfcfff8f1d94b860d2f1"}, + {file = "msgspec-0.22.0-cp315-cp315t-win_amd64.whl", hash = "sha256:885c6e0c89d6103648525fe62aa78d600054dedf7b3713d23b15d7ddb6d66a13"}, + {file = "msgspec-0.22.0-cp315-cp315t-win_arm64.whl", hash = "sha256:268594d0bae5510572599a6ab0364dd9de43c867d24a30856cd9f5edb63d8dc6"}, + {file = "msgspec-0.22.0.tar.gz", hash = "sha256:0a13624a4969159fe35d8c2a3d377b2b61bbd8585e327440d5e52725affcce38"}, ] [[package]] name = "multidict" -version = "6.7.1" -requires_python = ">=3.9" +version = "7.1.0" +requires_python = ">=3.10" summary = "multidict implementation" groups = ["default"] dependencies = [ "typing-extensions>=4.1.0; python_version < \"3.11\"", ] files = [ - {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, - {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, - {file = "multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505"}, - {file = "multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122"}, - {file = "multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df"}, - {file = "multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db"}, - {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d"}, - {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e"}, - {file = "multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa"}, - {file = "multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a"}, - {file = "multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b"}, - {file = "multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6"}, - {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172"}, - {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd"}, - {file = "multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba"}, - {file = "multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511"}, - {file = "multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19"}, - {file = "multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf"}, - {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23"}, - {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2"}, - {file = "multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33"}, - {file = "multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3"}, - {file = "multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5"}, - {file = "multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df"}, - {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1"}, - {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963"}, - {file = "multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108"}, - {file = "multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32"}, - {file = "multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8"}, - {file = "multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118"}, - {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee"}, - {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2"}, - {file = "multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b"}, - {file = "multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d"}, - {file = "multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f"}, - {file = "multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5"}, - {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581"}, - {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a"}, - {file = "multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2"}, - {file = "multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7"}, - {file = "multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5"}, - {file = "multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2"}, - {file = "multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56"}, - {file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"}, + {file = "multidict-7.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:41ff3202cc23c800507777df5a4805b402f262b31008c60fdc652aeb6db2f278"}, + {file = "multidict-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e50f7775b66c7802f4cb697e986c5acf30ec07301efee95b396c08114e890d67"}, + {file = "multidict-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85cb3ced4fa84949cee12bfe78208b6ece7baf3cbd242b26dcaf773efff8d206"}, + {file = "multidict-7.1.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2128f3358335e0c83688ecb40c19d9d6606cd60784dfbf2e24e980ac2ba87b0d"}, + {file = "multidict-7.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2e718fa9d1d900decbc240a533d5d0baf0947ef464c78a8cd4fa32b4e8f590c"}, + {file = "multidict-7.1.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ecc68f5e47bc6f6f889bbed5bc657b22bb2237ad9ccab8229cb5a0d64f4cb536"}, + {file = "multidict-7.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5f21fda91bd6c34455bd5c312e42aa1334da46cdafb4c533ecd01e0f7f19250b"}, + {file = "multidict-7.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a90453a79423cd7145cc08fc92322dcd7aca4862258f533e03f473226d4b835"}, + {file = "multidict-7.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c54ae1b89e582aa25f213cd8b5eac0bda1724e79299f486baeb3f562bbf82ca5"}, + {file = "multidict-7.1.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ff8dd079e7b5f3438332499233a2a5acfca0741fd0eb3d4ddba0c2d9bc04d19"}, + {file = "multidict-7.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e4ef15d0a29fc2da67fe8ba2301ecabd6f8733696cc2bf0a0cf96a144a20328c"}, + {file = "multidict-7.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5fa296f14068538fced53c6eec86520a2ef3d3d27a0fb134640d03e067986d5f"}, + {file = "multidict-7.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6ab323f0c5490abaf35a78563e1043c7a772eb86d93f359ecc0fd286d1cd3807"}, + {file = "multidict-7.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:88ec4d16e9f58071c9896ea01c4da97cce9d01418fe844ff06eebb00e0a1386a"}, + {file = "multidict-7.1.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:9161eb81b8062da824426d3700d4b0d287f0cb0b05923713adfe3bd25e7937ac"}, + {file = "multidict-7.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c564d0758748f38aec56a6b98c6801a427b3a63f39b7cac538b2b2d18ca32740"}, + {file = "multidict-7.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c5e4a362a95b85301d262ef6bed06cc8e4a144ac7e2be874cb4c3c46ae89d754"}, + {file = "multidict-7.1.0-cp311-cp311-win32.whl", hash = "sha256:5d19bb1ec12e385c09215d5d53a243c060c7e8a0aacdba16d933e22902ee380d"}, + {file = "multidict-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:396ba9917fe489ec3a5942ae3e29e91324c8b9956f371f7e124c971c71379e7a"}, + {file = "multidict-7.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5ed78742502b8d90ff2816688d407a097c8b5cc6af4343fc5ad7a98df53a7cd"}, + {file = "multidict-7.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ccfb950359a80de0fcd2030ad60ac1b1a861462de3e2ef746697c9256659af21"}, + {file = "multidict-7.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:939d8cd2d8c35e3956f6bc858390b6ccb611e6152b4920d64ab5e98f3fcf39e4"}, + {file = "multidict-7.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f8e95c95039eab6a2dad8c83c38ab87fc5431d28849e0c8a7e2a4e70ba38710d"}, + {file = "multidict-7.1.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:05d12b4bac53abe0c65f3163af2b45894e2e1c0cc55493ac784d52a350047d88"}, + {file = "multidict-7.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e79ed92b1dece6bb57e9b46effd74d7a5d3d00187c85466d880ed184239a698"}, + {file = "multidict-7.1.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fab380fcff8b3555eb2bd04304fa4330909a771a9a9b0dc07666cfc23148a711"}, + {file = "multidict-7.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b5c41e44da74383c924cc5d75ef0a268f301d69305b3c42bd17af685d55e412"}, + {file = "multidict-7.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dbaa7f7c2f0ca8578895fc61fb8c8e50ebb405dad8982f92f4343285c7a3fda"}, + {file = "multidict-7.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed6b7705d49dd07e5e0dd5f5c873fc44047e92d714299b13245b5fecac49d01"}, + {file = "multidict-7.1.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53daa47dd176db64bb35170e3d5d0ae2388c060121201883696278f055a0e70c"}, + {file = "multidict-7.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:542429c796430de924d03b68a6173bb6d79d5c4967d4e9a18de3e501cad55593"}, + {file = "multidict-7.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c44ca6d3cdf4cfcbcd4f928fdcbe87af5fd7319f6ad4169617b7fd6b4527c33c"}, + {file = "multidict-7.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eb0228c809b2e7eb47921876050af0bc4214b351bad8d8112f70b6ed4288763c"}, + {file = "multidict-7.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6ad60de1f4c702448fc8f1449f05e810f6b7957c08a5b3950c8a792dfb13b50a"}, + {file = "multidict-7.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f79def86aee67b5ba01b2565f1610f262bf88ae53c379f93e5fa29c50fe793be"}, + {file = "multidict-7.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:248dabb89b5aa90b2f7e43e045f048f7e5392ec77b6446d80853ba7117d7bbdf"}, + {file = "multidict-7.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0747a83e7ae617793181a4763ee8b84863cec5c0bbbde70c4394e4c0276c36de"}, + {file = "multidict-7.1.0-cp312-cp312-win32.whl", hash = "sha256:1df055e51fe7491120cc84f3362bd43db186be78d0e4c476acad45e435af9ffb"}, + {file = "multidict-7.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:10202ba98cfb3f7eb60da7ca87a2c458a69b7d0d6e4d4388cd6773ebbce89085"}, + {file = "multidict-7.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0aa1ba3ff7cdda05a1242490612976b2ae1c90fc6200903ef8f53815dcb35c5d"}, + {file = "multidict-7.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:51d7f33be9a4a1a2801430846d72841deea0894eae8381a07e7d90e0f71b3c4b"}, + {file = "multidict-7.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2a964dfeb2aba3663f0536c809aa1ff385f065e89fae57e883fb7edfb4067c2f"}, + {file = "multidict-7.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d02cd23b5af182a49d635ee72be38053767711987a9fd82625b16b93828a0d8c"}, + {file = "multidict-7.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0179698c3c913eb64f32397083747fad20ed0f0a2b7469a08cd1a8a95d14d90e"}, + {file = "multidict-7.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ccf98ee859fe29f874ddd8e637f14ba59108a333492b521acb885a9095244a9c"}, + {file = "multidict-7.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5fa1484f74d011addf2e5f5a0378ec41521989839a05d6051d8067d8ce732423"}, + {file = "multidict-7.1.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1fed3d721f75c25a9fcdd0e362af53f4b20acbcdc63081112f85419ba0ce3444"}, + {file = "multidict-7.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df03e392cae1e05462918abbae06d6100f1e53f67db971ff0ac6c07d9edf7321"}, + {file = "multidict-7.1.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:379f477b98a1e9a77ddc3ccaa8c709d3fb4a288ff54b96e171e637b55b4adbae"}, + {file = "multidict-7.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:40f586bc8a084a3671ddcae9e5fbd3228a596bfb63d9f0380f153f9a65b69f08"}, + {file = "multidict-7.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6e7f70d912a589e30290ed926f90ddbc3160998359cbad7c9ede1bcee481748c"}, + {file = "multidict-7.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5129cc1f5fec6888e2db0be936dab67242e32c738811c8769aeea93aab4257a8"}, + {file = "multidict-7.1.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1401caec21fd7f002e79ab6806bbfd1f54bb3de6d5e12bd91c6685dce16ad2be"}, + {file = "multidict-7.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c81062e947f4b5a624135a843f6ac4b3c7fe6508300c9fb27347f022ba0c513d"}, + {file = "multidict-7.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c53be0dd676484a660acc56e4f1cd0dd74bc1255d12fa285e86a3fa9d5f22bf9"}, + {file = "multidict-7.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:41e0c3350d08994ee8640c39884e16514e282f70ba40f5b2299582509a327774"}, + {file = "multidict-7.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:abeec7a89d698aa1c9b4c36bd5e3c746faef0867076e6a2ca27fa5077c4ece26"}, + {file = "multidict-7.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:23f6d325241b0db006ca2841309ed17622137e134930a740a8f1331ec4404791"}, + {file = "multidict-7.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:034b0dc1b7fb8279599c5d8563f86abb4d2454735b06544ecab23c54572ad2bd"}, + {file = "multidict-7.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c8074ad4d67067c87bd0663dfda654f786336078c8fd7d2f6c1aa41de8494cc"}, + {file = "multidict-7.1.0-cp313-cp313-win32.whl", hash = "sha256:7b25c335fc53acf29d4d21dbc19fe39d2824201cdda0448623152cc5917bd259"}, + {file = "multidict-7.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:7de54b49e6da811b0321e412d14efdaa1ee0c0b6609296ea5b9022bc5b2bd843"}, + {file = "multidict-7.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:aeba2c750102051aa51e087c2ccbc79f2724a41c94168f8731e36f54c453551a"}, + {file = "multidict-7.1.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:128ea4142f81a79d430f3d0eb55206093e5eda03a12abbc7b03c34748ff6116b"}, + {file = "multidict-7.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:439a19f7fbbff232ce96682c57e27030b8ac3a4b8121484c94f04bf99d08bfff"}, + {file = "multidict-7.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8090c35199d6b7bc6426bb8bdaf341e64f295cc2624a1fda7860c0837f1acc03"}, + {file = "multidict-7.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b7cc5333fcbfb27327d12612ed72322f221b61c2b69deb1155078c964f86e1a1"}, + {file = "multidict-7.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b0e0040b0d8dd89bd0af9ab18901981e344ffba68bb30b8eabb4eab6c303279b"}, + {file = "multidict-7.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:33376418ab2846b931a72b36cfa16810befc4f49485d0b3f4dc054a4d6d00038"}, + {file = "multidict-7.1.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1101aea5c3eb1d26e090b931c693488af0db9f3d52e68be8d4cdd807dad9841d"}, + {file = "multidict-7.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f996b19ac89e0dae65821ce65f788619e4286f78c62d005ecd3b75b5d9c0892b"}, + {file = "multidict-7.1.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e0d91a4bcb59ac0d7af0d8e0da737332e1b7fe6831e53e47819b1b5349d431b2"}, + {file = "multidict-7.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e96d67914ddbf5466e4476a1cd7ff30a332cbab85ed895207acc3e58c979b6a7"}, + {file = "multidict-7.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:170ba61761f59ab92afcc86ce5534a3f3d0b07c38b339b950a83213f22dd86ec"}, + {file = "multidict-7.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33389fe084e5426d9fd85d7d9ca91a29cd0d88a83c7c96e411aca49a3f9967bc"}, + {file = "multidict-7.1.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77024596b9046572c4e90b34c1ff212346756dc48933f90c53cf6e233660788d"}, + {file = "multidict-7.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ff15531a376dc6f35984443fd1429e4b150c36ce27633e7cc52a9e5318546e20"}, + {file = "multidict-7.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:544f2642a456fa264614e975d921540ee8c3b368b04d5aa1ddbec33241b13e08"}, + {file = "multidict-7.1.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08834fb8b20e1a985c70e8380a10940234b4162de62694458727330376e58b33"}, + {file = "multidict-7.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b4908e17867930b7ac77f89a18dc67308c67c511f037d8580489be86fb585912"}, + {file = "multidict-7.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:9b24e1f93b9b586ec03bc7bea1bf021ec90bf2528c729195028a3ca1c266b3f9"}, + {file = "multidict-7.1.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5c93473d0d7cd9bbb370973a9679a62f381c7050d7dff4ad6aaa92e8650f5a79"}, + {file = "multidict-7.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bf14cfcc30b097583d698a6e2b8b68c9bcffab277c485d481881958360c2938d"}, + {file = "multidict-7.1.0-cp314-cp314-win32.whl", hash = "sha256:86bc779a0896e59e4be30a5be5cd6eeffd0b40b6f0e75e730218736b7bfc6f5c"}, + {file = "multidict-7.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:6c9fd50f636a8fa9cb6324cd3eac962fec2bc5bb432452a3b583583a1059acfc"}, + {file = "multidict-7.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:e6906aa4bc62cde2c8aeb8a99a7b4401b241e274ae7b11df67d863d61ab3d5de"}, + {file = "multidict-7.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fabfdd4cf97db033196b51af46b8a681d4785c2a66347f2a5af1b4bbb1182629"}, + {file = "multidict-7.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a177a0ee5cf19931dcaeb3f662bc562754cfa4f4ace2351d9da24a954ef7db94"}, + {file = "multidict-7.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0ae91de396d5c4ac97cb24dbada3d5c91a51454781e0a70476b008f4e879e4f0"}, + {file = "multidict-7.1.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:160bdb3520fdadcaa21e1b98aab2e011265070814ecab3804eb61674becbd400"}, + {file = "multidict-7.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c2144785e42527404bbd5cfd11981fee4abe59a22aded0e498eb711a831d3f3"}, + {file = "multidict-7.1.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7d0b4fec6a8d02d7e95de5cfa913261820f1ce04bd4c0381924de0da523179b8"}, + {file = "multidict-7.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:943a9bce22180ad0f4d32d1b402a0949a4ecfe5a1257b47f54a1b51981d81b86"}, + {file = "multidict-7.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f76ceb623f7ff50df46ac57e1587c479d87a5766319c4f43d0c0a5158896afab"}, + {file = "multidict-7.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98beff85392ce435b28a0971ec21cade61ce8be8b632c9d855475a28ef92d31a"}, + {file = "multidict-7.1.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:71196ebb8d523148e5975396a444de02367f204b53b14e26794c96b2be0ed742"}, + {file = "multidict-7.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:35534b366410a36bb3d6f788691e37a76e4d1da48326b0ada3e5032580dd76af"}, + {file = "multidict-7.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b4674b12701c3fcbdf7f88b9e4479701c93bec5da9eb576140d5fcc0092990af"}, + {file = "multidict-7.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ead852a5e906a43fcb6784eeac480f6a67919a51d480c1f80d32ddf9d615475"}, + {file = "multidict-7.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:afe36ca503c2ffe30fb6df82b20389fa3c4035b5d65888a61310921cf3ae91c5"}, + {file = "multidict-7.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a5f0bebb10aae010d3c9ee3abaf83ab2069c718457aea09c15532355dd7e061f"}, + {file = "multidict-7.1.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:b9d9b7d72975521434368fe8aed3f6b522060bf271adabaa5ca6c87c0c08e168"}, + {file = "multidict-7.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:274023bf952f849e0d05eba28a4c1f65f9796430d2b09ec16539386c0f76554c"}, + {file = "multidict-7.1.0-cp314-cp314t-win32.whl", hash = "sha256:7e0bfa161df365ba3c88899ee3b7c94755200967284bdedef8c1b8b43e2c0f2b"}, + {file = "multidict-7.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:34a35be8fb82d37087e8176aba907b9459f03d0e293c80f574c6337a436f4eaa"}, + {file = "multidict-7.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:d7dd46a8fcd7653c09ebe67eae9d4cb6636c7a905d9cbaf587dabcbd4eca6013"}, + {file = "multidict-7.1.0-cp315-cp315-android_24_x86_64.whl", hash = "sha256:852c921217f330b3e81a822647ebadeae7e42cf503ec1992d0bfbc90121c09fb"}, + {file = "multidict-7.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cbec738d2ad551c6f70955d7eec95e339380ee1564e2afe86bfee05fed52ceec"}, + {file = "multidict-7.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a5f721a2437390ab69c10c6df5c142478d399af8dfb02e6d823cf2358e8a4748"}, + {file = "multidict-7.1.0-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:99cf27791129d37e191ff013bfc29bf6631c29edb21680c00978567b91fc5d6b"}, + {file = "multidict-7.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:2ba6611fc93c4b169d0e0ea376ebf4b8a529933d1f5f2c2ec7d8f8b93ef58ec2"}, + {file = "multidict-7.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:c45629c0049fbdef932dbe408ac2b271fdc8c7d9962ca31160f4a0fc3455fe4f"}, + {file = "multidict-7.1.0-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cf606cfe3f67984b4064ac605d71e1eba12515fbabf5bd5a34a8952b8800dc66"}, + {file = "multidict-7.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50fdfcb03be719d9573597b095b1175d2e9d0b30d065791dfd9fca727c499442"}, + {file = "multidict-7.1.0-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a8bba9d1f6db4ef2a6ebfc937a65d36e80e3aada00b382eaf56fea8f639322d5"}, + {file = "multidict-7.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:46d4af0afc6eb9867b3ae50605787c80b868e2f52eac3801246034925fe578b8"}, + {file = "multidict-7.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5cc58ebb731200ddb64d55f1b345630fb5f7a8138cdbd242af9dce964a7cb03d"}, + {file = "multidict-7.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b87ad54e8d4adeb0a1f04889504d6ec7f04fb02609220810f51f1b6c66bc1cc"}, + {file = "multidict-7.1.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44f7e5dd83a615636b80182bdf446ece57ed61d5d51854acc5d9840631136d4e"}, + {file = "multidict-7.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:9bc5e7f843d14a167cdc26fe2d22f6f3aa2feb57919cf3ff034262a57d8d95d0"}, + {file = "multidict-7.1.0-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:0631eb5f49f67de10bbdc3f64141326dbc62e8d319900966648381ce0845d8ca"}, + {file = "multidict-7.1.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:d1b1b32f3c32f734dde8f36ac1df8e275e768a7b333241cd637cb2538628a4b4"}, + {file = "multidict-7.1.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:159976f9c40f96e3fe0952b708846a43a76bacb114e9cc828816f5080bddd5ec"}, + {file = "multidict-7.1.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:81a0e08c64dfdad27dab687b96f572b23bafa1999a39d1b6f70b3ddbb73e8bd0"}, + {file = "multidict-7.1.0-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:4e11e7299079718c78f8147e7206c22fe35bab4466d38992420795288a0b8096"}, + {file = "multidict-7.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:34d2ee98e15d5cfe782a431bc913fce3b58cf3fdb34fcb437aeb275cdf9007ab"}, + {file = "multidict-7.1.0-cp315-cp315-win32.whl", hash = "sha256:f376224572d1f5da1c871f969ab04765727f180e70d012d93e07bfc08442c64b"}, + {file = "multidict-7.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:67fcf28db77b385820881521db7435e9f1c607cfaf07db6eb78aa9d1146bde86"}, + {file = "multidict-7.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:c7aafa4dd2f702ee2198005d6cba4309c1e25ed1c201d77beddefa47411bead8"}, + {file = "multidict-7.1.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1348ddc076251cd542f4a99ccda4b7c1f8444e8ab489d3541a978ca5901c7c1f"}, + {file = "multidict-7.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:9267bf8261a779abb2a6eab5f107f5db85b2d1745f2494081c731aaf28738ce3"}, + {file = "multidict-7.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:8a844b8b1685f38a2e8b2f3213b286e2a7abfe67508381780a0d4599ac337c1c"}, + {file = "multidict-7.1.0-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:18a447d46a3a2f1e61b365cbf5627db7030fdb707dad70c4f2760e5144166ecc"}, + {file = "multidict-7.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88811f890db240a1c82bf0bcd52973763707a552c8113ac3fcebca183afb2fa8"}, + {file = "multidict-7.1.0-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a60b720c329c0007feae692b7bf91cf17b3f9bd3727be96cc6f9a3336651041b"}, + {file = "multidict-7.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b117ed1cd1a23df0902461c38093408b95971833dcee629112acda25b603c8d0"}, + {file = "multidict-7.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bbcae7a54050b7ad7bc7bf425ba63dea7d2cd31a92246ba787a2ce69a9b98dbc"}, + {file = "multidict-7.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4cba2b0b9235fe10e12301d6b4cfba0f353fa668d635f6e988b03623c2cd42ba"}, + {file = "multidict-7.1.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:535173fbcc3933d84f9929d49d7a59a0faec259ee07d07c34c7d2a980b4e3683"}, + {file = "multidict-7.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:ea027bdeca1d7e498237634ee4e3a852e2723eef39996dec0ff0f77dff8a2336"}, + {file = "multidict-7.1.0-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:507151e1e3dee95e9e8159e329aed4f75aa5205ecd6505a4f6be546890eafbe1"}, + {file = "multidict-7.1.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:9c10791e9f5ef132effc8fdce2009482c1cfb26618c5fc1b7952a47dd5eb632e"}, + {file = "multidict-7.1.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:f16ac8af2804855d3cae5fc3c5ab609c9fd0fc8ecacd92579c05ed3c173396fd"}, + {file = "multidict-7.1.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:b78de22bae456a976f33df34d598dfd16edc9a03df8f4cc8b7c17bdba4c97b4a"}, + {file = "multidict-7.1.0-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:db77888081431aaa69f3fd3480891746ddce6c2a571f6201869a24e2f06cf423"}, + {file = "multidict-7.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:c39dfcaa0bf23443474c0cb58d8d8aea9529c1841d99654cb38e4dada7b1948a"}, + {file = "multidict-7.1.0-cp315-cp315t-win32.whl", hash = "sha256:16b21164797bde6f417066d02775975cc2e15ab8abf80efa55fe85e0b4894020"}, + {file = "multidict-7.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:55392202cb374dd1a1f89a8ce1586644870d9e936752059d053e576acc50bc89"}, + {file = "multidict-7.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:f979a077d1c0a9a36dd4fab0d3a36b8de7b593bf935e13df85a380395b2c11ad"}, + {file = "multidict-7.1.0-py3-none-any.whl", hash = "sha256:d9ef29cfd98e17085b4f91bba8fa1570bec6787d5c52ce653ed33a58785585d0"}, + {file = "multidict-7.1.0.tar.gz", hash = "sha256:61a4e5d81b8d4e4ad61964b230129e7a2b914793d96289029078fc9009f074ec"}, ] [[package]] @@ -853,7 +886,7 @@ files = [ [[package]] name = "nepattern" -version = "0.7.7" +version = "0.7.8" requires_python = ">=3.8" summary = "a complex pattern, support typing" groups = ["default", "cli", "full"] @@ -862,8 +895,8 @@ dependencies = [ "typing-extensions>=4.5.0", ] files = [ - {file = "nepattern-0.7.7-py3-none-any.whl", hash = "sha256:2d66f964333f42df7971390da4fb98dfed1e8b769236f305c28a83c0bcda849a"}, - {file = "nepattern-0.7.7.tar.gz", hash = "sha256:6667f888457e78937998f9412eb70ad16d220464d2d77850dd2b05e9ecfb3207"}, + {file = "nepattern-0.7.8-py3-none-any.whl", hash = "sha256:59fe29954b2bf8661ebca089a90977d7ca743cd6a696f4c829f97efe6fe59004"}, + {file = "nepattern-0.7.8.tar.gz", hash = "sha256:a2e20440be72a500ed7d57f28d010ac3654ae691c189b757f1ae01e33b3df57b"}, ] [[package]] @@ -879,289 +912,320 @@ files = [ [[package]] name = "packaging" -version = "26.1" -requires_python = ">=3.8" +version = "26.3" +requires_python = ">=3.9" summary = "Core utilities for Python packages" groups = ["cli", "dev", "full"] files = [ - {file = "packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f"}, - {file = "packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de"}, + {file = "packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c"}, + {file = "packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79"}, ] [[package]] name = "pathspec" -version = "1.0.4" +version = "1.1.1" requires_python = ">=3.9" summary = "Utility library for gitignore style pattern matching of file paths." groups = ["dev"] files = [ - {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, - {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, + {file = "pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189"}, + {file = "pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"}, ] [[package]] name = "platformdirs" -version = "4.9.6" -requires_python = ">=3.10" +version = "4.13.3" +requires_python = ">=3.11" summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." groups = ["cli", "dev", "full"] files = [ - {file = "platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917"}, - {file = "platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a"}, + {file = "platformdirs-4.13.3-py3-none-any.whl", hash = "sha256:f6ad7f447f24f8a3b82cce5976387428bff894a0eca6c3488f4a17f153c130c4"}, + {file = "platformdirs-4.13.3.tar.gz", hash = "sha256:5e567f664eb087ab8521c0179cd8d1bd60857d271136567a39719e28e2d383ce"}, ] [[package]] name = "propcache" -version = "0.4.1" -requires_python = ">=3.9" +version = "0.5.4" +requires_python = ">=3.10" summary = "Accelerated property cache" groups = ["default"] files = [ - {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, - {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, - {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"}, - {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"}, - {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"}, - {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"}, - {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"}, - {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"}, - {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"}, - {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"}, - {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"}, - {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"}, - {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"}, - {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"}, - {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"}, - {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"}, - {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"}, - {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}, - {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}, - {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}, - {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}, - {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}, - {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}, - {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}, - {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}, - {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, + {file = "propcache-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:897d1ddf6716e8f47200f7aad9a0efa6cc7586df66c6defa572f9eab379c078e"}, + {file = "propcache-0.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9cbfff4423eef4cc6cafc021469641a2b835f610b2647a6c5281903e21b8670d"}, + {file = "propcache-0.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fc24f209c1b7f7f688b66b98293954f5504279760999b58920ee12dd8471c1d"}, + {file = "propcache-0.5.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62530ca89187827e4a4fe733f971abe81a7542eeea48ff61995f19b64d7199c8"}, + {file = "propcache-0.5.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fc3f7599528db40b1efa0889a620116e2704144495273d66066e8164e45838"}, + {file = "propcache-0.5.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f2d880ff60f45898f4acfa152aac8d04e3ee627d90ff4003491bf92239d5757"}, + {file = "propcache-0.5.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e9368e87a3efc285e559131092c5db643eb8e56de4ee42064d5baec22ef2bb5"}, + {file = "propcache-0.5.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:004e685b315646c410771836e72a44f143bbe624f29653a42687815069a303d5"}, + {file = "propcache-0.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:594eb4c6ec35e7179b058481f4e9f02521b56de16fa577c4b85c76fb1bf8a9f8"}, + {file = "propcache-0.5.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2dba2f02d2d5c09ef8a0e6c1a42aeaa451f4be9898cb00b04fe98717da2eb23b"}, + {file = "propcache-0.5.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c3ef2818d63bc86071e9d2989ae75a1bc32b8f7059cfd9f5abbbee70c32e2ed6"}, + {file = "propcache-0.5.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:dd2ac8f5b643454c2cc6b6118b13da16e88f4a6434fc3ba61aca384029f04f36"}, + {file = "propcache-0.5.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4054acf80d40456a0537f2913b349718649d8d6458a14ab7f48d0ce28c30869d"}, + {file = "propcache-0.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40e94adb1e7d39ff28a8bd8d8b8fbd1df6b9f40976dbe379134f1ce058e532dd"}, + {file = "propcache-0.5.4-cp311-cp311-win32.whl", hash = "sha256:9f86f7259efe2c951f43e57d471c9b41daa5bfc7db9f67189059cf1ae6d77fd9"}, + {file = "propcache-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:e904d4d01f36bd6e197590be1533c44e06058771e0746dd073a8ebb3ef880858"}, + {file = "propcache-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:d42a9a856a4a6e2f6c10f1318c07e7daa498d6593abe745c71dae4521a26ca39"}, + {file = "propcache-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b28f41fa3b8c6900457f858ec5b03998f3a6d535fbc1bb2edec5961ea05ec429"}, + {file = "propcache-0.5.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dcbf346a318a5e30063f547630b02bb787ce2f45b6368d5da143660b6a3835d8"}, + {file = "propcache-0.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87a3caecf8095e48dc72f84bfa42e23a848cf410cc9cc13031fba4869b706a21"}, + {file = "propcache-0.5.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60a64cbccaa11b7760ce705a14ada17ba459e7ca9f23ba587eb013821032d7ef"}, + {file = "propcache-0.5.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a74bfa37147cc08fb29df10bd9c16f40fa7f860cd3a6d2fff853323a94f6e17f"}, + {file = "propcache-0.5.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4d7a54719b67338a305dca2ce6aafe366817df94ddfd4b5514374356f5ca546"}, + {file = "propcache-0.5.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2814ecd8e818f487bee4b0f921bc4d1c176cc5fc71ac0f072d0fa67eda4ac14b"}, + {file = "propcache-0.5.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6af4693716bfb03f1752ef1b30faa593db2c01d5272e9b8564a1549452a979ab"}, + {file = "propcache-0.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4fbc1a15dc8cd1689508758d626b372b1f09d28d9577667feaf9e6bfcd8efcbc"}, + {file = "propcache-0.5.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cdee8205a44d0be91bbac4c41b95d86641b72dfc7aef1279400e4fda3f26a937"}, + {file = "propcache-0.5.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9a2a8a50a93dee0268a860a07fa3b4bd968f8ce4dbd794957da772f395368526"}, + {file = "propcache-0.5.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7ffafcbfc7b549ab940047e505c831eabac5e67de53e1bc174adbc5285c55944"}, + {file = "propcache-0.5.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d1f5a500bfcbb2c0ab85e98a0dcd70f5899d34efe365a0187700369a79603031"}, + {file = "propcache-0.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a235f73d6e020855dc29dff012d920c02ee0feab8d73a24185a7569f4be1161"}, + {file = "propcache-0.5.4-cp312-cp312-win32.whl", hash = "sha256:b3083bfe87f95c756e610bd8025f26cbd1cd4aaa03a422f2d65efb7a97cd53d8"}, + {file = "propcache-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:98914de2c4d7f0f9f4a8c6ea4bf05841f4175796941e3ef7d47eb718f22311fb"}, + {file = "propcache-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:8876b39961e33d912afe3c1bee18ee564fdad0206f873cc15d522756b7f50737"}, + {file = "propcache-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:36c0d9db44b523ef93d03341b1c42d69ff01d673c053d1b1c6c3a363bcaa39ba"}, + {file = "propcache-0.5.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1d52a05dc417279f7e5c7618c5dfbbc29923aaf9bc0a5c1802ddcebf54c61a0"}, + {file = "propcache-0.5.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44149f46500a0a41b95b4d99c2e586a77319539730607b9892974a092788b111"}, + {file = "propcache-0.5.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbab5f5ff6897c81f355d079010cdae85b02e5a0b518b5251523b8ad8ae9ac3c"}, + {file = "propcache-0.5.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c3e98c55bde2bcf7db3c70d1aed7ae9aa8aebbf19a250c66645cde44cdb8b867"}, + {file = "propcache-0.5.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db3ae52ccc150dbc84704e9d642743897f3e1c54742ff34cacb661e52e3818a9"}, + {file = "propcache-0.5.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f85915e00dcb1cd9f2f890ead064ed40a27df06f0db65be427b29482ae357572"}, + {file = "propcache-0.5.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c2ba30a89035b57b73e00475de948521602f543d79ce01db10b04b36c4c76fc8"}, + {file = "propcache-0.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ae58f361bd5dae942717c65d3413b478c70aea9c462599e7b9adad3731db3894"}, + {file = "propcache-0.5.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:96f7c5c15656040ddcbc51e56dc59b58aa25999d743c126abd425b9766ab43e9"}, + {file = "propcache-0.5.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7cc528e760a8af06f2b13e9b9f362cd90c7c718ea61228a96dbd31ba16ed7f47"}, + {file = "propcache-0.5.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:425f8cc86ab5018b4b8d4a23bc8e74d964bd3d757c3702e301aa79be76c53f6c"}, + {file = "propcache-0.5.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a5793c7698a53f56f4a1889a4737c7eeb1b7ad0842fa6b1abca22913ff79c8c1"}, + {file = "propcache-0.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c02c0e570c5c7e077b0181a9f3cdb7d4c3617d1cda6b5c95bd5d34022923d82c"}, + {file = "propcache-0.5.4-cp313-cp313-win32.whl", hash = "sha256:3e413d7a4a9b4866b7a761d6060d434b64d23cd35122eda3b026a0bbe8196b25"}, + {file = "propcache-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:0c889f6fa84957bc7e8b4eab71fd16a0455068d5045e3aa40c733071d2b2fd77"}, + {file = "propcache-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:69fc35c0779522da366c563e5faf203ffc1f8ff0021d5b1337fa4efa5be73177"}, + {file = "propcache-0.5.4-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e6720ba44ad7e72174314d0e1fb0172494cff5c73a3a8a2159c3d2402ff15565"}, + {file = "propcache-0.5.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4cfe0a92ae30151869e67a4b5f5e105e4e03ad30b3f38e5211b5bf77d0881993"}, + {file = "propcache-0.5.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d759d05634f1b038fb625a66662a8c85e5a8fec912da381b5149ddac107482b"}, + {file = "propcache-0.5.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:251c63dd46a0659bb875cb254dc4c1e79ee91a847c737cd62373295afc2235dc"}, + {file = "propcache-0.5.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a8d5ff04eb1f85698a78d20c62a14676e7b960dcafde09a388d60ad377d355d"}, + {file = "propcache-0.5.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b9100a93b372418d8688f3f2a3e5b45c64d70ca4d6176e121aca1e3bfc1e32f"}, + {file = "propcache-0.5.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc07876cfb079b6f6f36d21ce75784ad6c2c6b563eeac0ed26c2fa2669b85df9"}, + {file = "propcache-0.5.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0951315a6b3142ee2167404d707743f0157c110091342b1aa0accac5cf0e4acf"}, + {file = "propcache-0.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bee7d3aed13d56f54e681df38c3a23031bc9e3863f687d9d598825c9146acd7d"}, + {file = "propcache-0.5.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4e985382be6d15da8d0c2710a6fa7b9070fc9ecdeefb7f580e88373984ec8be3"}, + {file = "propcache-0.5.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9e9ab13760aa8b6d0881ae7cb04fd891d8d490cd2554ea8e79bb278399169bcc"}, + {file = "propcache-0.5.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1b2f3bec4261a94019575481c726c29850f72e27907773c75b1de421e20e9f9d"}, + {file = "propcache-0.5.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:720cf832eb2d0b0dfee129cb3335a26f6ce3cc45ee1187e8f0731758caa16792"}, + {file = "propcache-0.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fb0a5be8d9aa213150e8d8148a42aca4984b285bcad1e69587dc4298edd929b"}, + {file = "propcache-0.5.4-cp314-cp314-win32.whl", hash = "sha256:30cc1cebaf9aef49db06357a50398323ae04d70460c0491837d026ab7d6452ea"}, + {file = "propcache-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:0a095db8e15a6020db149ecbed6461939fe74f6acaa3ae8b702a1fe8c38cd983"}, + {file = "propcache-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:45488d1a5f9ab5bd90aaa1ca20f50fe1922b8ffad71a2009d2adf41355897aac"}, + {file = "propcache-0.5.4-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:53eaa697c4d0422ff4cb714d00231b43352064d97b944033b30c1d57cc506ec0"}, + {file = "propcache-0.5.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:886b59c4d28ca97dd23b025fdfc50a0356be934efbbbca89ad26230067f86fe5"}, + {file = "propcache-0.5.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fa15757fea1dfcd5b7745cad9f4638929605531bd4018ab2adff7955f1a403d"}, + {file = "propcache-0.5.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f0093ac3e9daada202c2082439d414a625c57184727a46e112a3fb2a81cb788"}, + {file = "propcache-0.5.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3cd3a7edb6b95b9b33998135ebfa18d709da82290fb8f27c858970b5a12c8b56"}, + {file = "propcache-0.5.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c174bfd1c48a1b51a3078e95586dde718374bac79719ab3541ec9e74aec40574"}, + {file = "propcache-0.5.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a219f0ac59817a9114dd2aa57c13180f993e819ba658c7ddab4b66ed1ee0d370"}, + {file = "propcache-0.5.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:17a7400cec0256f0a71ae71f9da398f9894c956ff6668a1c9d317b3367316320"}, + {file = "propcache-0.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:978f28401afbc76cdc3df9e1717b4229a06b626a1dcc75db4e1f2beb3884c3e9"}, + {file = "propcache-0.5.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4a1f4f5ffa55dce6307631f3cb2948e117e665966ea512e0d502b16c24f567e7"}, + {file = "propcache-0.5.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:213bb68d9ced5cf2bf717b1071bf2b09b4b04c426256f9fe6d054c60318424c4"}, + {file = "propcache-0.5.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:286867fb156488c251a3721766e380ac4495e4fd6b51aaa1403d89ce7f4359d9"}, + {file = "propcache-0.5.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:445ee3bfb46e85838387fb3c536a73cc0b994dc192b004e40e170adc54aa2a7e"}, + {file = "propcache-0.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:48cb48c5346a97de792254af77715aa2529c2a1ebc5f586aa0aae44a02f1fe57"}, + {file = "propcache-0.5.4-cp314-cp314t-win32.whl", hash = "sha256:03b229037d25b801e7af53fd52b9fc49d9439b036fca1e087e02780631adfa97"}, + {file = "propcache-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:8a1fc236528c457cd739c88abe823da851b7ab645d72792f88658114cc340c12"}, + {file = "propcache-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:135036c5cfc93864affb0f9af9a27e5d7a71cb7bd745e7b6dbfc2d56cc30e827"}, + {file = "propcache-0.5.4-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:45bf2e730ab8905d0527fe05a86500f406e64305c34cc81ebe64b4617cab9760"}, + {file = "propcache-0.5.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:31eb43ba2edc704ab2ec27815315dd8a19def0fb16215be4cfe8d32fe78ffd51"}, + {file = "propcache-0.5.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:174507f82d3594622acb1dd2dafecf2d899d6d506335494e7107767bf05f3aae"}, + {file = "propcache-0.5.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e337653721d20ead710da33bf44487fbe8a0db8782714b60306481e9f95b51"}, + {file = "propcache-0.5.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d21d0d2c82bbfeb1677a9711f38df968f9837576102bb4add1bd449d28d88f1"}, + {file = "propcache-0.5.4-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccf4f7a79e26bb7efb06ecd50c177833b71df05cbc748701372325e6bcc17f6f"}, + {file = "propcache-0.5.4-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23278f808cd81d5ada7184a76606b925fb3389c60e1077b2cd7da7b1fcf0553c"}, + {file = "propcache-0.5.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e738ab81179510ce79b2eac9a6ecf47feffd9e76d1c72e403005dddb6e36c06c"}, + {file = "propcache-0.5.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:a419ee85e654927baabda3929c03c0cc1112bf472ff0dfd6142f4e3a81ca4162"}, + {file = "propcache-0.5.4-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:b61805357d966680acf68b3b6d49772631ed9df44ebece10ff1460e117a7da8a"}, + {file = "propcache-0.5.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:58134228927cee6c047d626c08e60a81be604a20578a12ce752cc5c9a84d4826"}, + {file = "propcache-0.5.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:350b272b2279f4135a64fc0c304a5d08e28a137c9573442c606152446638a831"}, + {file = "propcache-0.5.4-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:45bebbe252550fec975ba3b62bc6f931643cfd3b5464ef47619cf3fef154e01c"}, + {file = "propcache-0.5.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:ada748108a43d29b7c328ba7db3755327cd94f028bcc1a7ee3f0addcfacd9c38"}, + {file = "propcache-0.5.4-cp315-cp315-win32.whl", hash = "sha256:ee19113bce2f3acd46432050688b70f61acd6857d75abb9ec96341b7e9ced123"}, + {file = "propcache-0.5.4-cp315-cp315-win_amd64.whl", hash = "sha256:ceb3e879afac028f93d272c957814695dc5569e4904262dbee92f6c41bd5e4a3"}, + {file = "propcache-0.5.4-cp315-cp315-win_arm64.whl", hash = "sha256:c83acbce9f2b5e3f5f5eda9e53d2001fed22fcdfef81274a9e02d8fd53b70a30"}, + {file = "propcache-0.5.4-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:a5e8ef588c109725dc713ba69aadcac00a1ef90c2ce9c0a8c7075128f569f47f"}, + {file = "propcache-0.5.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:4d86476a935c88963d9b8e1a9a0d38188790e9622169bfbafa173046846709d3"}, + {file = "propcache-0.5.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:f5470694918830da62fac9e69133b53d23b736d7070e587b27a4a2be37e08e68"}, + {file = "propcache-0.5.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10ef33a68a61ce317e095fd2e202a592ea92392b90944a78c993f0d9a73ab06c"}, + {file = "propcache-0.5.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5cacf3c9efd09df409dc33654dd077e1c245ba8fb747b0f0236ef41b7c49b589"}, + {file = "propcache-0.5.4-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:770e8209d018175fc0063936fa9583b6d27e88c5ad31543f3383d66080efdd62"}, + {file = "propcache-0.5.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03969626faf0783a592dfa17e28eac06018bd0b44dafae6943d53b92421a7f72"}, + {file = "propcache-0.5.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef3b928d9c984322b5c44e6964d8dbc653da87d2d8ee1647fa6da43072e650a9"}, + {file = "propcache-0.5.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:7177c43eddf10a0893c4fec52ebb408fdcd7f7d63962caace9180d8f81b14ece"}, + {file = "propcache-0.5.4-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:420162a77f94eb1cf5ef7893f500016dabd548e73de956785a1dd899cc73006a"}, + {file = "propcache-0.5.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:3eb2e820e8e2101407da93f17c57cbb7d225461955fc60105daaba14cd421ee2"}, + {file = "propcache-0.5.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:13e52b6e0bde97dee98ab66552dbff2931649c96f1ac432eac299fe689ec373b"}, + {file = "propcache-0.5.4-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:12682126712ddc19b70ff819debbd279e58adf1f0c8f8f8138c18ade2044b284"}, + {file = "propcache-0.5.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:3af0c8642b2da4815d86e631232ac8286e17644fad907c19508aa8e7cb4ba8ad"}, + {file = "propcache-0.5.4-cp315-cp315t-win32.whl", hash = "sha256:1df8d8561b21465c5dd56110a01caf897e026d065b4b84e98a488209094272ec"}, + {file = "propcache-0.5.4-cp315-cp315t-win_amd64.whl", hash = "sha256:02c0a34f16889cf800f10f0247a564d8ce6eeab6ffcd7c87198f769067eb8432"}, + {file = "propcache-0.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:dc4242ca653c9b30ab51c5f8193323e7bc0928f897ee9103201e59a43abcb72e"}, + {file = "propcache-0.5.4-py3-none-any.whl", hash = "sha256:62c60aec739ed00124573cce1178138fd690c7676352d67a37328c1cf51d7468"}, + {file = "propcache-0.5.4.tar.gz", hash = "sha256:ff6b113f50bc066a698db5d944d2c6dc7507168dd3341e255a8892fd0715a558"}, ] [[package]] name = "pydantic" -version = "2.13.0" -requires_python = ">=3.9" +version = "2.14.1" +requires_python = ">=3.10" summary = "Data validation using Python type hints" groups = ["dev", "pydantic"] dependencies = [ "annotated-types>=0.6.0", - "pydantic-core==2.46.0", - "typing-extensions>=4.14.1", - "typing-inspection>=0.4.2", + "pydantic-core==2.50.1", + "typing-extensions>=4.16.0", + "typing-inspection>=0.4.4", ] files = [ - {file = "pydantic-2.13.0-py3-none-any.whl", hash = "sha256:ab0078b90da5f3e2fd2e71e3d9b457ddcb35d0350854fbda93b451e28d56baaf"}, - {file = "pydantic-2.13.0.tar.gz", hash = "sha256:b89b575b6e670ebf6e7448c01b41b244f471edd276cd0b6fe02e7e7aca320070"}, + {file = "pydantic-2.14.1-py3-none-any.whl", hash = "sha256:9195d967ec791692a04438115466764fb8b9a27b31f14a760437694f40d6b454"}, + {file = "pydantic-2.14.1.tar.gz", hash = "sha256:94f478203dd03404682a1ada216965651dd74b1d2d5ffd62e00e0837caab5c26"}, ] [[package]] name = "pydantic-core" -version = "2.46.0" -requires_python = ">=3.9" +version = "2.50.1" +requires_python = ">=3.10" summary = "Core functionality for Pydantic validation and serialization" groups = ["dev", "pydantic"] dependencies = [ - "typing-extensions>=4.14.1", + "typing-extensions>=4.16.0", ] files = [ - {file = "pydantic_core-2.46.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d449eae37d6b066d8a8be0e3a7d7041712d6e9152869e7d03c203795aae44ed"}, - {file = "pydantic_core-2.46.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4f7bfc1ffee4ddc03c2db472c7607a238dbbf76f7f64104fc6a623d47fb8e310"}, - {file = "pydantic_core-2.46.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a30f5d1d4e1c958b44b5c777a0d1adcd930429f35101e4780281ffbe11103925"}, - {file = "pydantic_core-2.46.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f68e12d2de32ac6313a7d3854f346d71731288184fbbfc9004e368714244d2cd"}, - {file = "pydantic_core-2.46.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d1a058fb5aff8a1a221e7d8a0cf5b0133d069b2f293cb05f174c61bc7cdac34"}, - {file = "pydantic_core-2.46.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd01128431f355e309267283e37e23704f24558e9059d930e213a377b1be919"}, - {file = "pydantic_core-2.46.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7747a50d9f75fe264b9e2091a2f462a7dd400add8723a87a75240106b6f4d949"}, - {file = "pydantic_core-2.46.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:1d9b841e9c82a9cdf397a720bb8a4f2d6da6780204e1eb07c2d90c4b5b791b0d"}, - {file = "pydantic_core-2.46.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:61d0f5951b7b86ec24e24fe0c5a2cce7c360830026dfbe004954e8fac9918b95"}, - {file = "pydantic_core-2.46.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:aec0be48d2555ceac04905ffb8f2bb7e55a56644858891196191827b6fc656b7"}, - {file = "pydantic_core-2.46.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:2c1ec2ced44a8a479d71a14f5be35461360acd388987873a8e0a02f7f81c8ec2"}, - {file = "pydantic_core-2.46.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5e157a25eed281f5e40119078e3dbf698c28b3d88ff0176eea3dd37191447b8d"}, - {file = "pydantic_core-2.46.0-cp310-cp310-win32.whl", hash = "sha256:311929d9bfdb9fdbaf28beb39d88a1e36ca6dc5424ceca6d3bf81c9e1da2313c"}, - {file = "pydantic_core-2.46.0-cp310-cp310-win_amd64.whl", hash = "sha256:60edfb53b13fbe7be9bb51447016b7bcd8772beb8ca216873be33e9d11b2c8e8"}, - {file = "pydantic_core-2.46.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0027da787ae711f7fbd5a76cb0bb8df526acba6c10c1e44581de1b838db10b7b"}, - {file = "pydantic_core-2.46.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:63e288fc18d7eaeef5f16c73e65c4fd0ad95b25e7e21d8a5da144977b35eb997"}, - {file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:080a3bdc6807089a1fe1fbc076519cea287f1a964725731d80b49d8ecffaa217"}, - {file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c065f1c3e54c3e79d909927a8cb48ccbc17b68733552161eba3e0628c38e5d19"}, - {file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e2db58ab46cfe602d4255381cce515585998c3b6699d5b1f909f519bc44a5aa"}, - {file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c660974890ec1e4c65cff93f5670a5f451039f65463e9f9c03ad49746b49fc78"}, - {file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3be91482a8db77377c902cca87697388a4fb68addeb3e943ac74f425201a099"}, - {file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:1c72de82115233112d70d07f26a48cf6996eb86f7e143423ec1a182148455a9d"}, - {file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7904e58768cd79304b992868d7710bfc85dc6c7ed6163f0f68dbc1dcd72dc231"}, - {file = "pydantic_core-2.46.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1af8d88718005f57bb4768f92f4ff16bf31a747d39dfc919b22211b84e72c053"}, - {file = "pydantic_core-2.46.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:a5b891301b02770a5852253f4b97f8bd192e5710067bc129e20d43db5403ede2"}, - {file = "pydantic_core-2.46.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:48b671fe59031fd9754c7384ac05b3ed47a0cccb7d4db0ec56121f0e6a541b90"}, - {file = "pydantic_core-2.46.0-cp311-cp311-win32.whl", hash = "sha256:0a52b7262b6cc67033823e9549a41bb77580ac299dc964baae4e9c182b2e335c"}, - {file = "pydantic_core-2.46.0-cp311-cp311-win_amd64.whl", hash = "sha256:4103fea1beeef6b3a9fed8515f27d4fa30c929a1973655adf8f454dc49ee0662"}, - {file = "pydantic_core-2.46.0-cp311-cp311-win_arm64.whl", hash = "sha256:3137cd88938adb8e567c5e938e486adc7e518ffc96b4ae1ec268e6a4275704d7"}, - {file = "pydantic_core-2.46.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:66ccedb02c934622612448489824955838a221b3a35875458970521ef17b2f9c"}, - {file = "pydantic_core-2.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a44f27f4d2788ef9876ec47a43739b118c5904d74f418f53398f6ced3bbcacf2"}, - {file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26a1032bcce6ca4b4670eb3f7d8195bd0a8b8f255f1307823e217ca3cfa7c27"}, - {file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b8d1412f725060527e56675904b17a2d421dddcf861eecf7c75b9dda47921a4"}, - {file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc3d1569edd859cabaa476cabce9eecd05049a7966af7b4a33b541bfd4ca1104"}, - {file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:38108976f2d8afaa8f5067fd1390a8c9f5cc580175407cda636e76bc76e88054"}, - {file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5a06d8ed01dad5575056b5187e5959b336793c6047920a3441ee5b03533836"}, - {file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:04017ace142da9ce27cafd423a480872571b5c7e80382aec22f7d715ca8eb870"}, - {file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2629ad992ed1b1c012e6067f5ffafd3336fcb9b54569449fabb85621f1444ed3"}, - {file = "pydantic_core-2.46.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3068b1e7bd986aebc88f6859f8353e72072538dcf92a7fb9cf511a0f61c5e729"}, - {file = "pydantic_core-2.46.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:1e366916ff69ff700aa9326601634e688581bc24c5b6b4f8738d809ec7d72611"}, - {file = "pydantic_core-2.46.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:485a23e8f4618a1b8e23ac744180acde283fffe617f96923d25507d5cade62ec"}, - {file = "pydantic_core-2.46.0-cp312-cp312-win32.whl", hash = "sha256:520940e1b702fe3b33525d0351777f25e9924f1818ca7956447dabacf2d339fd"}, - {file = "pydantic_core-2.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:90d2048e0339fa365e5a66aefe760ddd3b3d0a45501e088bc5bc7f4ed9ff9571"}, - {file = "pydantic_core-2.46.0-cp312-cp312-win_arm64.whl", hash = "sha256:a70247649b7dffe36648e8f34be5ce8c5fa0a27ff07b071ea780c20a738c05ce"}, - {file = "pydantic_core-2.46.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a05900c37264c070c683c650cbca8f83d7cbb549719e645fcd81a24592eac788"}, - {file = "pydantic_core-2.46.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de8e482fd4f1e3f36c50c6aac46d044462615d8f12cfafc6bebeaa0909eea22"}, - {file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c525ecf8a4cdf198327b65030a7d081867ad8e60acb01a7214fff95cf9832d47"}, - {file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f14581aeb12e61542ce73b9bfef2bca5439d65d9ab3efe1a4d8e346b61838f9b"}, - {file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c108067f2f7e190d0dbd81247d789ec41f9ea50ccd9265a3a46710796ac60530"}, - {file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ac10967e9a7bb1b96697374513f9a1a90a59e2fb41566b5e00ee45392beac59"}, - {file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7897078fe8a13b73623c0955dfb2b3d2c9acb7177aac25144758c9e5a5265aaa"}, - {file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:e69ce405510a419a082a78faed65bb4249cfb51232293cc675645c12f7379bf7"}, - {file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fd28d13eea0d8cf351dc1fe274b5070cc8e1cca2644381dee5f99de629e77cf3"}, - {file = "pydantic_core-2.46.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ee1547a6b8243e73dd10f585555e5a263395e55ce6dea618a078570a1e889aef"}, - {file = "pydantic_core-2.46.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c3dc68dcf62db22a18ddfc3ad4960038f72b75908edc48ae014d7ac8b391d57a"}, - {file = "pydantic_core-2.46.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:004a2081c881abfcc6854a4623da6a09090a0d7c1398a6ae7133ca1256cee70b"}, - {file = "pydantic_core-2.46.0-cp313-cp313-win32.whl", hash = "sha256:59d24ec8d5eaabad93097525a69d0f00f2667cb353eb6cda578b1cfff203ceef"}, - {file = "pydantic_core-2.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:71186dad5ac325c64d68fe0e654e15fd79802e7cc42bc6f0ff822d5ad8b1ab25"}, - {file = "pydantic_core-2.46.0-cp313-cp313-win_arm64.whl", hash = "sha256:8e4503f3213f723842c9a3b53955c88a9cfbd0b288cbd1c1ae933aebeec4a1b4"}, - {file = "pydantic_core-2.46.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:4fc801c290342350ffc82d77872054a934b2e24163727263362170c1db5416ca"}, - {file = "pydantic_core-2.46.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0a36f2cc88170cc177930afcc633a8c15907ea68b59ac16bd180c2999d714940"}, - {file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a3912e0c568a1f99d4d6d3e41def40179d61424c0ca1c8c87c4877d7f6fd7fb"}, - {file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3534c3415ed1a19ab23096b628916a827f7858ec8db49ad5d7d1e44dc13c0d7b"}, - {file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21067396fc285609323a4db2f63a87570044abe0acddfcca8b135fc7948e3db7"}, - {file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2afd85b7be186e2fe7cdbb09a3d964bcc2042f65bbcc64ad800b3c7915032655"}, - {file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67e2c2e171b78db8154da602de72ffdc473c6ee51de8a9d80c0f1cd4051abfc7"}, - {file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c16ae1f3170267b1a37e16dba5c297bdf60c8b5657b147909ca8774ce7366644"}, - {file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:133b69e1c1ba34d3702eed73f19f7f966928f9aa16663b55c2ebce0893cca42e"}, - {file = "pydantic_core-2.46.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:15ed8e5bde505133d96b41702f31f06829c46b05488211a5b1c7877e11de5eb5"}, - {file = "pydantic_core-2.46.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:8cfc29a1c66a7f0fcb36262e92f353dd0b9c4061d558fceb022e698a801cb8ae"}, - {file = "pydantic_core-2.46.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e1155708540f13845bf68d5ac511a55c76cfe2e057ed12b4bf3adac1581fc5c2"}, - {file = "pydantic_core-2.46.0-cp314-cp314-win32.whl", hash = "sha256:de5635a48df6b2eef161d10ea1bc2626153197333662ba4cd700ee7ec1aba7f5"}, - {file = "pydantic_core-2.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:f07a5af60c5e7cf53dd1ff734228bd72d0dc9938e64a75b5bb308ca350d9681e"}, - {file = "pydantic_core-2.46.0-cp314-cp314-win_arm64.whl", hash = "sha256:e7a77eca3c7d5108ff509db20aae6f80d47c7ed7516d8b96c387aacc42f3ce0f"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5e7cdd4398bee1aaeafe049ac366b0f887451d9ae418fd8785219c13fea2f928"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5c2c92d82808e27cef3f7ab3ed63d657d0c755e0dbe5b8a58342e37bdf09bd2e"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bab80af91cd7014b45d1089303b5f844a9d91d7da60eabf3d5f9694b32a6655"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e49ffdb714bc990f00b39d1ad1d683033875b5af15582f60c1f34ad3eeccfaa"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca877240e8dbdeef3a66f751dc41e5a74893767d510c22a22fc5c0199844f0ce"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87e6843f89ecd2f596d7294e33196c61343186255b9880c4f1b725fde8b0e20d"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e20bc5add1dd9bc3b9a3600d40632e679376569098345500799a6ad7c5d46c72"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:ee6ff79a5f0289d64a9d6696a3ce1f98f925b803dd538335a118231e26d6d827"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:52d35cfb58c26323101c7065508d7bb69bb56338cda9ea47a7b32be581af055d"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d14cc5a6f260fa78e124061eebc5769af6534fc837e9a62a47f09a2c341fa4ea"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:4f7ff859d663b6635f6307a10803d07f0d09487e16c3d36b1744af51dbf948b2"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:8ef749be6ed0d69dba31902aaa8255a9bb269ae50c93888c4df242d8bb7acd9e"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-win32.whl", hash = "sha256:d93ca72870133f86360e4bb0c78cd4e6ba2a0f9f3738a6486909ffc031463b32"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6ebb2668afd657e2127cb40f2ceb627dd78e74e9dfde14d9bf6cdd532a29ff59"}, - {file = "pydantic_core-2.46.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4864f5bbb7993845baf9209bae1669a8a76769296a018cb569ebda9dcb4241f5"}, - {file = "pydantic_core-2.46.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:be3e04979ba4d68183f247202c7f4f483f35df57690b3f875c06340a1579b47c"}, - {file = "pydantic_core-2.46.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b1eae8d7d9b8c2a90b34d3d9014804dca534f7f40180197062634499412ea14e"}, - {file = "pydantic_core-2.46.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a95a2773680dd4b6b999d4eccdd1b577fd71c31739fb4849f6ada47eabb9c56"}, - {file = "pydantic_core-2.46.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25988c3159bb097e06abfdf7b21b1fcaf90f187c74ca6c7bb842c1f72ce74fa8"}, - {file = "pydantic_core-2.46.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:747d89bd691854c719a3381ba46b6124ef916ae85364c79e11db9c84995d8d03"}, - {file = "pydantic_core-2.46.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:909a7327b83ca93b372f7d48df0ebc7a975a5191eb0b6e024f503f4902c24124"}, - {file = "pydantic_core-2.46.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:2f7e6a3752378a69fadf3f5ee8bc5fa082f623703eec0f4e854b12c548322de0"}, - {file = "pydantic_core-2.46.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ef47ee0a3ac4c2bb25a083b3acafb171f65be4a0ac1e84edef79dd0016e25eaa"}, - {file = "pydantic_core-2.46.0.tar.gz", hash = "sha256:82d2498c96be47b47e903e1378d1d0f770097ec56ea953322f39936a7cf34977"}, + {file = "pydantic_core-2.50.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c531166c42ea7bdfecc8c50049581f05dd1993b09cc7c52bb36a14e96deaec7d"}, + {file = "pydantic_core-2.50.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6d0c2183008c188e19f4906d426b293bdc4f67ab17df8e180fe16cda208fa71"}, + {file = "pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94be440c03fede26969a5ce75468e0e6a9927a1b46d9b679ee8adc1b057b0350"}, + {file = "pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:36c426eac0af8d1529ff8467e612b933346caec1fdc0d774f78f67a1a11e16c1"}, + {file = "pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:028e2f212273d4a39b1ec1e0de8166b1165a65fc0f1111452a9d94fc7c625c63"}, + {file = "pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6f0cc1bb9900dc558960894adeb30b0c083366fc1d69b856209fb2ca5c36fe5"}, + {file = "pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8812592c85d0edf423f10eadcef42716d71e8219085ad9e85b775057b7306133"}, + {file = "pydantic_core-2.50.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:bbce99252ba3167b2b6277f1829d5bf4b43b754524bddf7f944707c3db7d2253"}, + {file = "pydantic_core-2.50.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:476f6ed8e43cd1e0b460920e23571700872b284e77331cb30c4faf459cf48a4b"}, + {file = "pydantic_core-2.50.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2cbd1b75b09e976ed0d6b6ca297675632ca35df86130088457cdc60ef36970ae"}, + {file = "pydantic_core-2.50.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:5958c72adb417c39b12ac87525ac60b0d73315fcdc59e21f44ee4a5e2512c9ef"}, + {file = "pydantic_core-2.50.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f9e8a6c4ab04b78d61f78627370d834eb004b2869dcb28cfffa647b4ea1980"}, + {file = "pydantic_core-2.50.1-cp311-cp311-win32.whl", hash = "sha256:4be846f55c9477f5f3ddde8f2ce941137e16862a56d018ed885d422bb6ae02f2"}, + {file = "pydantic_core-2.50.1-cp311-cp311-win_amd64.whl", hash = "sha256:0048b6dddc8ef4b64fccaad878bd143b0c3882ea9936279dc11d613f6b7dd1bc"}, + {file = "pydantic_core-2.50.1-cp311-cp311-win_arm64.whl", hash = "sha256:6a733778df2f7087ec1100ed0b41533e4f3001976e99570fa34f57c66e7f8e3e"}, + {file = "pydantic_core-2.50.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:704075d10b74f2f3c6e15407c696d88701df35fc8953f434a431add0d0074db0"}, + {file = "pydantic_core-2.50.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e8e1d6ce820aa23317e8209a86bd65a540973c12dc7552b48a4f6c8e9926815e"}, + {file = "pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c18db21573bd2c6489f9a544b7499f0df2853958c568e5e783536ee1f690af41"}, + {file = "pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb57f304525a5e3c13333b772bf9a473f36326e9c821b2e8e1b2fd36f80ae2c3"}, + {file = "pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a27c09d86600f1bf2fe3f37e1ae697faf3143931c09322cd799da94deee923b5"}, + {file = "pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46b3301d3b5c886f77de7546e47274a5842c622ea2020b8c6524c6b66913b4a6"}, + {file = "pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ba4e9d8210d941c200431a56b2c0400b131865947903937ed3ec5404307d2e"}, + {file = "pydantic_core-2.50.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:e5faeaee74a57d32b3ab3aebad2e348f06d3ba946fc5d28c1728455f00a3d13a"}, + {file = "pydantic_core-2.50.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3cda0e538208e5d722bbf3698b24f19c0a7d05bc8d5f8a7f9b121ea7fa243d9"}, + {file = "pydantic_core-2.50.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:57f51b31ff826e2859120cf4737c5a758a48d96f3e97da40ccee1796d58078ff"}, + {file = "pydantic_core-2.50.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8daa7ee75245d43ad7d747e5c9ecc1b1d06552f72b14887e9276f787d57375f4"}, + {file = "pydantic_core-2.50.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:acbf31f37c53a5ac0c34706c80b4f5107ba20b05fdd3816124bf236ef0c57dd2"}, + {file = "pydantic_core-2.50.1-cp312-cp312-win32.whl", hash = "sha256:45b11cac094aa25725581d9304eee93c9028516b9ea80dd9e175e13a5a2c840e"}, + {file = "pydantic_core-2.50.1-cp312-cp312-win_amd64.whl", hash = "sha256:132529c83901437ff642f585216831bf5fd7a91df66829907e155192ead62498"}, + {file = "pydantic_core-2.50.1-cp312-cp312-win_arm64.whl", hash = "sha256:4e834f6a8e4ff772dcc34f58ef5504147a3ea5b0f4eeb13b0f8eb2ca75ac57f1"}, + {file = "pydantic_core-2.50.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d5e062c01286d861fd6a1c4ff6e063547b3e713067f2df033c0ff97ac2ca006b"}, + {file = "pydantic_core-2.50.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c003c3b7f49debb893d2d85ae099ac5959c9839e2f330fadb1fcdf7a6594482"}, + {file = "pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:409e0ea40ec30d9158f33574fd758e689f6045a0f2596701828c27816ca9687d"}, + {file = "pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:131059670f1d2444269b8585cb888963994871932447c08b39ac6a51fcfef658"}, + {file = "pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dbcbee53bf17196a7f745aa9bf5a9603953a1e365b1f020be3207c676a3e7c4"}, + {file = "pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:325c23f3e35cfbf0fe3486fa5f7260d1e45885173002d30a28ca019994124255"}, + {file = "pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17e722e156d0444ecaefbe640bdb60928752bf2013e2b7a11cdb099aaae19bec"}, + {file = "pydantic_core-2.50.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:aa8224f10880d9bf1b5993988ba153d42a8b4f3f4f511f93b1f09c93ff613c72"}, + {file = "pydantic_core-2.50.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:41bc8237121bd8dc8d888dfd6279fc166ffc88c1f1bf3a8bf00869680533ca4c"}, + {file = "pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:45c6266d071c241f2a168d45bf8c54344f0effce35e7e6b73afdec11f3687568"}, + {file = "pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:1deeacb112d14d3f4fcb16b165f7dbaf76c70ba6e82f37ba042bdab51970a0b8"}, + {file = "pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1c96fd793b73d1b92e65570132505498fe7b21eaef73cdf74e67e5dfba7ac9e4"}, + {file = "pydantic_core-2.50.1-cp313-cp313-win32.whl", hash = "sha256:06ead20d39ffd6f2f6f2a8f8a6de67ff8bb1b4f14a8a30e058502514ee2ac685"}, + {file = "pydantic_core-2.50.1-cp313-cp313-win_amd64.whl", hash = "sha256:7816e98acc08119dc0f340ab167048ecc54126316330c1f0caf7c6756c88e28f"}, + {file = "pydantic_core-2.50.1-cp313-cp313-win_arm64.whl", hash = "sha256:c17799a62c142d61b8a3c51752a7cbc87fe2ad4ccfab10e628a77b405075c662"}, + {file = "pydantic_core-2.50.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1cf41f1ae3fa155cf167a72689ad044bcc1e3c97e064123677149bdfb5dafc4a"}, + {file = "pydantic_core-2.50.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4df197990c15b5a37c5a277d131d9f2c67de6133f2e5dafd80d9bba4b99f46f9"}, + {file = "pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0036473f5583e6a60e50b8b21651511564277a3f05cc5dab8cf579f552cd5f6c"}, + {file = "pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:992c3514ec891fa7858099183e4d64e6bd5a5d4ff452fae29df22faa77a006bb"}, + {file = "pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:739dc730e6be3bd5ec2f4ab5cfc7eb047cc45fc1497b3bafec74ff2ed07df597"}, + {file = "pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fad3a91e51b6d2039c572db04a5a873260b399f6bd62c3552671fa7a4a2899"}, + {file = "pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42b54c2c90ad348b5e3a85e03e715d572c1fde357ef104cdfe3b03b697a404ea"}, + {file = "pydantic_core-2.50.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:2df1ff41884de2bc4b307bafd7c40a691094fad2ff8e767e5b45a319257bcf4e"}, + {file = "pydantic_core-2.50.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe90228920fd8ff2be62622b6bb8a2b11acd65046d50c6b130614b5879605a20"}, + {file = "pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:844b869f118e22a41a091bdcedda8a71bc1b0f62c38d1a0c3211cece47e1d8fc"}, + {file = "pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:2eb75304506894a281d346220a4f7481a1b8729577c5ed2a05395991966a8396"}, + {file = "pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6b20a4bffabdad0db2927ac034ae3b8a681b1f7a0182f3e60b479ad2fde21ebb"}, + {file = "pydantic_core-2.50.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:99ba9bc2b8062ea0c326a990f7f00e6530c23579de66dd246e72c4cafef950a5"}, + {file = "pydantic_core-2.50.1-cp314-cp314-win32.whl", hash = "sha256:cf356f70551d40374eaffb1aa63f1eb6d2006681cbd7a9faea173ce0f4dd7cd2"}, + {file = "pydantic_core-2.50.1-cp314-cp314-win_amd64.whl", hash = "sha256:d32f3acc081cc3923386d88f422cde8892335e95f034e0104bb4cf9310d9915f"}, + {file = "pydantic_core-2.50.1-cp314-cp314-win_arm64.whl", hash = "sha256:bed5163e03b98bc1fa2eb05d74c63d9c5c95d8ed6254985481640fbf5e237dea"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:9572c1369e9c9da2d64a7b7992c786d90ff295abc93964cfe3125e4290768070"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2005207aafe1231315718bf6ed5d064a7300fb4772754af35ee72fc68159492e"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f6047f62a6c5ae08d0a6afb035667aa2d97c3d20d69762e034c5ea144d92a5"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ef800dd7d85bcdadf4c3076e4c94e43939493558a3b69a1ea830c706d4617bb"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0135bcdcaa0f23573f286e4cb5e0fd2962700964ed13df085b85f2b97aeab9e"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b3a6f334c6a2345ca15318ff894502a90012536404b37c844a976c76c846e0b"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06e01fbbfdb9be777b316a71b6c49efaf4a08b615d0a98d678cda3023f79d019"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a29a061fec0b4e2d714f277e70a3a18125ecff803f2fea6eade2f2e53711d112"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f5187624823423e1d1b82b1072ac41dc837389e18d3d0572cc19bbee46cd550a"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3e46a9eb0a0901dd6275e6b06ac3a464885ef350ec4121fe486869de8053e4bb"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:756d669f04e62ec4148ecfe22be6a4484d9b1181a6ef32e205ebfd200540858b"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:c516cc5367ca3448995d42cb994bf3f4c9002d2a7c22eac9622551269ad1b807"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-win32.whl", hash = "sha256:9d1bed94af6a63835461f3cf7502058eb166c58c4778e11d0f433cfb1bd69e19"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c8dce1f1e0e5358b682a6ad3fa5e31b31d4560997b8e61417e9217c8d60f8a0c"}, + {file = "pydantic_core-2.50.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ceff0acc940be2715bd6ad17b24c0e5304abf44f6efd0f81ee8499e640f9dc86"}, + {file = "pydantic_core-2.50.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:8a6791afa2245e6c6b180122d105941644f5bd410bb18623b408808cc41a3102"}, + {file = "pydantic_core-2.50.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:84f34323a61a365b4e9295de6028474754829aaddd59c7bf1a040e7487ef8f3c"}, + {file = "pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23edad659e8dbd8ca7e4e877fe6c81573abbdf215bd25a68b53e1272f58b80c7"}, + {file = "pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a5fce22f1e87d181e924e12da7d81cfe031fb3881a5ddf26ad28f141756ca43"}, + {file = "pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c73622ef819328873b53109ee4f77ceb598bffedd02daf916102be3228866b78"}, + {file = "pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce8c25ca38cc0e3d7753ba180808de2c0c8cb24eae0df64491e40921454e9831"}, + {file = "pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7689580e72a642ab5ec64d5f55b2e33636fa43b4ebe63c0c2c965ef307c7d1aa"}, + {file = "pydantic_core-2.50.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:d5c0e32fdbce7f1e8ef4d11f655694bf5f4175c757a9f1dc2be09b8864e5bcf5"}, + {file = "pydantic_core-2.50.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40f523349960fa30f3ea51404308ff50f9997a90df639590f47a057c1f32b415"}, + {file = "pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:d4193206b6587047437f6f11d7e776df23e1c1e23af2a54d9347275614791e10"}, + {file = "pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:84bc765b282a9d5b7fe0348b8648904f25a6a04b2139da52b1dd30c8ac3a2c8f"}, + {file = "pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:ed1e728b39a383c81035b2459cfcb35d99dfb01f7d6ebe3a913bc1cc5b81e459"}, + {file = "pydantic_core-2.50.1-cp315-cp315-win32.whl", hash = "sha256:bc94f474417604bd383d2cd445d071b07dd55fedceed3ce33407bf1fcc107290"}, + {file = "pydantic_core-2.50.1-cp315-cp315-win_amd64.whl", hash = "sha256:983a662de2571cb2502fc8ff47b6770b03d025d2eb314c92f77b3f07c74720ed"}, + {file = "pydantic_core-2.50.1-cp315-cp315-win_arm64.whl", hash = "sha256:94845ff54dc5193f228cab81b2662a04bfbb892e95bdc15edf7399000ce57d54"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:4a53d13cdfbedbfa87f08b83c1a0a5efcc767d785a4b41934fa9cb672670493a"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:efbecf43d321f7b9281441f1f213f7c21c66988b0e06c2730ba13ed47a46bb08"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc1f08f68dac9f9e83845a8039880aba2ab553eb9b2259c3243a313182c253fe"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5dfe41f232befddb9c4377f6cfc702b51595e2d78ed082672adf8758d2c4619f"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adc06d218a1cadfd2ec4628424d7d79ce4eba69c2965e7e7b55106f0da5208c8"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cf91809d0721ab81592ba67bea7694821679c10b1a2e3c3460082b286c1918a"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23923ab9292c40da026330b1ecf4dc2618c8e86e0422e5d1fbf50d94d64ca4f8"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:f3377c8c2b3ce898423c5e5dd94c7982e30aa7717a7e6ab2470b9de364963709"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:455a773617b5913bf5c20d0692e5787b119e52c4d40ea644ca31f5758fd31be2"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:1a9006395dece0e32e704c315eff8a00bede494f6108546cfc5539c89fef4f9a"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:d2d82aa62521c55ddfb000ae70f88cdd8de974078f6024e821dfe5addd0c818f"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:009634b83993777ddcd69cad0ffcace43dabde692109528e35f0fde91e386a8b"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-win32.whl", hash = "sha256:3fde4fdc6487a58d944ca87cf5adc95d5f266e872c19599f5f4c0a8a1b1f9f9f"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-win_amd64.whl", hash = "sha256:1c8632d4ac04e6f91128fca584b3a8a507d81604c24eeaaad00d4be42765c32b"}, + {file = "pydantic_core-2.50.1-cp315-cp315t-win_arm64.whl", hash = "sha256:c3ede305158e75510be50869b319550ab072008c13d64d4ab1e094fb286b6f44"}, + {file = "pydantic_core-2.50.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7456d699b13954e9c0164dcb267250a10ae0dfb03e6e26d6796ab0d46e189c84"}, + {file = "pydantic_core-2.50.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0d955195bbbe489ad343fcc956eacea9357b79cb22192c66cacdefcbc14b32f"}, + {file = "pydantic_core-2.50.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2c634642694e6a0dad2ab1d375589fa671fd442edd5caf7d9737b8f6ca22906"}, + {file = "pydantic_core-2.50.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ee6db2fbed51a7991302e8fac498cd67e336246026d0dfa84cf5166ce1412760"}, + {file = "pydantic_core-2.50.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:79490e33c4c0fcb933bbbcfc3a62184d8803b99f535863dfbb925e1bcb6945ad"}, + {file = "pydantic_core-2.50.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:48569b0ade9edfbe065cad1d700175546592aebbb42f02adcebcc26e75b896fe"}, + {file = "pydantic_core-2.50.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5f3cae32fc46121f787cb2486de9cf95a8bf72aec5cc78f64c606fa1735a6ef5"}, + {file = "pydantic_core-2.50.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7f476456ac2bb0d937f75191494a09c83a30765fea4f70f3b404942fe25f6cdf"}, + {file = "pydantic_core-2.50.1.tar.gz", hash = "sha256:e50d7b94baac6c7d09927fa5ca5800a0c7ee5015c7fcff65beb3a1931b5a6e09"}, ] [[package]] name = "pygments" -version = "2.20.0" +version = "2.21.0" requires_python = ">=3.9" summary = "Pygments is a syntax highlighting package written in Python." groups = ["dev", "rich"] files = [ - {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, - {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, + {file = "pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9"}, + {file = "pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c"}, ] [[package]] @@ -1180,13 +1244,13 @@ files = [ [[package]] name = "python-dotenv" -version = "1.2.2" +version = "1.2.4" requires_python = ">=3.10" summary = "Read key-value pairs from a .env file and set them as environment variables" groups = ["cli", "dev", "dotenv", "full"] files = [ - {file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"}, - {file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"}, + {file = "python_dotenv-1.2.4-py3-none-any.whl", hash = "sha256:42269a8a5b3fd54ffa6f3d84b18abed50064717576b4ecf03dc4a55d8aa04fdc"}, + {file = "python_dotenv-1.2.4.tar.gz", hash = "sha256:f0d53e69935a851c0dcc78f3ab7aaccd8cabef0b92382b576b824212902873c0"}, ] [[package]] @@ -1196,11 +1260,6 @@ requires_python = ">=3.8" summary = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." groups = ["dev"] files = [ - {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, - {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, - {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c"}, - {file = "pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7"}, - {file = "pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2"}, {file = "pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440"}, {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc"}, {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d"}, @@ -1273,34 +1332,34 @@ files = [ [[package]] name = "ruff" -version = "0.15.10" +version = "0.17.1" requires_python = ">=3.7" summary = "An extremely fast Python linter and code formatter, written in Rust." groups = ["dev"] files = [ - {file = "ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f"}, - {file = "ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e"}, - {file = "ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1"}, - {file = "ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e"}, - {file = "ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1"}, - {file = "ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef"}, - {file = "ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158"}, - {file = "ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0"}, - {file = "ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609"}, - {file = "ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f"}, - {file = "ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151"}, - {file = "ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8"}, - {file = "ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07"}, - {file = "ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48"}, - {file = "ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5"}, - {file = "ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed"}, - {file = "ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188"}, - {file = "ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e"}, + {file = "ruff-0.17.1-py3-none-linux_armv6l.whl", hash = "sha256:1796c41eafec66f4635125dd1e074a7aa27b8d20c187d484360acf11d247fc4a"}, + {file = "ruff-0.17.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8eadb09a788010be29c895418bdfa513e4e1c65e64b94e5b19e4cd381f6cc7fb"}, + {file = "ruff-0.17.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:29b454f18ca3eeaf37fbf140841dcf27d36109e29dee9aae8acecb68c26c6f74"}, + {file = "ruff-0.17.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872e03dd231744d6c5ec5c8af6be52b71323fc6ee096e4c8b34d9f844ee3b978"}, + {file = "ruff-0.17.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c0d6cb5f8fbac77eec9af49f4fc0eaf856b05545e372b0694d24b4e76b1c640"}, + {file = "ruff-0.17.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78beced4a3c94dfedbee4b999b95b6a27b35820fdc0afbacc6d37a8da26c1cd1"}, + {file = "ruff-0.17.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7feefc63921202894f0e158b0e32f45a5950d70a7fb693ccb9264816c25b1c05"}, + {file = "ruff-0.17.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684154861a819bf1f433669d2a0ce9c3e54677c65b8ebb866cc2a7ec86345785"}, + {file = "ruff-0.17.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6ef98e51de8ef38082f90653ceeb84944bd7673783eea13e1647c5b9b064189"}, + {file = "ruff-0.17.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8f9aa5b5bd5fa1937803ea609c8f153b8de39bf3e0e149897ae34b0c5b52f467"}, + {file = "ruff-0.17.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:80e53b0e5faa3c5667f035daaa032893ae42b7cfe708c886d39ed131e54688ab"}, + {file = "ruff-0.17.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:056c5d5fa0ac790051ae165d7035c7bf956d02de2db557f2e9b31d877a505fca"}, + {file = "ruff-0.17.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd8f5a55b10f371642eacd0e8665a42a8b97da15c9a7187c79e697b77b5e0ce4"}, + {file = "ruff-0.17.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ce05e6f3973611d9550a600e04bb531061e368baba31610eb57c1509a8f21a0a"}, + {file = "ruff-0.17.1-py3-none-win32.whl", hash = "sha256:1de0030a8aa78c65c5fecb84b2e5c390ac684c0452875a02afc7c083d49a496a"}, + {file = "ruff-0.17.1-py3-none-win_amd64.whl", hash = "sha256:5df76f16580fab58d0f6c98a3ac254a4abceb7b2489b8b678335869cff77b3d7"}, + {file = "ruff-0.17.1-py3-none-win_arm64.whl", hash = "sha256:42400b9a6ff8515ff27796f07f92e0195be011c86e81e381606f58ea5502fef4"}, + {file = "ruff-0.17.1.tar.gz", hash = "sha256:5bb796c5112e9fb9527f2ef2faa0a130d6312d19de92bcfaddd70dbb2d41020f"}, ] [[package]] name = "satori-python-client" -version = "1.3.2" +version = "1.3.7" requires_python = "<4.0,>=3.10" summary = "Satori Protocol SDK for python, specify client part" groups = ["default"] @@ -1309,17 +1368,17 @@ dependencies = [ "graia-amnesia>=0.11.0", "launart>=0.8.2", "loguru>=0.7.2", - "satori-python-core<1.4.0,>=1.3.0", + "satori-python-core<1.4.0,>=1.3.6", "yarl>=1.9.4", ] files = [ - {file = "satori_python_client-1.3.2-py3-none-any.whl", hash = "sha256:1f6b7714d906fdc8df3cca929d5cfe06a7eec5f8183429d3378ae78d1b0cf511"}, - {file = "satori_python_client-1.3.2.tar.gz", hash = "sha256:da656e7132ba8d10febc5ffbeee0c73be68b65cfd69cda1c5f2a85293f1d6c85"}, + {file = "satori_python_client-1.3.7-py3-none-any.whl", hash = "sha256:748470043b23f8585b894b36d3d333a6b92759da47b99069c0f9c3edc078505d"}, + {file = "satori_python_client-1.3.7.tar.gz", hash = "sha256:94343cd06d00a23bf5951c9f5c25efe43aec25c7a49e3ef521ce603e3207a7a9"}, ] [[package]] name = "satori-python-core" -version = "1.3.5" +version = "1.3.9.post1" requires_python = "<4.0,>=3.10" summary = "Satori Protocol SDK for python, specify common part" groups = ["default"] @@ -1327,19 +1386,19 @@ dependencies = [ "typing-extensions>=4.7.0", ] files = [ - {file = "satori_python_core-1.3.5-py3-none-any.whl", hash = "sha256:ef9d6ecfb002d9808cf7d2b9b6c391529c9a9673cd2d63aa3887f7f91e63d985"}, - {file = "satori_python_core-1.3.5.tar.gz", hash = "sha256:898620707d72cdcb838476b42a253ffd7694349ce8e5a303a058366f02074a0d"}, + {file = "satori_python_core-1.3.9.post1-py3-none-any.whl", hash = "sha256:4c0f6a3d9cb455f695473ed78fb8ffdd78e8d485c184baae8b9d82d0a2c2449c"}, + {file = "satori_python_core-1.3.9.post1.tar.gz", hash = "sha256:61058ef4357f6c687d7dccb61bba031d1a44a7e988c57a0ca897ad5a64b4de5c"}, ] [[package]] name = "simpleeval" -version = "1.0.7" +version = "1.0.8" requires_python = ">=3.9" summary = "A simple, safe single expression evaluator library." groups = ["default"] files = [ - {file = "simpleeval-1.0.7-py3-none-any.whl", hash = "sha256:97ac271bfd8f2af9e7b9a36ceea67617f26fa873f9d5ae1922f64d4c1442534b"}, - {file = "simpleeval-1.0.7.tar.gz", hash = "sha256:1e10e5f9fec597814444e20c0892ed15162fa214c8a88f434b5b077cf2fef85b"}, + {file = "simpleeval-1.0.8-py3-none-any.whl", hash = "sha256:c9c4ff42879ebf0c05f6562cb81031869948916be343bcc950b81fcc081fe9ce"}, + {file = "simpleeval-1.0.8.tar.gz", hash = "sha256:b417f869c11f2de034eb919820a825f35d12ce99f63a43971947ce73679eb9ba"}, ] [[package]] @@ -1366,7 +1425,7 @@ files = [ [[package]] name = "tarina" -version = "0.7.3" +version = "0.7.5" requires_python = ">=3.9" summary = "A collection of common utils for Arclet" groups = ["default", "cli", "full"] @@ -1374,296 +1433,153 @@ dependencies = [ "typing-extensions>=4.4.0", ] files = [ - {file = "tarina-0.7.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b345a40001a26fc9ee8fd5ec20dd0ddbe1187296362cf7edba60e6fcd9bf8a07"}, - {file = "tarina-0.7.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90a6e53a1dffc6f29c89b752610065156d84ca488c6f783b0e261485519fb2fa"}, - {file = "tarina-0.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59fed29c436ac397a38d58a07428ce50b801a53f9701239821694d3f89a8c82f"}, - {file = "tarina-0.7.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd9fb5752070102b7294eea84b4e8bef759c468440595c0fac591427627c0e47"}, - {file = "tarina-0.7.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d70f1f770fff2b10a38bbc1410ea75a79b9e9a5f86b26fcedbbac138a12a3d2a"}, - {file = "tarina-0.7.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:13509529473d69863704735aba22d7370297099b46d55d0114504cad3f36f105"}, - {file = "tarina-0.7.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8bd84e9dfdd5400265a30d2eda1d79f9e51a73d6223a3c016a6c1b11f551b450"}, - {file = "tarina-0.7.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:366cbac04bee94b602c5f98afc79de3874257a78ed802c6b096e072df3724cbb"}, - {file = "tarina-0.7.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8e8a3c1e8f429b035068f299f3eec9f0659e8f53921beb299785f59610ca665"}, - {file = "tarina-0.7.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6a21a01a842e0ef983b576ad6322ebc8649fce411c6820444f5f80a465322a74"}, - {file = "tarina-0.7.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6428e5b19174a81f2b7bc852f7442728754ca693962eda41d5262bdb4da469c2"}, - {file = "tarina-0.7.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0973d2ed747e9721e734475d1cfc19ef489c8791e753273ffe5efc93f722bc3"}, - {file = "tarina-0.7.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:81c1f80ee26f82827229c4aa4d96bb08a269d44d03a51ce273b7f7509b9f2813"}, - {file = "tarina-0.7.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2c3f3e63e38ff27f39ab154a307cb393384fc2aa1ce037dc0745ae064718605f"}, - {file = "tarina-0.7.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:86363e783050289d7dfe9fa63f272a695042d362f0e3dac542064bc534bd1242"}, - {file = "tarina-0.7.3-cp310-cp310-win32.whl", hash = "sha256:7c090ee035647284f73cd5437ac7c51b5468036290463212b989e2df7b2638c9"}, - {file = "tarina-0.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:68e5e5938da7cd7b491a21549a0e4a7c2aa313ac92decb4aa4281f6cc5143816"}, - {file = "tarina-0.7.3-cp310-cp310-win_arm64.whl", hash = "sha256:54c304c87e1b5b4079c880fdfdcfa4194de65f087643686c82d592e616d931ac"}, - {file = "tarina-0.7.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:66a2a2140d3e11cbcaab4e4694bb248a80b68cf73ecfa847c43955110bad20d4"}, - {file = "tarina-0.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b33a28c40bca7af2a665930108a5f959369c44fdc4046ed9c4264dea630bee24"}, - {file = "tarina-0.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5ab958912fd524c7ed548b65f32033415514b9b81f8504545a1d3a891e7b2bf"}, - {file = "tarina-0.7.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ecc3c1b1b3c4f89bd4208d1d1f71a23d17e07238abef802f22102b73cb2b4b1"}, - {file = "tarina-0.7.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a5f2103ab04689923382e0bc04796b8ec25f5f36047717d4a0048c4791ac2bb6"}, - {file = "tarina-0.7.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a1282feca6bf448ceaae43d18f9828ebe6e9b04c0b445df254a2088514961118"}, - {file = "tarina-0.7.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8ef61e421a6209055cccda50fbd515315d108359df665af08f1777060c566623"}, - {file = "tarina-0.7.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21793dc2ea64760b2b74bbdc09b1cd9eacae3379aefef9d35ff3357cea5a94ef"}, - {file = "tarina-0.7.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08d94a27b67da5df9b3c5e068f2e9ccd2f0e9e8f166f617ef2610fe68d821213"}, - {file = "tarina-0.7.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0985b40e113499cc1bb249c432b569cb20a139bbcdf0c406f3d3f8f1816804b3"}, - {file = "tarina-0.7.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4795179ef82001668828d9a55902961df3e3c257f66c64e388d154ee8451b5cb"}, - {file = "tarina-0.7.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bcd1220bb6a1b71bf0301626eff3406397d49c083b92bb12545d47a116c090a2"}, - {file = "tarina-0.7.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:267679e15e403721fcf43a3bdbdb1c74ba41bfd405b11016f8f86f7e477bd88f"}, - {file = "tarina-0.7.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bd37ed89039444e86cbf95b2982fd0fe247d50ce3996720b685d09f63f66eacf"}, - {file = "tarina-0.7.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e6ccb72ea38623b43691ed20ec989cff92ab07f2af4b79e74b7bd2f6cc3272f8"}, - {file = "tarina-0.7.3-cp311-cp311-win32.whl", hash = "sha256:75fcc342e6071c1146d1c829ef63804e16de853f4d1b6076212f796cd7cd2cba"}, - {file = "tarina-0.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:4b5c23c90a8513a5123afca6a663e5998fc9a105d84cc2a374886727cf819a59"}, - {file = "tarina-0.7.3-cp311-cp311-win_arm64.whl", hash = "sha256:a7212eb245ed20fb50adf7b54f0c6e45fb7a03d509dfd8ead1bbd120955fd6ba"}, - {file = "tarina-0.7.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ffb298072195507d829624315cf43545d0e4068ee22eae10de4e14803cb10c80"}, - {file = "tarina-0.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3e07bb111d76c5ac86d14e7abcbedb613d5b5d19dd7fbf16564296d498cdaec7"}, - {file = "tarina-0.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0f9ae4d26926fbcea8479d69af0b946f53af9f69277a304602e85c2f7b06d89"}, - {file = "tarina-0.7.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87619b5f38fff4c3d707634f7e187c8f981370742667176532ac076a30112bf7"}, - {file = "tarina-0.7.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2eb0abc15491d0a94539182bbf42a27edf2565ae83a39fb6e50557fb33c8eec2"}, - {file = "tarina-0.7.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1223a5b426b9c48aed11c3420a140b73c7e90ae43da82112c07c87fc996e7c06"}, - {file = "tarina-0.7.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:88e3b9e8fdb34c12464f9b837a2cca371fdd9544a6ff3f21a8f8ed2cdbf01641"}, - {file = "tarina-0.7.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92ecb77054060a9b96ea8fd38a9e3332fb33b261c12321b82bf9e564d40a5b65"}, - {file = "tarina-0.7.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ee541ec5e19baf3503fad0977560834214b1e00fcfc3c72e0eb6447c001b0919"}, - {file = "tarina-0.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5363d16571f4091064e9cf04ec44711e42efdac89bf1c5a3f915345e84beb908"}, - {file = "tarina-0.7.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e22f4f9808049f6781aa3991a1e444424422137990d0f44cdf2cf1f50e911b53"}, - {file = "tarina-0.7.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ab5e0f3802d75f0bf5ed892c5e8ba6cdc6d52a8ec0d1307beb3d56d5a2acf0a1"}, - {file = "tarina-0.7.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:0a37d0e75e1e4db0239c6bee4bae592cf16f3ea750df44621dab709e80686a17"}, - {file = "tarina-0.7.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:965e7250df8793092eda34e2b73a8a6840dd7bb0ef362a5b698a90b55321d0ed"}, - {file = "tarina-0.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74e728dd1f908db3e1117fe2c62a24b3b2201b7d476321b769474fc8f7dcad6a"}, - {file = "tarina-0.7.3-cp312-cp312-win32.whl", hash = "sha256:d0204afc894f28b99236e5d1a18376df33ea5a8b3f887cdaadd79f8fd6335c95"}, - {file = "tarina-0.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:7736745effe2221be05ec7b09aefe17db67e964472048e2ee991ba27de55a278"}, - {file = "tarina-0.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:85c93795c149a057b9dca46ed05c9d196ecd83e98f9c2e476f3893bcb94056d6"}, - {file = "tarina-0.7.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:63601f006b45e2448f649e62688df234c2b7584fdfc18be85728078b4980f94e"}, - {file = "tarina-0.7.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:142ce68803a2808f295bb196850565ac6f030eb59eb94be8b90d0444f0a55acb"}, - {file = "tarina-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8acd64d5c69403f0168bccabcabb703e1dcee094827160475ac443c197ff1f2"}, - {file = "tarina-0.7.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:686f1ec484177c7fddf74d0dc956186770e0c95ec8d955ce83b5c4714b1de2f3"}, - {file = "tarina-0.7.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db84d3cbdc63eb439c6fd4a98dff0e7a4770e82a246214abfa30cb6ec8a11eb5"}, - {file = "tarina-0.7.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbe121dfb10e1f184073c2ef4697b00e049df07f6527abd012c2f95f3901b660"}, - {file = "tarina-0.7.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d3e61fd55df0ace3862380a5f99c3a9bf78d2adcb80c15f03e619cb3595138d4"}, - {file = "tarina-0.7.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b33f611be8414334baea5c0cc0c87ff22b1b2aac834d5ea11333dc7a73aeb148"}, - {file = "tarina-0.7.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83b19788ccf5ee6e13d842a731b8f1485f5266b9aa8323f993c3be93ca4bbe4d"}, - {file = "tarina-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eb00c7a41a8cc2d799b10a91320690bdcab131a53d718965a21f739938d72c41"}, - {file = "tarina-0.7.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a64cfb3e51455ccd6a8bb123665079f8892474d2f38e919705838f52503acd91"}, - {file = "tarina-0.7.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2c1b90783790a27cb77fdb11628aa3bba46d91cda01b6b3fb128550fc1896308"}, - {file = "tarina-0.7.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0b482a5ff0d60ebeb30954800d698bc596b6a810302ecf19151cc410e4ad8f85"}, - {file = "tarina-0.7.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a0440644315f0a14b5d9e99e965c8d34d49593c41aac8d01f7155a622413ff3a"}, - {file = "tarina-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2ccdadbf2fb91f92b87ee01bdc845e22f9d3fc9fecbb1c5b8d19546d2a47dde1"}, - {file = "tarina-0.7.3-cp313-cp313-win32.whl", hash = "sha256:79bb9cdb48df99ae55654fb594e5d4107fe5ad50ab64576130fb0408f105c49d"}, - {file = "tarina-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:cb19ef3fbbd8e41c0fc1d9b8d1b2db17cef0c10bd5248fb0552d480a8f50de45"}, - {file = "tarina-0.7.3-cp313-cp313-win_arm64.whl", hash = "sha256:49b29d8ae77dd786dd78b516287415def76298f210baa64b269409ac72a2f7e7"}, - {file = "tarina-0.7.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5fbf281c82379cbcfe10addba96da8eede58960321cede1749c7aee50ab4475a"}, - {file = "tarina-0.7.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:329e6e359db54d32b8c03463cf84d878dc960e8cf42faf0ba68c3cbac25657aa"}, - {file = "tarina-0.7.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e5c383cb47e2b1db7dc5121706dc8c02f921985d5a6b139b95a1306a96dfdf6"}, - {file = "tarina-0.7.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abb2c4bc39b2ef33fdaaca72f8045b873c4ed35c1388ef4c4170f255f8f4e473"}, - {file = "tarina-0.7.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e007ea3d6aea70f6795163d2a752e541c9bc978c79a49cf07b93ec2f47042d49"}, - {file = "tarina-0.7.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6b341c8d5ebef2c4d6030475666bfb6caef92f326a207758b0018c617d8d30d"}, - {file = "tarina-0.7.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b10cbde503fb60b0224488ac855721fdb6ff95a4a38eec4b8cf212a73e054f67"}, - {file = "tarina-0.7.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:87ab161630b01cb92b8c7e488cbde4268a41c602e00c96560723563c9a6aa908"}, - {file = "tarina-0.7.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:40b1498fd20d07893c11b9b9d8613c38a08cb42018b82c029f51cd28c8ee0f59"}, - {file = "tarina-0.7.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4708ec9e1fa6e8e8ecfba2adc94a123a6f39fbc587166bbb111b32c9048f7605"}, - {file = "tarina-0.7.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b033ee4895e553c6c3a8e001d801c912778316a8efbaf85fbdb4aaff9c099a9c"}, - {file = "tarina-0.7.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d240100555375ae063f0666b42ec8340df89fef6a0a4a3425e6f76029a9b43"}, - {file = "tarina-0.7.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:10358bf5e21429e98598f7e8ff6f92e1edba96562aaa989b2a4c254d156ad0bc"}, - {file = "tarina-0.7.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4fa5d06f56371d430aba7d95ad096ea6e06fd792c74f64593a94d723591e516e"}, - {file = "tarina-0.7.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3b6cba13c954a8d0874a840ed814deae630c21a8b6db136e2ede56282a356b89"}, - {file = "tarina-0.7.3-cp314-cp314-win32.whl", hash = "sha256:afaab73a1251b308998aec4878095064cd4511bca9251963dcd8e8dbb55d5432"}, - {file = "tarina-0.7.3-cp314-cp314-win_amd64.whl", hash = "sha256:c85d5920ac29c3f0be681f6e68a0e92f3cd103afe6da8b04db91bc9e59386210"}, - {file = "tarina-0.7.3-cp314-cp314-win_arm64.whl", hash = "sha256:94e7f2936d82736a213cd540ddd1bd6a2f32531b46e77fc464eccf7bf40e7984"}, - {file = "tarina-0.7.3-py3-none-any.whl", hash = "sha256:f9636e30416ccef9a528d654ffc4495c6a226ae7c38e6f3a6ebed31c6075d335"}, - {file = "tarina-0.7.3.tar.gz", hash = "sha256:e8361cbae353bf8065f892b9c5a3f75f540d05d0fe60535942b0cfcc3cc90576"}, -] - -[[package]] -name = "tomli" -version = "2.4.1" -requires_python = ">=3.8" -summary = "A lil' TOML parser" -groups = ["dev"] -marker = "python_version < \"3.11\"" -files = [ - {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, - {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, - {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, - {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, - {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, - {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, - {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, - {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, - {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, - {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, - {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, - {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, - {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, - {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, - {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, - {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, - {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, - {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, - {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, + {file = "tarina-0.7.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1d49cce8bbaff34ee65fba4fa00a3bccb4df3a130c3746a11e5ac073e164aab6"}, + {file = "tarina-0.7.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbed46995bddb76ab5aed01ff71649ba459bd0085d20228e0c3293f62b9475d2"}, + {file = "tarina-0.7.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a7abcad3e658b1aacbad64bddc6efa9387b20b6b91651491f71d5a207265d24a"}, + {file = "tarina-0.7.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:59466323c7900638df3fd21e23e2e41a034f6b63d99f339a15ed0d211256136a"}, + {file = "tarina-0.7.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:baceb80bde273d4611c36047a3f774fc273ff2a0f76c027a353b88f4bf082159"}, + {file = "tarina-0.7.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:38e07029e735bb34b19ca75f7bb6747336d9ea8cc1ef85a43aceb851743a8108"}, + {file = "tarina-0.7.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:71145cf82654875354c9d4642d47ad4523138baebfaadd2475c2070122da3b70"}, + {file = "tarina-0.7.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:464f3b8a8cdb48f8722a5b6c0e76c019aff59f8aa5b92c1bf3013372a992f90a"}, + {file = "tarina-0.7.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ae093285bb26999ebb5d11f55735c481a91c73a6902385cf727e3f8d77890d96"}, + {file = "tarina-0.7.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c85cb91d3c390e15038b7a956644fac342c15a5499d088ff83215d99b761f83b"}, + {file = "tarina-0.7.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:9c8b812d19ea7298f84fcad439e78d388fd5c9d7abab66e5dcc58603ced151c8"}, + {file = "tarina-0.7.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a2da6f0decc07a30fb22910d99ad6a31003c05fa47f64165ea6b1c6cc65d2039"}, + {file = "tarina-0.7.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:490e9867d788b8a7d9e4d5f993623a9ca0e21cc104ec57b8ce9227cfd8171133"}, + {file = "tarina-0.7.5-cp311-cp311-win32.whl", hash = "sha256:a1068fe72ad3e6ce0d2bf9681627524e9aec5de4ca8dcda2d76afb0363ff612f"}, + {file = "tarina-0.7.5-cp311-cp311-win_amd64.whl", hash = "sha256:9541480471fa14512f33da71cdd43ca19bb655fb9ae6a6abada994314486c4c8"}, + {file = "tarina-0.7.5-cp311-cp311-win_arm64.whl", hash = "sha256:e8252a321ef5bc531ddd600df3164443e6ff12e33bf14af783cba80ab97ab7b6"}, + {file = "tarina-0.7.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:951ac81d13ca2934b8a45c2683dab5f2972a45e98c0c13fbe0204066273665c2"}, + {file = "tarina-0.7.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdbb8476dc116b0516993896ae9dad78a3aff0f0dc086cf9a31da88aa716e27d"}, + {file = "tarina-0.7.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:962feac697114d62ee443ba722f12bc86ddf1f944a6fa0a99254cbce007a7ab5"}, + {file = "tarina-0.7.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cdb3cabbdb300ff1f9ae1906bf4ecea32935f0eb67462c7bdb79ff1d04f24cff"}, + {file = "tarina-0.7.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d18b90f77bc9a9d6ef1d7701e06aa0739ea19341920f4a6d28d8cadcf970ca7e"}, + {file = "tarina-0.7.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5901429fa63091c8e3d9aa4af177cf5222dae4225b22d3961deb72d1026f89a"}, + {file = "tarina-0.7.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:82f2825f181d6da5b99952a781cccaeae79eb176e2ec40593cd2b08614643310"}, + {file = "tarina-0.7.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b898f958d54e7d30c66c1491692510ab36ca0b37c0ca82b5a590e7fb33b624c"}, + {file = "tarina-0.7.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8591cfe7fbeda8a89c8054b5155fa82aa6ddd07493b6a920a60fd191d85c4a3f"}, + {file = "tarina-0.7.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e15566d633e75914a93b0a071ebf5353565ce3cdfc2bf7fc9fffa37bf3c8a3b3"}, + {file = "tarina-0.7.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c631696c2cff881dd0ed210fcf733586e092931cb8a29b88b0e2d3d1ed757aa4"}, + {file = "tarina-0.7.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3962e4efe8400ed21175b996a221561a2e4bf7140550bc2dbc0960ffad29fbbe"}, + {file = "tarina-0.7.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f25641467615d20295de598cf15a09739aaf6987bdf2349e7afd0bda308d2a4"}, + {file = "tarina-0.7.5-cp312-cp312-win32.whl", hash = "sha256:ad427b485e2859bc6e54956203f51f636e10dff99da109cc8d02f9fea9115b67"}, + {file = "tarina-0.7.5-cp312-cp312-win_amd64.whl", hash = "sha256:98a1e0ac974cec6f5c67918b762826997d26274f9385f042f4198184137fb2f3"}, + {file = "tarina-0.7.5-cp312-cp312-win_arm64.whl", hash = "sha256:fa7208294059c6567f55c15e1d673bed1b1d5ada448dc20f2e310263d262b484"}, + {file = "tarina-0.7.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b678fce0c6ebb58e7a620f8ea89228cee7380722c0f91fd842a2b01f7b0f2317"}, + {file = "tarina-0.7.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de3642d874b2e4d5962b2a6b4bdb68a0fdcb053410fc106bed64eedae36fcf57"}, + {file = "tarina-0.7.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8169f0ed7e7fdac47cff33e255c43b02136ae4fa45ff9a9ecdc318d64fd2b5ff"}, + {file = "tarina-0.7.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b7e3471aa88e1b5fdabe1f03cfeb348c9da295fd2a00f07d57d99afb55dea8d1"}, + {file = "tarina-0.7.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1191d073f732d81c8402404bae27877e19d59675859803fab6f224985484902"}, + {file = "tarina-0.7.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d9a1d6e693c58d950e4c79d2b720831ba58506fea0d076917c5607767be2c44"}, + {file = "tarina-0.7.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ccb522abd4618988b304d12992b45cb9d7843362ac93341cd925dfcb7de1ea7"}, + {file = "tarina-0.7.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8518c31fef9ca7a4f0c72cef92b8a17efaaf00fef285802c3d18048691bb5d75"}, + {file = "tarina-0.7.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4a734415b233bb7925c40572cd4dafd2738668304c8c9605441fb6939e1826bc"}, + {file = "tarina-0.7.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:438a79cf311c05bf6931a884836125e35af61d777bece7d458b601bea0d8c115"}, + {file = "tarina-0.7.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:29a009427857ab101051c1f419a954b28deec3892047b91420017f96c03a1ebc"}, + {file = "tarina-0.7.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1fc7f7437898299939ef8426ceb3ae0fdbf5735b525fd03adcf9168514487495"}, + {file = "tarina-0.7.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:23e37f31f039c3a82719dd5459a95a7796365d272c6790f52a225341f06ba3a4"}, + {file = "tarina-0.7.5-cp313-cp313-win32.whl", hash = "sha256:9914d83943a6a33414c8c6b76a3116371a1db0b85768640ac3430cf3c8fc1fd2"}, + {file = "tarina-0.7.5-cp313-cp313-win_amd64.whl", hash = "sha256:9a9e1747f8104a5f6d092688368ecd3187234534caf0bf94b241db53cc8d7401"}, + {file = "tarina-0.7.5-cp313-cp313-win_arm64.whl", hash = "sha256:940f3962ad3964c936a591d84b1b7c9d0e519ef406940a59198fd92bbcd23fff"}, + {file = "tarina-0.7.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:74f8668da4eebbcd49910f8a88546a0b86389c948598e01570bea992e8a8af30"}, + {file = "tarina-0.7.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:895301d752708909ec06d266633c212dc43a08a7ce699b6506b48ee1efb9d0ec"}, + {file = "tarina-0.7.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:189079fb636c42ad9dfac37ee71b1b867f4993bc5d95ee6da441a2150e410248"}, + {file = "tarina-0.7.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c138371b5452e11f5b26d0a2a1fd1fb85ebcbd3d3546b5b8bd6170aefa298eee"}, + {file = "tarina-0.7.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:514c555943b0fc955e4ab7705540604d08838cd1e6197736b871dd8943ee3a1d"}, + {file = "tarina-0.7.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89fe43399590c244aa3971446690ca31a7107e3cfce983e47049837ce45111f6"}, + {file = "tarina-0.7.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c5fc1861f42fb36869ae5aca823a29ccf46f258d40335edfe53a5c406daacaf7"}, + {file = "tarina-0.7.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c1fd975e497465ca28f2214541ca2fd2e7d4104bc6827d75ff94756f383c4e5c"}, + {file = "tarina-0.7.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7e7ceae88d968cc86d4d8069eb8c3c335a3af1c0bfd4d5b0c731da02f44df739"}, + {file = "tarina-0.7.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6384e8f994d821c88bed510f178ddb192f9450d170e81f4b2046a328622cf5c1"}, + {file = "tarina-0.7.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ae0c04f982ec223557ff01a6fab9b734fcc472b86b00ed38c795730fe5b129d8"}, + {file = "tarina-0.7.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:45d8f6320bf2d40c888abb6a9bc435f35e6e8f667d75f65ea57aafd1ac76a42b"}, + {file = "tarina-0.7.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4590697a3a4f9b97d214c2a7de3af370fdf12cad729ba800ef4edbad9467d0a6"}, + {file = "tarina-0.7.5-cp314-cp314-win32.whl", hash = "sha256:23d8e301d599b9b62fbc2663cffb94a4a93cad822b3e1526dcd7428169f87151"}, + {file = "tarina-0.7.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa4344d0172c217ce11fa3318829faa90e09e28a38e3838705e085e200ae2116"}, + {file = "tarina-0.7.5-cp314-cp314-win_arm64.whl", hash = "sha256:714d16892d6258cb7e1dddf19ffe954f62e899e9857bfc1a2a4016c24c7eafbe"}, + {file = "tarina-0.7.5-py3-none-any.whl", hash = "sha256:f8d25cd4953f5613fac478cd9b5af5d598542033f385a45fe5e88e8ddf566af0"}, + {file = "tarina-0.7.5.tar.gz", hash = "sha256:581a1a7c0f60a54aa2b93a4989bbb333fb9d26ffda96b0addf9309b736a7c831"}, ] [[package]] name = "tomlkit" -version = "0.14.0" +version = "0.15.1" requires_python = ">=3.9" summary = "Style preserving TOML library" groups = ["cli", "dev", "full", "toml"] files = [ - {file = "tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680"}, - {file = "tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064"}, + {file = "tomlkit-0.15.1-py3-none-any.whl", hash = "sha256:177a05aece5a8ca5266fd3c448abb47b8d352f09d477d3ca8332db4d89b24304"}, + {file = "tomlkit-0.15.1.tar.gz", hash = "sha256:e25bbf38843005246210a12982776f27f99cb9be67160e14434d0c0d21ee1e97"}, ] [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" requires_python = ">=3.9" summary = "Backported and Experimental Type Hints for Python 3.9+" groups = ["default", "cli", "dev", "full", "pydantic", "reload"] files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, + {file = "typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8"}, + {file = "typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5"}, ] [[package]] name = "typing-inspection" -version = "0.4.2" -requires_python = ">=3.9" +version = "0.4.4" +requires_python = ">=3.10" summary = "Runtime typing introspection tools" groups = ["dev", "pydantic"] dependencies = [ - "typing-extensions>=4.12.0", + "typing-extensions>=4.15.0", ] files = [ - {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, - {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, + {file = "typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147"}, + {file = "typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47"}, ] [[package]] name = "watchfiles" -version = "1.1.1" -requires_python = ">=3.9" +version = "1.3.0" +requires_python = ">=3.10" summary = "Simple, modern and high performance file watching and code reload in python." groups = ["dev", "full", "reload"] dependencies = [ "anyio>=3.0.0", ] files = [ - {file = "watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c"}, - {file = "watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43"}, - {file = "watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31"}, - {file = "watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac"}, - {file = "watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d"}, - {file = "watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d"}, - {file = "watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863"}, - {file = "watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab"}, - {file = "watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82"}, - {file = "watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4"}, - {file = "watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844"}, - {file = "watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e"}, - {file = "watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5"}, - {file = "watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741"}, - {file = "watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6"}, - {file = "watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b"}, - {file = "watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14"}, - {file = "watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d"}, - {file = "watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff"}, - {file = "watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606"}, - {file = "watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701"}, - {file = "watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10"}, - {file = "watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849"}, - {file = "watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4"}, - {file = "watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e"}, - {file = "watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d"}, - {file = "watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610"}, - {file = "watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af"}, - {file = "watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6"}, - {file = "watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce"}, - {file = "watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa"}, - {file = "watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb"}, - {file = "watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803"}, - {file = "watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94"}, - {file = "watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43"}, - {file = "watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9"}, - {file = "watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9"}, - {file = "watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404"}, - {file = "watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18"}, - {file = "watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a"}, - {file = "watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219"}, - {file = "watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428"}, - {file = "watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0"}, - {file = "watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150"}, - {file = "watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae"}, - {file = "watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d"}, - {file = "watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b"}, - {file = "watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374"}, - {file = "watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0"}, - {file = "watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42"}, - {file = "watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18"}, - {file = "watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da"}, - {file = "watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051"}, - {file = "watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e"}, - {file = "watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70"}, - {file = "watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261"}, - {file = "watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620"}, - {file = "watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04"}, - {file = "watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77"}, - {file = "watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef"}, - {file = "watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf"}, - {file = "watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5"}, - {file = "watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd"}, - {file = "watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb"}, - {file = "watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5"}, - {file = "watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3"}, - {file = "watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33"}, - {file = "watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510"}, - {file = "watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05"}, - {file = "watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6"}, - {file = "watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81"}, - {file = "watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b"}, - {file = "watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a"}, - {file = "watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02"}, - {file = "watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21"}, - {file = "watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5"}, - {file = "watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7"}, - {file = "watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101"}, - {file = "watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44"}, - {file = "watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c"}, - {file = "watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc"}, - {file = "watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c"}, - {file = "watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099"}, - {file = "watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01"}, - {file = "watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3"}, - {file = "watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2"}, - {file = "watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d"}, - {file = "watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b"}, - {file = "watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88"}, - {file = "watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336"}, - {file = "watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24"}, - {file = "watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49"}, - {file = "watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2"}, + {file = "watchfiles-1.3.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:000b9688fc8133037a8b075c8ebf98f32844ff8964dda61e85c1db547dafc441"}, + {file = "watchfiles-1.3.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:bbc1198edfdc90fda0600f825aa94150f428dfcbf8138746f55998e0e660d64c"}, + {file = "watchfiles-1.3.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4af9c464d410e8c44b58ddf1cbec7c3a02c1cb2c6c80df9c74659ba2fbda0ab5"}, + {file = "watchfiles-1.3.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb94e40b9a0db19636b5e3fed0a9803452ecc7381df13b385272abb779ee83ca"}, + {file = "watchfiles-1.3.0-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7398930a2b76b9dc67a4e6b8bfc7baf30946892a86f417a3920c07bdd6febc30"}, + {file = "watchfiles-1.3.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:025b108f2d5cc2799cb941f79380df3e8b22bfb589c218e801767fe78d598c95"}, + {file = "watchfiles-1.3.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9ef3cd403d756a304a0a08a112163c63da7ec72c8c9d083c98818291ff8f29"}, + {file = "watchfiles-1.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5768b49e426fd5b550b012c866db347cdf15c398ef98dc557b6e6b72fa74cd1"}, + {file = "watchfiles-1.3.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:d36c72fcdc08f143d87316a4c4c3b064e340da2c7383d39803950740ed418935"}, + {file = "watchfiles-1.3.0-cp310-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f0c9865240e065a2247f4b5248528ae3d01e0e8ae071462c250c884dc16e3ee3"}, + {file = "watchfiles-1.3.0-cp310-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:380f513d26cc2e598266b88d66456e07d67ef4be8f0f435a1154d9c44b43d509"}, + {file = "watchfiles-1.3.0-cp310-abi3-win32.whl", hash = "sha256:509d9f74d2bec5c1f4cd868ddcfe0c9601f7ea53066dfcb19d8ede742c4de661"}, + {file = "watchfiles-1.3.0-cp310-abi3-win_amd64.whl", hash = "sha256:1acabde19b67e673274e89a04d613b7fb1f122e2c4b8ec9a581e59c149264b61"}, + {file = "watchfiles-1.3.0-cp310-abi3-win_arm64.whl", hash = "sha256:d978cc1dd7ba44f5590d7de74f7a9c8be7262d9e469c2c0efb8bbfc2574321dc"}, + {file = "watchfiles-1.3.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e0204f90677b7fb8d3a824279a71a01166bea9f6e53923b8056592d87b33b0b6"}, + {file = "watchfiles-1.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7c1125e99f84934a92996ae343501285617373acd66f16b59fd5654b27ad8d80"}, + {file = "watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a85e243c87109d9f48a2e0d10d6abdee8768cc114848b32281ec83d7f81d86ef"}, + {file = "watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd538c71766c59c732e2c5cedc39323c99c11897b99f93eac773438e54af206a"}, + {file = "watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da213822ec9082b62cadf3008e07a8c690ec7a3c89f6dc5e55a8cf07b56a1b26"}, + {file = "watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4593392e87669836670f87d62fc754059eaeb0e6157af44060a46558cebe5a3"}, + {file = "watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26b81bc515a0f03f66a69a6fea86eea54c8eca4173c46dd7222569f79dd2f977"}, + {file = "watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca39661934749df580d89f3dde266f2af7c6968f4c21c72ce2dc387d299e6aa7"}, + {file = "watchfiles-1.3.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:04c5500faa725d69a99b0f63750e80795e455bb951849e9c98a811b4324997c4"}, + {file = "watchfiles-1.3.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:ec3cd4bb181a7b6329134204c2277c7144ab5f62cce9c5335d6f901ee7ce8d6a"}, + {file = "watchfiles-1.3.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:e5871d4a7f788a9f64fb06f5793389d29d171e5c32d37c3ebfd520b017cc7694"}, + {file = "watchfiles-1.3.0-cp314-cp314t-win32.whl", hash = "sha256:81c989d7267bfb7b676cd32c83bec97914e396870983cbf493e29415037bff5e"}, + {file = "watchfiles-1.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d754e049006e81a9be49dc78ba7802081cee426e968cbc63ee19e164fa2d0e39"}, + {file = "watchfiles-1.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a5b631db08fd3032db57ec6a92d1777cd60f88803287e4e941a4858c53aab1e0"}, + {file = "watchfiles-1.3.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:45726c6b5a8d67c8087eb2933c4398948546d04206ef40f629dc4881d65d23d1"}, + {file = "watchfiles-1.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6398d250994baff581147ccba846223076fe6492ca93c0d1b3588fcf5355eb5c"}, + {file = "watchfiles-1.3.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:591de392458bca26f6130fea4e667c06b657e873ad334ea06fad5a79f5086cef"}, + {file = "watchfiles-1.3.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec46a1ff4c1d81405a5fc645abdbff5e4e9388f7c9a6cdb392316a7c29b884f"}, + {file = "watchfiles-1.3.0.tar.gz", hash = "sha256:99aee4a07847c06820765fd7b1b49ceac4f3f711ccb7d104655a33231de1c207"}, ] [[package]] @@ -1680,7 +1596,7 @@ files = [ [[package]] name = "yarl" -version = "1.23.0" +version = "1.25.1" requires_python = ">=3.10" summary = "Yet another URL library" groups = ["default"] @@ -1690,143 +1606,136 @@ dependencies = [ "propcache>=0.2.1", ] files = [ - {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, - {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, - {file = "yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6"}, - {file = "yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d"}, - {file = "yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb"}, - {file = "yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2"}, - {file = "yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5"}, - {file = "yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46"}, - {file = "yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34"}, - {file = "yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d"}, - {file = "yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e"}, - {file = "yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543"}, - {file = "yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957"}, - {file = "yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3"}, - {file = "yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5"}, - {file = "yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595"}, - {file = "yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090"}, - {file = "yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe"}, - {file = "yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169"}, - {file = "yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70"}, - {file = "yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4"}, - {file = "yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4"}, - {file = "yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2"}, - {file = "yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25"}, - {file = "yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f"}, - {file = "yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5"}, + {file = "yarl-1.25.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9d693bf4bf534e9ba3ae2780cfd577f5135629f7b5ac653490859d0b77864865"}, + {file = "yarl-1.25.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ab2054c5531af2a9ba7b69b8ec91e4f884420e83a8c5e579b013084cb57e5e5d"}, + {file = "yarl-1.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:564fdc7085d2245ab84f88882fdb1d6ac0723124bff6ded35bfb1c00f812630d"}, + {file = "yarl-1.25.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acae6b45d1ace09b6ba3876da43b88366ef368f73b988c7f57e14231753d4420"}, + {file = "yarl-1.25.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1fb2a01ba8cd9c5d2c5dc1ec35e0fc951d04b4f037541d4ac090c993ce58b3d7"}, + {file = "yarl-1.25.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e92b6bcc741b86d67606c40d3cb9c7cc8e6c737f81e31f4a94efc204456c92e3"}, + {file = "yarl-1.25.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72c34ac7ad4314c19362d5ce27626dcc8429bd30bbf8c179f4234078851f9492"}, + {file = "yarl-1.25.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5add7b4ca7afeea91d52e4d4e4db3b1fe9885b71f07054560d8c4296b7441a2"}, + {file = "yarl-1.25.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:def538065f9e4d4cf1ae164bd59aba00dfa84f03923e0de4c3788f252d6bcd17"}, + {file = "yarl-1.25.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a191bfdb30a79b98e5d175d75285f9fcb78bf0e46ba5efda042e1c72071a0de"}, + {file = "yarl-1.25.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:71f42c5b9a948c113bbdebfa544598321431d064ff959d32e99b1feb61d68345"}, + {file = "yarl-1.25.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:72849d892954be4d09e569b8b831ac39ce58417fedc767d4308a0fe542018a40"}, + {file = "yarl-1.25.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:efb01a106f971cb3752856bca2318bbdf7f01bd8823779c461586cbe5ffd5258"}, + {file = "yarl-1.25.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a1daf47cd95a7c3a63456336bc5aaa8c86dd3a47d07ed3d0e76132ae4666a5a1"}, + {file = "yarl-1.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9489e6abf47ba37f332075a91444c7cfedb03e6ce99fbb2f116bfe1ce810da3b"}, + {file = "yarl-1.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7306dee25b8a0e737363f347362b875094b4dc4e367311470656ae420fdbf8e"}, + {file = "yarl-1.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:abb1384477f5901d436b5d2e5465954de46ea6098f59163d243660b5c4461d35"}, + {file = "yarl-1.25.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d7aa6debf92a1dd14cb5280b083a764169a13cfb23a452111160274ed989f4"}, + {file = "yarl-1.25.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:83d4a37e4b95da4d8bda930d6d35b75b4cdadbacbb4980cae290ea3100b5d51d"}, + {file = "yarl-1.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e029648f9c951db30e98a7d7ec90835db88ec4b32820efe2a9bdc2287e032eb6"}, + {file = "yarl-1.25.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d781294bb815ecb5ea57ff6bbf8038e0a31a95fdf3e1788f66e0dc100d64b58"}, + {file = "yarl-1.25.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e12c538e00e7c1b286a07061046b90e8124e6a9793efae2c70db6a4aad07faad"}, + {file = "yarl-1.25.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e4de3ac4adbad3d0bc7c6f4360a7dbff5de2f15e3b723be3198074e17fd9c40"}, + {file = "yarl-1.25.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:419f392a1da624877975709e3864dfe833af6cc7671b39318086d456e288380c"}, + {file = "yarl-1.25.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6f117789d22dce188e5754e8bc65b7e6ebf8cb73963b9fa761f672a5883769d"}, + {file = "yarl-1.25.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80e47012e730da131c9f059c80936783f9659aae22dc31c03c0595590d11ed54"}, + {file = "yarl-1.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e80f557716fd765439577131e526b8942ffc2c07bdbc5e39fa62f660ba1e963f"}, + {file = "yarl-1.25.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f61964f235a43738bfac50da46fc4254943a7eea3051aeb0b6fc7c992c29fadc"}, + {file = "yarl-1.25.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e546fe1d4a93ebc2910f0d768baff19faa09843ab3f2036a67ed6e69fae4419d"}, + {file = "yarl-1.25.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cce0727fd5ac04d372fa9bbfde9febc2bcf209aadfcf0468e45dec72719895d1"}, + {file = "yarl-1.25.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af4ea5b37403ef4e30f3927eaed540db942bde01d8d3ff083527c0704d1c9c68"}, + {file = "yarl-1.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:68782fdb4027b8d1eee25ec35e9a6db05e863b899eb0310b3a33b6c3fef55707"}, + {file = "yarl-1.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:7d575b54cb3863ef9bc290ea4b009999d55dc237326131e4853cf33e888fee03"}, + {file = "yarl-1.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:bc3ac7bf569f6b64dad04dd7808c7872dae8a97df657856eac05e9b7e3614a85"}, + {file = "yarl-1.25.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:25868beca8b6765f8f7d0e11fe6dd7c66dd4b0793b9500286d20cc92352126a5"}, + {file = "yarl-1.25.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:10b2fd95332f0d716d5eee3c9fb2ce8eada19082de7fee83d32e37992fd75c26"}, + {file = "yarl-1.25.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f12afda4eea8c8994a76d4df1875c765194f5fbe8a9d197929ea303caee29ec"}, + {file = "yarl-1.25.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14b79a30a93a3ce2e8832603fd0ab780ada281b0ba5110b519a634f2d7d7d1fc"}, + {file = "yarl-1.25.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bd6340d20ae2c7ca719b87b426e808e90743b676d05d4c26c4fb5ca71f41184"}, + {file = "yarl-1.25.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:126a2533570c554719ca40a1288fdee1700b6bc82e7131aa69fa85252d92e651"}, + {file = "yarl-1.25.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a3faadac7d812ddac258feb57b9846b60c1b437c4f4b9ad42595c6f6fe4390df"}, + {file = "yarl-1.25.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be80550d9bfe83d9b62398a37081a90434e6df2d978ec345c3d2820de6beddab"}, + {file = "yarl-1.25.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e07595c7d6f4db270ceede356a1bd1c07a34f1c26f958d1ed0cd7b48e0d2bba3"}, + {file = "yarl-1.25.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eb96ed1ae6c7d072d60840c0434aef07a2df611812810807fbc54263a6053e9a"}, + {file = "yarl-1.25.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3feb99222553a8cbedfa52c2f59dd84c3f50d5b582c728d522caf8d72769a54b"}, + {file = "yarl-1.25.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a2ed0ba415ccdf08f14bf544cb78346d0f76086707ffee24921a2c84dbf1305a"}, + {file = "yarl-1.25.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2b49375d22299b0a834c2bca72f39aaecc270d96fb24c30424899676f487b22a"}, + {file = "yarl-1.25.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ef74070ac553c59eb4f04258722066d6c6135b7baa03b2e9f2da65c096e96d98"}, + {file = "yarl-1.25.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0a66db89ea473abeac4b70523cafd94db3772380e565f9d28af7a179b7af71fa"}, + {file = "yarl-1.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:1f51020b2eb8a003c84925638ec63c21a750a4bddd3a22ec8eac6a742dadf1b9"}, + {file = "yarl-1.25.1-cp313-cp313-win_arm64.whl", hash = "sha256:b10dd0557ba422715b5206b3743192135a6022acca8baec51aa127d0a75db8fe"}, + {file = "yarl-1.25.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a9ca696eb02e5c02a8afd872ada510eba9b7fe6e68b9572c2e9a9b1941e31e2e"}, + {file = "yarl-1.25.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a5877f2255aab518ebe528289037699201d5dc5f045f2396cb30aa02db22f57f"}, + {file = "yarl-1.25.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7a5c3115595995779ee21f2567035793911c3802a43c74f3fbb0314929ec67ac"}, + {file = "yarl-1.25.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77e5099b99b37f3cf79c246998ca9f7313a78054cd1809ec46bc1afad47e1c4c"}, + {file = "yarl-1.25.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6efaf45df6a849cef613a03a94c845647456662f85438c886bb67a9c027c8c2c"}, + {file = "yarl-1.25.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5f90e44653c4e0f78501ed9bb7d3fce835a8d62b7c6ed0cb16557534087e743"}, + {file = "yarl-1.25.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:632da579b2d879f6bad20f2cfa35ded1efe2f4f77f8abb26a6234a5b236acd2f"}, + {file = "yarl-1.25.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:30eec96e8a91bd588ce897c9543f6d5d8d34b28fbcba28a4dedf20ebeae9fe57"}, + {file = "yarl-1.25.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:12b6bc4906e11f5e1a1cdcb12296e7afbd366c783cc8073403cd2fb74334e453"}, + {file = "yarl-1.25.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9d6ed3d17bccce4c05343e1ca8da13bc5c02c812a4e7282ddd05e8769322d3fc"}, + {file = "yarl-1.25.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:f38a70074041d3b7e138e452799f5174198bae5bd5ab2000917badf403908c5f"}, + {file = "yarl-1.25.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca89e4e21854ed27ec753297dde84b16c9f8e53b14a4866fb44457d643c19f8"}, + {file = "yarl-1.25.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1ab7618921a93767387a4b83776f751588f5b5ae9bb5bc96620e2e2e00bca868"}, + {file = "yarl-1.25.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0ae12ff2b805fa02c4dab838005caef735e39986322698c48588d3beacb65c62"}, + {file = "yarl-1.25.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90c30ed53546da833c700115c0064c22120d1b1560f474699fd31f22dd668233"}, + {file = "yarl-1.25.1-cp314-cp314-win_amd64.whl", hash = "sha256:acfa7e22aa6c6e7a5996a41d275bfa01efa7ea56ab890590280e9063e2cf5c1b"}, + {file = "yarl-1.25.1-cp314-cp314-win_arm64.whl", hash = "sha256:8e7d98cdbb6d71e726f7d525952867096053d1f290dd4e3c50d7d313a136f414"}, + {file = "yarl-1.25.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d21f0fa80a02d05299207eeaafef345d812ace96d5306e4ef265e1d419a615fa"}, + {file = "yarl-1.25.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17c9877a89fb6e2bca6f9087eb24cd7fb434653946ef5075e470d23d49b52287"}, + {file = "yarl-1.25.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:29273edf1530e397bd07cb784db1fbe0d2590b77569f2e24679a9c0a2d763b94"}, + {file = "yarl-1.25.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7abffdf37af1cec6a2ad69b827aa84320db5894791bc8ed932dc93fb274b7e9"}, + {file = "yarl-1.25.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2239a02249d9326655419e0168a28ca9008938eaab31dc29fc875c217927a6c0"}, + {file = "yarl-1.25.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:664ec6a520b74a1df2810666eb67695fcb77fa663e6ea0a25aaf2e529cb24dfa"}, + {file = "yarl-1.25.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f1c91f5a5980a937ff8e238e98e6897e1ad74a4b1e2c0d68c73b5ffbb3f5c0b"}, + {file = "yarl-1.25.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c88edaec8c349ad4c5ad4c486a3defcc4b80ceb2f074436ffa0a87caf5e76a6"}, + {file = "yarl-1.25.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:35dcbea443fafb3eece757ad4e514560ddeb6c34cfae1582c620d7b293d7feee"}, + {file = "yarl-1.25.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:882569ff613758cac762a457a5d72d6e211b28d4bcfea89d1d71ea942b02eac0"}, + {file = "yarl-1.25.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d0f1489233a254bb3643d2f05de7d59019254d81daeca6b9162fe9edef57e0c7"}, + {file = "yarl-1.25.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f41753a76f4f63927d03a0d8ba8f5ce0f2083bec29a8cfaccc55371b1564b96b"}, + {file = "yarl-1.25.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fb0eb4955adf0579001581f2f71a126e8781ba61bcd120f127b0401163c6c2d"}, + {file = "yarl-1.25.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a1e32763e641a1566507d90a8d3b19bfc3cc04a9d4e5ae3e32189874ed4b58a3"}, + {file = "yarl-1.25.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65b5b2066651b7432d389e9799d979c703bcc6ef44266bb8153ef54e91e4aab3"}, + {file = "yarl-1.25.1-cp314-cp314t-win_amd64.whl", hash = "sha256:734f6e5400352ac4254456003d462866c684703570929cff7a7bde015d0cb371"}, + {file = "yarl-1.25.1-cp314-cp314t-win_arm64.whl", hash = "sha256:287e99ff5aa4dc1c7630bfc683ded6f106d756c99dec432a2d7f197a784f51c6"}, + {file = "yarl-1.25.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9b1bdaae98bc016825dd3c9d8ee1832f829b3341f9cc6ebd1a1b0a7fef7367cc"}, + {file = "yarl-1.25.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:e7011b8fb8c4054bf0c12e5edc6cd83778b0028e99ce59b18586ed036f92cfdc"}, + {file = "yarl-1.25.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:f074e8d4aa0a5798920ddb6de3d08b228c614ff3724c3e8bd7577f4bafea867b"}, + {file = "yarl-1.25.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d42e7e3ca399555578b4d617e3a6ecf13371b3743a115995fa010c7bf341459"}, + {file = "yarl-1.25.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aa4ed3dd308548f9e707d9caaf005d2d7f8c1e7868f858dfeb47fe76e16b391d"}, + {file = "yarl-1.25.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42a66563d8cc056ee32e6191e05097a7b2b3bc302e0bc3133daf8710eb18bd26"}, + {file = "yarl-1.25.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:98d370568f393215d605304cdb77b3d5539bd192c75b623c7304c42c8d6d8273"}, + {file = "yarl-1.25.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23bf5b403c879a54964e0feac7285688e04bb220074878d737d331522da0a5bf"}, + {file = "yarl-1.25.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d45673badd08456d0340e9364eddafe1c53a9d2896424294de4d7dd71ad3ee57"}, + {file = "yarl-1.25.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:0136d640dfa9b0523853e411430a99f8a91eca85774c6420285a33b755bc6de3"}, + {file = "yarl-1.25.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:59ba3a6e1aa8cfe5adf4bd270fd965db21955401b7ca6f1696010c55ed4daec2"}, + {file = "yarl-1.25.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:87796fedc3ba97ec14fab55acb48584276e6c1e4c1e89c422bda62c838e754a9"}, + {file = "yarl-1.25.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:bd0912757081f89b107d6c00b2ff8a194401b0b87eadcf4481de2b865a8fd44f"}, + {file = "yarl-1.25.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:b51c159a9794633f5e0db7ecec7b2b6e3734eca1f5d17dc989ff3552a43ff78b"}, + {file = "yarl-1.25.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:319e070a01db9920fb63761843f96a104c8e2b9427266731810dc1e22595b17c"}, + {file = "yarl-1.25.1-cp315-cp315-win_amd64.whl", hash = "sha256:a2059a2d891bd156bc5184e7ab7a56e78a84dfcfdeac8c501b552533ad1c36ee"}, + {file = "yarl-1.25.1-cp315-cp315-win_arm64.whl", hash = "sha256:a78b50b4f7918a3de71105d5c0b93bbc57bb8339a4d03a9dfd449f9068e76f3d"}, + {file = "yarl-1.25.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:b5402a340723fa7da00b5cff987ddab61276be6d11251ea71ae02bcac54890d8"}, + {file = "yarl-1.25.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:eda19ea5ee88742f47a2340816e6f2d40b53bed3ab5b69794769f36af9f35bb4"}, + {file = "yarl-1.25.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:75baa6cf9b6d1c52f3e111a130e202fd8cf0a5b3a066c3f73d615e885092e4ec"}, + {file = "yarl-1.25.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbcef5a9119ef653653132cccaf999b30a0af6f33bb0a4ba80bec30056868487"}, + {file = "yarl-1.25.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7efc9f082dfed77c316edffa9deb52888e1bc6789171887cc1f68e06d65465c8"}, + {file = "yarl-1.25.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fe01645169a2112aa1d4ebc3e4c5f029c5c8f97adfc32e5d37c993b39a994d75"}, + {file = "yarl-1.25.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1c557dfd5e3db046053a0bdc72261ade790ebe8e2c7a41b36b0ca1f14cb95f3"}, + {file = "yarl-1.25.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ce4d6ccafb33d39bd78444612d14938ead674c25702ded2ee9c54a47735d225"}, + {file = "yarl-1.25.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80a063f8297fc796296f00f100be520f209b23dc98f93ce8eba6ee7122598209"}, + {file = "yarl-1.25.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:7cb414a73e21a7ab58254926073f2930cb22f5b4314ea4260a687e2b3fd4dce3"}, + {file = "yarl-1.25.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:85a18376073f8a39aa07be34f9fc77e2869aa72c55c441efdd2cf79a0407504d"}, + {file = "yarl-1.25.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:77716e245c90f058466a05e6a465bb8600f767a8f4b18b4d40f3aff958e5f73c"}, + {file = "yarl-1.25.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:1e80dcf1446e1b080b1932b0d103c464a04112f5bc31f0f983ad418172063cde"}, + {file = "yarl-1.25.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:bdc8d8b8c22e9e43ac68316b5e6cf083dec537f4ec213cb4aa967b583bc3fa64"}, + {file = "yarl-1.25.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:dfbf531053a0935f2e871bcd4753f90313688772ff8c017f5ea402e315a78c1f"}, + {file = "yarl-1.25.1-cp315-cp315t-win_amd64.whl", hash = "sha256:b13b88747769537f3d32e89e3a735da10c0a9e35d7322928c701b5f93d3afffd"}, + {file = "yarl-1.25.1-cp315-cp315t-win_arm64.whl", hash = "sha256:783dd1467083f4d3f7722ad6a313f24c173e7571372738fcb7a6e6d1ba48df25"}, + {file = "yarl-1.25.1-py3-none-any.whl", hash = "sha256:681c758b0490f9e96b78e5fa8e8dc6e648e9185bb6eaebe73183c33ea0c445f3"}, + {file = "yarl-1.25.1.tar.gz", hash = "sha256:03dd38de09bc213e9a8b29761eec33ee1d5318dac0e49d8af36e4d27830e23a7"}, ] [[package]] name = "zipp" -version = "3.23.1" -requires_python = ">=3.9" +version = "4.1.1" +requires_python = ">=3.10" summary = "Backport of pathlib-compatible object wrapper for zip files" groups = ["default", "cli", "full"] files = [ - {file = "zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc"}, - {file = "zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110"}, + {file = "zipp-4.1.1-py3-none-any.whl", hash = "sha256:8979f52d874162f485ff2981e3891f3a3317b7a3dd43ff1e1775b9304f307a9c"}, + {file = "zipp-4.1.1.tar.gz", hash = "sha256:7ebb7a44c021b29fd8dbd7cce6812d0d7b5b454521f93cc71af6ccd155aaa70b"}, ] diff --git a/pyproject.toml b/pyproject.toml index 16fe674..8407bc0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,23 +6,23 @@ authors = [ {name = "RF-Tar-Railt",email = "rf_tar_railt@qq.com"}, ] dependencies = [ - "arclet-letoderea<0.22.0,>=0.21.3", + "arclet-letoderea<0.23.0,>=0.22.1", "arclet-alconna<2.0.0,>=1.8.44", - "satori-python-core<1.4.0,>=1.3.5", - "satori-python-client<1.4.0,>=1.3.2", - "arclet-alconna-tools>=0.7.3", + "satori-python-core<1.4.0,>=1.3.7", + "satori-python-client<1.4.0,>=1.3.7", + "arclet-alconna-tools>=0.7.12", "nonestorage>=0.1.0", - "simpleeval>=1.0.3", + "simpleeval>=1.0.8", ] -requires-python = ">=3.10" +requires-python = ">=3.11" readme = "README.md" license = {text = "MIT"} classifiers = [ - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "License :: OSI Approved :: MIT License", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", @@ -32,6 +32,25 @@ classifiers = [ "Framework :: AsyncIO", "Operating System :: OS Independent", ] +keywords = [ + "asyncio", + "framework", + "instant-messaging", + "satori", + "entari", + "plugin-system", + "hmr", + "hot", + "module", + "replacement", + "reload", + "watch", + "development", + "hot-reload", + "hot-module-replacement", + "high-performance", + "event-driven", +] [project.urls] homepage = "https://arclet.top" @@ -39,23 +58,23 @@ repository = "https://github.com/ArcletProject/Entari" [project.optional-dependencies] yaml = [ - "ruamel-yaml>=0.18.10", + "ruamel-yaml>=0.19.1", ] cron = [ - "croniter>=5.0.1", + "croniter>=6.0.0", ] reload = [ - "watchfiles>=1.0.3", + "watchfiles>=1.3.0", ] dotenv = [ - "python-dotenv>=1.0.1", + "python-dotenv>=1.2.4", ] full = [ - "croniter>=5.0.1", - "watchfiles>=1.0.3", - "python-dotenv>=1.0.1", - "ruamel-yaml>=0.18.10", - "entari-cli>=0.1.0", + "croniter>=6.0.0", + "watchfiles>=1.3.0", + "python-dotenv>=1.2.4", + "ruamel-yaml>=0.19.1", + "entari-cli>=0.4.0", ] pydantic = [ "pydantic>=2.10.6", @@ -67,7 +86,7 @@ toml = [ "tomlkit>=0.13.3", ] cli = [ - "entari-cli>=0.1.0", + "entari-cli>=0.4.0", ] rich = [ "rich>=14.1.0", @@ -87,14 +106,14 @@ path = "arclet/entari/__init__.py" [tool.black] line-length = 120 -target-version = ["py310", "py311", "py312", "py313", "py314"] +target-version = ["py311", "py312", "py313", "py314", "py315"] include = '\.pyi?$' extend-exclude = ''' ''' [tool.ruff] line-length = 120 -target-version = "py310" +target-version = "py311" include = ["arclet/**.py"] respect-gitignore = true @@ -107,7 +126,7 @@ force-sort-within-sections = false extra-standard-library = ["typing_extensions"] [tool.pyright] -pythonVersion = "3.10" +pythonVersion = "3.11" pythonPlatform = "All" typeCheckingMode = "basic" @@ -117,11 +136,11 @@ format = { composite = ["ruff check --select I --fix ./arclet/","black ./arclet/ [dependency-groups] dev = [ - "black>=26.3.1", - "ruff>=0.15.10", - "watchfiles>=0.24.0", - "python-dotenv>=1.0.1", - "ruamel-yaml>=0.18.10", + "black>=26.10.1", + "ruff>=0.17.1", + "watchfiles>=1.3.0", + "python-dotenv>=1.2.4", + "ruamel-yaml>=0.19.1", "msgspec>=0.21.0", "pydantic>=2.12.5", "tomlkit>=0.13.3",