From 389d1b5c75c96cfb1bac9cd8addcc198982a9409 Mon Sep 17 00:00:00 2001 From: st1020 Date: Sat, 11 Apr 2026 23:49:59 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20Event=20=E4=B8=8D=E5=86=8D=E7=BB=A7?= =?UTF-8?q?=E6=89=BF=E4=BA=8E=20BaseModel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 2 +- alicebot/event.py | 25 +++--------- examples/adapters/console_adapter.py | 16 ++++++-- examples/adapters/http_server_test_adapter.py | 18 ++++++--- .../alicebot/adapter/apscheduler/event.py | 39 ++++++++++++++++--- .../alicebot/adapter/cqhttp/__init__.py | 3 +- .../alicebot/adapter/cqhttp/event.py | 23 ++++++++++- .../alicebot/adapter/dingtalk/__init__.py | 3 +- .../alicebot/adapter/dingtalk/event.py | 26 +++++++++++-- .../alicebot/adapter/mirai/__init__.py | 3 +- .../alicebot/adapter/mirai/event/base.py | 27 +++++++++++-- .../alicebot/adapter/onebot/__init__.py | 3 +- .../alicebot/adapter/onebot/event.py | 23 ++++++++++- .../alicebot/adapter/telegram/__init__.py | 7 +++- .../alicebot/adapter/telegram/event/base.py | 30 +++++++++++++- tests/fake_adapter.py | 19 ++++++++- tests/test_plugin/test_plugin_get.py | 9 +++++ 17 files changed, 226 insertions(+), 50 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index f6cb3b7..7c2e2cb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "name": "AliceBot: Examples", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/examples/test.py", + "program": "${workspaceFolder}/examples/main.py", "console": "integratedTerminal", "justMyCode": false, "cwd": "${workspaceFolder}/examples", diff --git a/alicebot/event.py b/alicebot/event.py index 06e2914..0c4d3d9 100644 --- a/alicebot/event.py +++ b/alicebot/event.py @@ -4,16 +4,14 @@ """ from abc import ABCMeta, abstractmethod -from typing import TYPE_CHECKING, Any, NamedTuple, Self, override - -from pydantic import BaseModel, ConfigDict +from typing import Any, NamedTuple, Self from alicebot.typing import AnyAdapter, AnyEvent __all__ = ["Event", "EventHandleOption", "MessageEvent"] -class Event[AdapterT: AnyAdapter](BaseModel, metaclass=ABCMeta): +class Event[AdapterT: AnyAdapter](metaclass=ABCMeta): """事件类的基类。 Attributes: @@ -21,21 +19,10 @@ class Event[AdapterT: AnyAdapter](BaseModel, metaclass=ABCMeta): type: 事件类型。 """ - model_config = ConfigDict(extra="allow") - - if TYPE_CHECKING: - adapter: AdapterT - else: - adapter: Any - type: str | None - - @override - def __str__(self) -> str: - return f"Event<{self.type}>" - - @override - def __repr__(self) -> str: - return self.__str__() + @property + @abstractmethod + def adapter(self) -> AdapterT: + """产生当前事件的适配器对象。""" class EventHandleOption(NamedTuple): diff --git a/examples/adapters/console_adapter.py b/examples/adapters/console_adapter.py index 5d6b92d..37aa311 100644 --- a/examples/adapters/console_adapter.py +++ b/examples/adapters/console_adapter.py @@ -19,8 +19,19 @@ class ConsoleAdapterEvent(MessageEvent["ConsoleAdapter"]): message: 消息内容。 """ + _adapter: "ConsoleAdapter" message: str + def __init__(self, adapter: "ConsoleAdapter", message: str) -> None: + """初始化 ConsoleAdapterEvent。""" + self._adapter = adapter + self.message = message + + @property + @override + def adapter(self) -> "ConsoleAdapter": + return self._adapter + @override def get_sender_id(self) -> None: return None @@ -43,9 +54,8 @@ class ConsoleAdapter(PollingAdapter[ConsoleAdapterEvent, None]): async def on_tick(self) -> None: print("Please input message: ") # noqa: T201 message = await anyio.to_thread.run_sync(sys.stdin.readline) - await self.handle_event( - ConsoleAdapterEvent(adapter=self, type="message", message=message) - ) + event = ConsoleAdapterEvent(adapter=self, message=message.strip()) + await self.handle_event(event) async def send(self, message: str) -> None: """发送消息。 diff --git a/examples/adapters/http_server_test_adapter.py b/examples/adapters/http_server_test_adapter.py index 9208559..581de1e 100644 --- a/examples/adapters/http_server_test_adapter.py +++ b/examples/adapters/http_server_test_adapter.py @@ -14,8 +14,19 @@ class HttpServerTestEvent(Event["HttpServerTestAdapter"]): """HTTP 服务端示例适配器事件类。""" + _adapter: "HttpServerTestAdapter" message: str + def __init__(self, adapter: "HttpServerTestAdapter", message: str) -> None: + """初始化 HttpServerTestEvent。""" + self._adapter = adapter + self.message = message + + @property + @override + def adapter(self) -> "HttpServerTestAdapter": + return self._adapter + class HttpServerTestAdapter(HttpServerAdapter[HttpServerTestEvent, None]): """HTTP 服务端示例适配器类。""" @@ -28,10 +39,7 @@ class HttpServerTestAdapter(HttpServerAdapter[HttpServerTestEvent, None]): @override async def handle_response(self, request: web.Request) -> web.StreamResponse: - event = HttpServerTestEvent( - adapter=self, - type="message", - message=await request.text(), - ) + message = await request.text() + event = HttpServerTestEvent(adapter=self, message=message) await self.handle_event(event) return web.Response() diff --git a/packages/alicebot-adapter-apscheduler/alicebot/adapter/apscheduler/event.py b/packages/alicebot-adapter-apscheduler/alicebot/adapter/apscheduler/event.py index 4a9f65c..c264741 100644 --- a/packages/alicebot-adapter-apscheduler/alicebot/adapter/apscheduler/event.py +++ b/packages/alicebot-adapter-apscheduler/alicebot/adapter/apscheduler/event.py @@ -1,7 +1,7 @@ """APScheduler 适配器事件。""" # pyright: reportMissingTypeStubs = false -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, override from apscheduler.job import Job from apscheduler.triggers.base import BaseTrigger @@ -22,11 +22,30 @@ class APSchedulerEvent(Event["APSchedulerAdapter"]): """APSchedulerEvent 事件基类。""" - type: str | None = "apscheduler" - if TYPE_CHECKING: - plugin_class: "builtins.type[AnyPlugin]" - else: - plugin_class: Any + type: str = "apscheduler" + _adapter: "APSchedulerAdapter" + _plugin_class: "builtins.type[AnyPlugin]" + + def __init__( + self, + *, + adapter: "APSchedulerAdapter", + plugin_class: "builtins.type[AnyPlugin]", + ) -> None: + """初始化 APSchedulerEvent 事件对象。""" + super().__init__() + self._adapter = adapter + self._plugin_class = plugin_class + + @property + @override + def adapter(self) -> "APSchedulerAdapter": + return self._adapter + + @property + def plugin_class(self) -> "builtins.type[AnyPlugin]": + """产生当前事件的 Plugin 类。""" + return self._plugin_class @property def job(self) -> Job: @@ -42,3 +61,11 @@ def trigger(self) -> str | BaseTrigger | None: def trigger_args(self) -> dict[str, Any] | None: """当前事件对应的 Plugin 的 `trigger_args`。""" return getattr(self.plugin_class, "trigger_args", None) + + @override + def __str__(self) -> str: + return f"<{self.__class__.__name__} plugin_class={self.plugin_class.__name__}>" + + @override + def __repr__(self) -> str: + return self.__str__() diff --git a/packages/alicebot-adapter-cqhttp/alicebot/adapter/cqhttp/__init__.py b/packages/alicebot-adapter-cqhttp/alicebot/adapter/cqhttp/__init__.py index 4e03fb4..167f332 100644 --- a/packages/alicebot-adapter-cqhttp/alicebot/adapter/cqhttp/__init__.py +++ b/packages/alicebot-adapter-cqhttp/alicebot/adapter/cqhttp/__init__.py @@ -181,7 +181,8 @@ async def handle_cqhttp_event(self, msg: dict[str, Any]) -> None: msg.get("sub_type"), ) - cqhttp_event = event_class(adapter=self, **msg) + cqhttp_event = event_class(**msg) + cqhttp_event.adapter = self if cqhttp_event.post_type == "meta_event": # meta_event 不交由插件处理 diff --git a/packages/alicebot-adapter-cqhttp/alicebot/adapter/cqhttp/event.py b/packages/alicebot-adapter-cqhttp/alicebot/adapter/cqhttp/event.py index a8800fe..518ad0a 100644 --- a/packages/alicebot-adapter-cqhttp/alicebot/adapter/cqhttp/event.py +++ b/packages/alicebot-adapter-cqhttp/alicebot/adapter/cqhttp/event.py @@ -70,14 +70,27 @@ def _get_literal_field(field: FieldInfo | None) -> str | None: return literal_values[0] -class CQHTTPEvent(Event["CQHTTPAdapter"]): +class CQHTTPEvent(BaseModel, Event["CQHTTPAdapter"]): # pyright: ignore[reportUnsafeMultipleInheritance] """CQHTTP 事件基类""" + model_config = ConfigDict(extra="allow") + + _adapter: "CQHTTPAdapter" + type: str | None = Field(alias="post_type") time: int self_id: int post_type: str + @property + @override + def adapter(self) -> "CQHTTPAdapter": + return self._adapter + + @adapter.setter + def adapter(self, value: "CQHTTPAdapter") -> None: + self._adapter = value + @property def to_me(self) -> bool: """当前事件的 `user_id` 是否等于 `self_id`。""" @@ -99,6 +112,14 @@ def get_event_type(cls) -> tuple[str | None, str | None, str | None]: _get_literal_field(cls.model_fields.get("sub_type")), ) + @override + def __str__(self) -> str: + return f"<{self.__class__.__name__} type={self.type}, time={self.time}, self_id={self.self_id}>" + + @override + def __repr__(self) -> str: + return self.__str__() + class MessageEvent(CQHTTPEvent, BaseMessageEvent["CQHTTPAdapter"]): """消息事件""" diff --git a/packages/alicebot-adapter-dingtalk/alicebot/adapter/dingtalk/__init__.py b/packages/alicebot-adapter-dingtalk/alicebot/adapter/dingtalk/__init__.py index 292abdc..a4c9ca1 100644 --- a/packages/alicebot-adapter-dingtalk/alicebot/adapter/dingtalk/__init__.py +++ b/packages/alicebot-adapter-dingtalk/alicebot/adapter/dingtalk/__init__.py @@ -74,7 +74,8 @@ async def handler(self, request: web.Request) -> web.Response: logger.error("Illegal http header", sign=request.headers["sign"]) else: try: - dingtalk_event = DingTalkEvent(adapter=self, **(await request.json())) + dingtalk_event = DingTalkEvent(**(await request.json())) + dingtalk_event.adapter = self except Exception: logger.exception("Request parsing error") return web.Response() diff --git a/packages/alicebot-adapter-dingtalk/alicebot/adapter/dingtalk/event.py b/packages/alicebot-adapter-dingtalk/alicebot/adapter/dingtalk/event.py index b7a8f8c..0ac21d8 100644 --- a/packages/alicebot-adapter-dingtalk/alicebot/adapter/dingtalk/event.py +++ b/packages/alicebot-adapter-dingtalk/alicebot/adapter/dingtalk/event.py @@ -3,7 +3,7 @@ import time from typing import TYPE_CHECKING, Any, Literal, override -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field from alicebot.event import MessageEvent @@ -29,11 +29,14 @@ class Text(BaseModel): content: str -class DingTalkEvent(MessageEvent["DingTalkAdapter"]): +class DingTalkEvent(BaseModel, MessageEvent["DingTalkAdapter"]): # pyright: ignore[reportUnsafeMultipleInheritance] """DingTalk 事件基类""" - type: str | None = Field(alias="msgtype") + model_config = ConfigDict(extra="allow") + + _adapter: "DingTalkAdapter" + type: str | None = Field(alias="msgtype") msgtype: str msgId: str createAt: str @@ -56,6 +59,15 @@ class DingTalkEvent(MessageEvent["DingTalkAdapter"]): response_msg: None | str | dict[str, Any] | DingTalkMessage = None response_at: None | dict[str, Any] | DingTalkMessage = None + @property + @override + def adapter(self) -> "DingTalkAdapter": + return self._adapter + + @adapter.setter + def adapter(self, value: "DingTalkAdapter") -> None: + self._adapter = value + @property def message(self) -> DingTalkMessage: """返回 message 字段。""" @@ -96,3 +108,11 @@ async def reply( at=at, ) raise WebhookExpiredError + + @override + def __str__(self) -> str: + return f"<{self.__class__.__name__} type={self.type}, time={self.createAt}, self_id={self.chatbotUserId}>" + + @override + def __repr__(self) -> str: + return self.__str__() diff --git a/packages/alicebot-adapter-mirai/alicebot/adapter/mirai/__init__.py b/packages/alicebot-adapter-mirai/alicebot/adapter/mirai/__init__.py index ab16e0c..42525ad 100644 --- a/packages/alicebot-adapter-mirai/alicebot/adapter/mirai/__init__.py +++ b/packages/alicebot-adapter-mirai/alicebot/adapter/mirai/__init__.py @@ -147,7 +147,8 @@ async def handle_mirai_event(self, msg: dict[str, Any]) -> None: Args: msg: 接收到的信息。 """ - mirai_event = self.get_event_model(msg["type"])(adapter=self, **msg) + mirai_event = self.get_event_model(msg["type"])(**msg) + mirai_event.adapter = self if isinstance(mirai_event, MetaEvent): # meta_event 不交由插件处理 diff --git a/packages/alicebot-adapter-mirai/alicebot/adapter/mirai/event/base.py b/packages/alicebot-adapter-mirai/alicebot/adapter/mirai/event/base.py index 6055f26..0e19020 100644 --- a/packages/alicebot-adapter-mirai/alicebot/adapter/mirai/event/base.py +++ b/packages/alicebot-adapter-mirai/alicebot/adapter/mirai/event/base.py @@ -1,9 +1,9 @@ """事件基类。""" # pyright: reportIncompatibleVariableOverride=false -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Literal, override -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from alicebot.event import Event @@ -56,7 +56,28 @@ class OtherClientSender(BaseModel): platform: str -class MiraiEvent(Event["MiraiAdapter"]): +class MiraiEvent(BaseModel, Event["MiraiAdapter"]): # pyright: ignore[reportUnsafeMultipleInheritance] """Mirai 事件基类""" + model_config = ConfigDict(extra="allow") + + _adapter: "MiraiAdapter" + type: str + + @property + @override + def adapter(self) -> "MiraiAdapter": + return self._adapter + + @adapter.setter + def adapter(self, value: "MiraiAdapter") -> None: + self._adapter = value + + @override + def __str__(self) -> str: + return f"<{self.__class__.__name__} type={self.type}>" + + @override + def __repr__(self) -> str: + return self.__str__() diff --git a/packages/alicebot-adapter-onebot/alicebot/adapter/onebot/__init__.py b/packages/alicebot-adapter-onebot/alicebot/adapter/onebot/__init__.py index fcd4bff..f6c9e06 100644 --- a/packages/alicebot-adapter-onebot/alicebot/adapter/onebot/__init__.py +++ b/packages/alicebot-adapter-onebot/alicebot/adapter/onebot/__init__.py @@ -182,7 +182,8 @@ async def handle_onebot_event(self, msg: dict[str, Any]) -> None: msg.get("sub_type"), ) - onebot_event = event_class(adapter=self, **msg) + onebot_event = event_class(**msg) + onebot_event.adapter = self if onebot_event.type == "meta": # meta_event 不交由插件处理 diff --git a/packages/alicebot-adapter-onebot/alicebot/adapter/onebot/event.py b/packages/alicebot-adapter-onebot/alicebot/adapter/onebot/event.py index cac44c9..5c0811a 100644 --- a/packages/alicebot-adapter-onebot/alicebot/adapter/onebot/event.py +++ b/packages/alicebot-adapter-onebot/alicebot/adapter/onebot/event.py @@ -61,15 +61,28 @@ def _get_literal_field(field: FieldInfo | None) -> str | None: return literal_values[0] -class OneBotEvent(Event["OneBotAdapter"]): +class OneBotEvent(BaseModel, Event["OneBotAdapter"]): # pyright: ignore[reportUnsafeMultipleInheritance] """OneBot 事件基类""" + model_config = ConfigDict(extra="allow") + + _adapter: "OneBotAdapter" + id: str time: float type: Literal["meta", "message", "notice", "request"] detail_type: str sub_type: str + @property + @override + def adapter(self) -> "OneBotAdapter": + return self._adapter + + @adapter.setter + def adapter(self, value: "OneBotAdapter") -> None: + self._adapter = value + @classmethod def get_event_type(cls) -> tuple[str | None, str | None, str | None]: """获取事件类型。 @@ -83,6 +96,14 @@ def get_event_type(cls) -> tuple[str | None, str | None, str | None]: _get_literal_field(cls.model_fields.get("sub_type")), ) + @override + def __str__(self) -> str: + return f"<{self.__class__.__name__} type={self.type}, detail_type={self.detail_type}, sub_type={self.sub_type}>" + + @override + def __repr__(self) -> str: + return self.__str__() + class BotEvent(OneBotEvent): """包含 self 字段的机器人事件""" diff --git a/packages/alicebot-adapter-telegram/alicebot/adapter/telegram/__init__.py b/packages/alicebot-adapter-telegram/alicebot/adapter/telegram/__init__.py index f23084c..f4a55e9 100644 --- a/packages/alicebot-adapter-telegram/alicebot/adapter/telegram/__init__.py +++ b/packages/alicebot-adapter-telegram/alicebot/adapter/telegram/__init__.py @@ -151,7 +151,12 @@ async def handle_telegram_event(self, update: Update) -> None: ) return event_class = EVENT_MODELS[event_class_name] - telegram_event = event_class(adapter=self, type=event_class_name, update=update) + telegram_event = event_class( # pyright: ignore + type=event_class_name, + update=update, + ) + telegram_event.adapter = self + await self.handle_event(telegram_event) def _format_telegram_api_params( diff --git a/packages/alicebot-adapter-telegram/alicebot/adapter/telegram/event/base.py b/packages/alicebot-adapter-telegram/alicebot/adapter/telegram/event/base.py index c50ed88..7b043f4 100644 --- a/packages/alicebot-adapter-telegram/alicebot/adapter/telegram/event/base.py +++ b/packages/alicebot-adapter-telegram/alicebot/adapter/telegram/event/base.py @@ -1,6 +1,8 @@ """事件基类。""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override + +from pydantic import BaseModel, ConfigDict from alicebot.event import Event @@ -10,14 +12,38 @@ from .. import TelegramAdapter # noqa: TID252 -class TelegramEvent(Event["TelegramAdapter"]): +class TelegramEvent(BaseModel, Event["TelegramAdapter"]): # pyright: ignore[reportUnsafeMultipleInheritance] """Telegram Event Baseclass.""" __event_type__: str = "" + model_config = ConfigDict(extra="allow") + + _adapter: "TelegramAdapter" + + type: str update: Update + @property + @override + def adapter(self) -> "TelegramAdapter": + return self._adapter + + @adapter.setter + def adapter(self, value: "TelegramAdapter") -> None: + self._adapter = value + @property def update_id(self) -> int: """The update's unique identifier.""" return self.update.update_id + + @override + def __str__(self) -> str: + return ( + f"<{self.__class__.__name__} type={self.type}, update_id={self.update_id}>" + ) + + @override + def __repr__(self) -> str: + return self.__str__() diff --git a/tests/fake_adapter.py b/tests/fake_adapter.py index a8cf618..1887019 100644 --- a/tests/fake_adapter.py +++ b/tests/fake_adapter.py @@ -61,7 +61,24 @@ def fake_adapter_class_factory( class FakeMessageEvent(MessageEvent[FakeAdapter]): - message: str = "test" + _adapter: FakeAdapter + type: str + message: str + + def __init__( + self, + adapter: FakeAdapter, + type: str = "message", + message: str = "test", + ) -> None: + self._adapter = adapter + self.type = type + self.message = message + + @property + @override + def adapter(self) -> "FakeAdapter": + return self._adapter @override def get_sender_id(self) -> None: diff --git a/tests/test_plugin/test_plugin_get.py b/tests/test_plugin/test_plugin_get.py index 9eb126c..7b4c478 100644 --- a/tests/test_plugin/test_plugin_get.py +++ b/tests/test_plugin/test_plugin_get.py @@ -179,8 +179,17 @@ def test_plugin_get_event_type(bot: Bot, mocker: MockerFixture) -> None: mock = mocker.AsyncMock() class FakeOtherEvent(Event[FakeAdapter]): + _adapter: FakeAdapter type: str | None = "other" + def __init__(self, adapter: FakeAdapter) -> None: + self._adapter = adapter + + @property + @override + def adapter(self) -> "FakeAdapter": + return self._adapter + class TestPlugin(Plugin): @override async def handle(self) -> None: From 4d99f0d4bd16227072d82e8eeaba594f1f0868cf Mon Sep 17 00:00:00 2001 From: st1020 Date: Sun, 12 Apr 2026 00:36:49 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20mypy=20=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alicebot/adapter/telegram/__init__.py | 5 +---- uv.lock | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/alicebot-adapter-telegram/alicebot/adapter/telegram/__init__.py b/packages/alicebot-adapter-telegram/alicebot/adapter/telegram/__init__.py index f4a55e9..b96948b 100644 --- a/packages/alicebot-adapter-telegram/alicebot/adapter/telegram/__init__.py +++ b/packages/alicebot-adapter-telegram/alicebot/adapter/telegram/__init__.py @@ -151,10 +151,7 @@ async def handle_telegram_event(self, update: Update) -> None: ) return event_class = EVENT_MODELS[event_class_name] - telegram_event = event_class( # pyright: ignore - type=event_class_name, - update=update, - ) + telegram_event = event_class(type=event_class_name, update=update) # type: ignore telegram_event.adapter = self await self.handle_event(telegram_event) diff --git a/uv.lock b/uv.lock index f16e6a3..4322f38 100644 --- a/uv.lock +++ b/uv.lock @@ -1143,15 +1143,15 @@ wheels = [ [[package]] name = "sophia-doc" -version = "0.1.10" +version = "0.1.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docstring-parser" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/c9/bbecbbaa67ddba6958de55a35b92280c4ff71cad3a09ba7722d33734b191/sophia_doc-0.1.10.tar.gz", hash = "sha256:ffeea87ca63df1a017fcf56f96beb435879bf953073ee1a0f4b6d0380f04033e", size = 12344, upload-time = "2026-04-05T14:00:37.882Z" } +sdist = { url = "https://files.pythonhosted.org/packages/93/f8/514fab4a5505adb7fccec1068f0dc5f3790a0238c0af07c769791a0db9a6/sophia_doc-0.1.11.tar.gz", hash = "sha256:afa35aa428fa37c6dbedfd2965315a7fefa457fdacd4b9dc95bedbd06832c4aa", size = 12348, upload-time = "2026-04-11T16:35:15.828Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/7c/115e8c7efa2f44c17fa6cf17274c183823f702fa474fc40d5380d932e15a/sophia_doc-0.1.10-py3-none-any.whl", hash = "sha256:bdc3aa5980915e6194fd5537848ab436b66f61fe1b0a1a38af1a5b0c0192451a", size = 13441, upload-time = "2026-04-05T14:00:39.057Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7d/30d5df13b77398d6c17017130366346371c82f0e1e60be33b32aa9678db1/sophia_doc-0.1.11-py3-none-any.whl", hash = "sha256:5da9227612fbbbff4fb34b05afbadf1672168e768053086056fbadd9c5c064b9", size = 13461, upload-time = "2026-04-11T16:35:14.574Z" }, ] [[package]]