ErisPulse 2.6.0.dev0__tar.gz → 2.6.0.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/PKG-INFO +3 -3
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/README.md +2 -2
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/pyproject.toml +1 -1
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/adapter.py +14 -5
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/command.py +3 -6
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/adapter.py +147 -9
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/adapter.pyi +1 -1
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/constants.py +34 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/constants.pyi +10 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/locales/en.py +5 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/locales/ja.py +5 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/locales/ru.py +5 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/locales/zh_cn.py +5 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/locales/zh_tw.py +5 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/lifecycle.py +33 -6
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/lifecycle.pyi +14 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/module.py +11 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/router.py +71 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/router.pyi +1 -1
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/runtime/frame_config.py +7 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/runtime/frame_config.pyi +1 -1
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/sdk.py +86 -1
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/sdk.pyi +1 -1
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/.gitignore +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/LICENSE +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/base.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/base.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/cli.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/cli.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/create.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/create.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/init.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/init.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/install.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/install.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/language.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/language.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/list.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/list.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/list_remote.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/list_remote.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/run.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/run.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/self_update.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/self_update.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/uninstall.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/uninstall.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/upgrade.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/commands/upgrade.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/console.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/console.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/locales/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/locales/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/locales/en.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/locales/en.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/locales/ja.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/locales/ja.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/locales/ru.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/locales/ru.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/locales/zh_cn.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/locales/zh_cn.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/locales/zh_tw.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/i18n/locales/zh_tw.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/registry.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/registry.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/utils/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/utils/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/utils/display.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/utils/display.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/utils/file_watcher.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/utils/file_watcher.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/utils/package_manager.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/CLI/utils/package_manager.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/adapter.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/client.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/client.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/errors.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/errors.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/kv_builder.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/kv_builder.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/manager.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/manager.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/module.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/module.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/router.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/router.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/send_builder.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/send_builder.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/send_rules.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/send_rules.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/storage.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/storage.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/websocket.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Bases/websocket.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/base.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/base.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/command.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/message.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/message.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/message_builder.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/message_builder.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/meta.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/meta.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/notice.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/notice.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/request.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/request.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/session_type.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/session_type.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/wrapper.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/Event/wrapper.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/admin.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/admin.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/client.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/client.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/config.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/config.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/constants.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/constants.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/locales/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/locales/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/locales/en.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/locales/ja.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/locales/ru.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/locales/zh_cn.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/i18n/locales/zh_tw.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/logger.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/logger.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/module.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/storage.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/Core/storage.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/__main__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/__main__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/finders/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/finders/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/finders/adapter.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/finders/adapter.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/finders/bases/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/finders/bases/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/finders/bases/finder.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/finders/bases/finder.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/finders/module.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/finders/module.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/adapter.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/adapter.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/bases/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/bases/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/bases/loader.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/bases/loader.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/module.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/module.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/strategy.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/strategy.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/strict.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/loaders/strict.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/py.typed +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/runtime/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/runtime/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/runtime/config_schema.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/runtime/config_schema.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/runtime/context.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/runtime/context.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/runtime/exceptions.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/runtime/exceptions.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/runtime/hints.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/runtime/hints.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/web_status/4xx.png +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/web_status/5xx.png +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/web_status/__init__.py +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/web_status/__init__.pyi +0 -0
- {erispulse-2.6.0.dev0 → erispulse-2.6.0.dev1}/src/ErisPulse/web_status/unknow.png +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ErisPulse
|
|
3
|
-
Version: 2.6.0.
|
|
3
|
+
Version: 2.6.0.dev1
|
|
4
4
|
Summary: Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署
|
|
5
5
|
Project-URL: Homepage, https://github.com/ErisPulse/ErisPulse
|
|
6
6
|
Project-URL: Documentation, https://www.erisdev.com#docs
|
|
@@ -69,7 +69,7 @@ Description-Content-Type: text/markdown
|
|
|
69
69
|
|
|
70
70
|
<img src=".github/assets/mascot-hero.png" align="right" width="300" alt="ErisPulse" style="margin-left: 24px; margin-bottom: 16px; border-radius: 12px;" />
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
**English** | [简体中文](README.zh-CN.md) | [繁體中文](README.zh-TW.md) | [日本語](README.ja.md) | [Русский](README.ru.md)
|
|
73
73
|
|
|
74
74
|
# ErisPulse
|
|
75
75
|
|
|
@@ -715,4 +715,4 @@ The early exploration and growth of ErisPulse would not have been possible witho
|
|
|
715
715
|
|
|
716
716
|
We also thank all developers and project authors who have contributed to ErisPulse, OneBot ecosystem, and the open-source community.
|
|
717
717
|
|
|
718
|
-
</div>
|
|
718
|
+
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<img src=".github/assets/mascot-hero.png" align="right" width="300" alt="ErisPulse" style="margin-left: 24px; margin-bottom: 16px; border-radius: 12px;" />
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**English** | [简体中文](README.zh-CN.md) | [繁體中文](README.zh-TW.md) | [日本語](README.ja.md) | [Русский](README.ru.md)
|
|
4
4
|
|
|
5
5
|
# ErisPulse
|
|
6
6
|
|
|
@@ -646,4 +646,4 @@ The early exploration and growth of ErisPulse would not have been possible witho
|
|
|
646
646
|
|
|
647
647
|
We also thank all developers and project authors who have contributed to ErisPulse, OneBot ecosystem, and the open-source community.
|
|
648
648
|
|
|
649
|
-
</div>
|
|
649
|
+
</div>
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ErisPulse"
|
|
7
|
-
version = "2.6.0-dev.
|
|
7
|
+
version = "2.6.0-dev.1"
|
|
8
8
|
description = "Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -464,7 +464,10 @@ class SendDSL:
|
|
|
464
464
|
if target_type == DETAIL_TYPE_PRIVATE:
|
|
465
465
|
target_type = "user"
|
|
466
466
|
|
|
467
|
-
|
|
467
|
+
instance = self.__class__(self._adapter, target_type, target_id, self._account_id)
|
|
468
|
+
if self._rules:
|
|
469
|
+
instance._rules = _copy_rules(self._rules)
|
|
470
|
+
return instance
|
|
468
471
|
|
|
469
472
|
def Using(self, account_id: str | int) -> "SendDSL":
|
|
470
473
|
"""
|
|
@@ -477,9 +480,12 @@ class SendDSL:
|
|
|
477
480
|
>>> adapter.Send.Using("bot1").To("123").Text("Hello")
|
|
478
481
|
>>> adapter.Send.To("123").Using("bot1").Text("Hello") # 支持乱序
|
|
479
482
|
"""
|
|
480
|
-
|
|
481
|
-
self._adapter, self._target_type, self._target_id, account_id
|
|
483
|
+
instance = self.__class__(
|
|
484
|
+
self._adapter, self._target_type, self._target_id, account_id
|
|
482
485
|
)
|
|
486
|
+
if self._rules:
|
|
487
|
+
instance._rules = _copy_rules(self._rules)
|
|
488
|
+
return instance
|
|
483
489
|
|
|
484
490
|
def Account(self, account_id: str | int) -> "SendDSL":
|
|
485
491
|
"""
|
|
@@ -492,9 +498,12 @@ class SendDSL:
|
|
|
492
498
|
>>> adapter.Send.Account("bot1").To("123").Text("Hello")
|
|
493
499
|
>>> adapter.Send.To("123").Account("bot1").Text("Hello") # 支持乱序
|
|
494
500
|
"""
|
|
495
|
-
|
|
496
|
-
self._adapter, self._target_type, self._target_id, account_id
|
|
501
|
+
instance = self.__class__(
|
|
502
|
+
self._adapter, self._target_type, self._target_id, account_id
|
|
497
503
|
)
|
|
504
|
+
if self._rules:
|
|
505
|
+
instance._rules = _copy_rules(self._rules)
|
|
506
|
+
return instance
|
|
498
507
|
|
|
499
508
|
# ==================== 发送规则装饰器 ====================
|
|
500
509
|
|
|
@@ -330,17 +330,14 @@ class CommandHandler:
|
|
|
330
330
|
|
|
331
331
|
return result
|
|
332
332
|
except asyncio.TimeoutError:
|
|
333
|
-
# 清理超时的等待
|
|
334
|
-
if wait_key in self._waiting_replies:
|
|
335
|
-
del self._waiting_replies[wait_key]
|
|
336
333
|
logger.trace(f"wait_reply 超时: key={wait_key}, timeout={timeout}s")
|
|
337
334
|
return None
|
|
338
335
|
except Exception as e:
|
|
339
|
-
# 清理异常情况
|
|
340
|
-
if wait_key in self._waiting_replies:
|
|
341
|
-
del self._waiting_replies[wait_key]
|
|
342
336
|
logger.error(f"等待回复时发生错误: {e}")
|
|
343
337
|
return None
|
|
338
|
+
finally:
|
|
339
|
+
# 无论成功、超时、异常还是 CancelledError,都确保清理等待条目
|
|
340
|
+
self._waiting_replies.pop(wait_key, None)
|
|
344
341
|
|
|
345
342
|
async def _handle_message(self, event: dict[str, Any]):
|
|
346
343
|
"""
|
|
@@ -23,6 +23,9 @@ from .constants import (
|
|
|
23
23
|
CONFIG_KEY_ADAPTER_STATUS,
|
|
24
24
|
CONFIG_KEY_ADAPTER_STATUS_OF,
|
|
25
25
|
DEFAULT_ADAPTER_ENABLED,
|
|
26
|
+
DEFAULT_HANDLER_DRAIN_TIMEOUT_SECS,
|
|
27
|
+
DEFAULT_HANDLER_MAX_CONCURRENCY,
|
|
28
|
+
DEFAULT_OFFLINE_BOT_EXPIRY_SECS,
|
|
26
29
|
HANDLER_SLOW_THRESHOLD_SECS,
|
|
27
30
|
)
|
|
28
31
|
from .i18n import i18n
|
|
@@ -91,6 +94,13 @@ class AdapterManager(ManagerBase):
|
|
|
91
94
|
# 后台任务追踪 - {platform: asyncio.Task}
|
|
92
95
|
self._adapter_tasks: dict[str, asyncio.Task] = {}
|
|
93
96
|
|
|
97
|
+
# 在途事件处理器 Task 集合(用于 shutdown 时等待/取消)
|
|
98
|
+
self._pending_handler_tasks: set[asyncio.Task] = set()
|
|
99
|
+
|
|
100
|
+
# 事件处理器并发控制信号量(运行时从配置加载,默认值由常量提供)
|
|
101
|
+
self._handler_semaphore: asyncio.Semaphore | None = None
|
|
102
|
+
self._handler_max_concurrency: int = 0 # 0 = 尚未初始化
|
|
103
|
+
|
|
94
104
|
# Bot状态存储 - {platform: {bot_id: {"status": str, "last_active": float, "info": dict}}}
|
|
95
105
|
self._bots: dict[str, dict[str, dict]] = {}
|
|
96
106
|
|
|
@@ -505,6 +515,8 @@ class AdapterManager(ManagerBase):
|
|
|
505
515
|
# 仅在关闭全部适配器时清理事件处理器,避免部分关闭影响其他适配器
|
|
506
516
|
all_platforms = set(self._adapters.keys())
|
|
507
517
|
if set(platforms) >= all_platforms:
|
|
518
|
+
# 等待或取消所有在途的事件处理器 Task
|
|
519
|
+
await self._drain_pending_handler_tasks()
|
|
508
520
|
self._onebot_handlers.clear()
|
|
509
521
|
self._raw_handlers.clear()
|
|
510
522
|
self._onebot_middlewares.clear()
|
|
@@ -550,6 +562,39 @@ class AdapterManager(ManagerBase):
|
|
|
550
562
|
# 回收该平台运行期间注册的路由/事件/命令(幂等)
|
|
551
563
|
self._cleanup_adapter_resources(platform)
|
|
552
564
|
|
|
565
|
+
async def _drain_pending_handler_tasks(
|
|
566
|
+
self, timeout: float = DEFAULT_HANDLER_DRAIN_TIMEOUT_SECS
|
|
567
|
+
) -> None:
|
|
568
|
+
"""
|
|
569
|
+
{!--< internal-use >!--}
|
|
570
|
+
等待或取消所有在途的事件处理器 Task
|
|
571
|
+
|
|
572
|
+
:param timeout: 等待 Task 退出的最长时间(秒)
|
|
573
|
+
"""
|
|
574
|
+
pending = list(self._pending_handler_tasks)
|
|
575
|
+
if not pending:
|
|
576
|
+
return
|
|
577
|
+
# 取消所有未完成的 Task
|
|
578
|
+
for task in pending:
|
|
579
|
+
if not task.done():
|
|
580
|
+
task.cancel()
|
|
581
|
+
# 等待它们退出(带超时)
|
|
582
|
+
try:
|
|
583
|
+
await asyncio.wait_for(
|
|
584
|
+
asyncio.gather(*pending, return_exceptions=True),
|
|
585
|
+
timeout=timeout,
|
|
586
|
+
)
|
|
587
|
+
except asyncio.TimeoutError:
|
|
588
|
+
logger.trace(
|
|
589
|
+
i18n.t(
|
|
590
|
+
"core.adapter.handler_drain_timeout",
|
|
591
|
+
count=len(pending),
|
|
592
|
+
timeout=timeout,
|
|
593
|
+
)
|
|
594
|
+
)
|
|
595
|
+
finally:
|
|
596
|
+
self._pending_handler_tasks.clear()
|
|
597
|
+
|
|
553
598
|
def _cleanup_adapter_resources(self, platform: str) -> None:
|
|
554
599
|
"""
|
|
555
600
|
{!--< internal-use >!--}
|
|
@@ -605,6 +650,20 @@ class AdapterManager(ManagerBase):
|
|
|
605
650
|
i18n.t("core.adapter.handlers_clean_failed", platform=platform, error=e)
|
|
606
651
|
)
|
|
607
652
|
|
|
653
|
+
# 清理该适配器注册的生命周期钩子
|
|
654
|
+
try:
|
|
655
|
+
lifecycle_removed = lifecycle.unregister_by_owner(platform)
|
|
656
|
+
if lifecycle_removed > 0:
|
|
657
|
+
logger.trace(
|
|
658
|
+
i18n.t(
|
|
659
|
+
"core.adapter.lifecycle_hooks_cleaned",
|
|
660
|
+
platform=platform,
|
|
661
|
+
count=lifecycle_removed,
|
|
662
|
+
)
|
|
663
|
+
)
|
|
664
|
+
except Exception:
|
|
665
|
+
pass
|
|
666
|
+
|
|
608
667
|
async def restart(self, platform: str) -> bool:
|
|
609
668
|
"""
|
|
610
669
|
重启指定平台适配器(shutdown + 资源兜底清理 + start)
|
|
@@ -716,6 +775,13 @@ class AdapterManager(ManagerBase):
|
|
|
716
775
|
task.cancel()
|
|
717
776
|
self._adapter_tasks.clear()
|
|
718
777
|
|
|
778
|
+
# 清除在途事件处理器 Task 引用(不等待,在 shutdown 中已处理)
|
|
779
|
+
self._pending_handler_tasks.clear()
|
|
780
|
+
|
|
781
|
+
# 重置并发信号量
|
|
782
|
+
self._handler_semaphore = None
|
|
783
|
+
self._handler_max_concurrency = 0
|
|
784
|
+
|
|
719
785
|
# 清除Bot状态
|
|
720
786
|
self._bots.clear()
|
|
721
787
|
|
|
@@ -1126,6 +1192,33 @@ class AdapterManager(ManagerBase):
|
|
|
1126
1192
|
},
|
|
1127
1193
|
)
|
|
1128
1194
|
|
|
1195
|
+
def _get_handler_semaphore(self) -> asyncio.Semaphore:
|
|
1196
|
+
"""
|
|
1197
|
+
{!--< internal-use >!--}
|
|
1198
|
+
获取事件处理器并发控制信号量
|
|
1199
|
+
|
|
1200
|
+
懒初始化,首次调用时从框架配置读取 handler_max_concurrency。
|
|
1201
|
+
配置变更后可通过设置 _handler_max_concurrency = 0 来强制重建。
|
|
1202
|
+
|
|
1203
|
+
:return: asyncio.Semaphore 并发控制信号量
|
|
1204
|
+
"""
|
|
1205
|
+
if self._handler_semaphore is None or self._handler_max_concurrency == 0:
|
|
1206
|
+
max_concurrency = DEFAULT_HANDLER_MAX_CONCURRENCY
|
|
1207
|
+
try:
|
|
1208
|
+
from ..runtime import get_framework_config
|
|
1209
|
+
|
|
1210
|
+
framework_config = get_framework_config()
|
|
1211
|
+
max_concurrency = framework_config.get(
|
|
1212
|
+
"handler_max_concurrency", max_concurrency
|
|
1213
|
+
)
|
|
1214
|
+
except Exception:
|
|
1215
|
+
pass
|
|
1216
|
+
if max_concurrency <= 0:
|
|
1217
|
+
max_concurrency = DEFAULT_HANDLER_MAX_CONCURRENCY
|
|
1218
|
+
self._handler_max_concurrency = max_concurrency
|
|
1219
|
+
self._handler_semaphore = asyncio.Semaphore(max_concurrency)
|
|
1220
|
+
return self._handler_semaphore
|
|
1221
|
+
|
|
1129
1222
|
def _dispatch_handler_task(
|
|
1130
1223
|
self,
|
|
1131
1224
|
func: Callable,
|
|
@@ -1133,7 +1226,7 @@ class AdapterManager(ManagerBase):
|
|
|
1133
1226
|
*,
|
|
1134
1227
|
event_type: str = "unknown",
|
|
1135
1228
|
platform: str = "unknown",
|
|
1136
|
-
) -> asyncio.Task:
|
|
1229
|
+
) -> asyncio.Task | None:
|
|
1137
1230
|
"""
|
|
1138
1231
|
{!--< internal-use >!--}
|
|
1139
1232
|
将事件处理器包装为独立 asyncio.Task 并调度执行
|
|
@@ -1147,20 +1240,21 @@ class AdapterManager(ManagerBase):
|
|
|
1147
1240
|
:param platform: 平台名称(用于日志)
|
|
1148
1241
|
:return: asyncio.Task
|
|
1149
1242
|
"""
|
|
1150
|
-
import time as _time
|
|
1151
|
-
|
|
1152
1243
|
_func_name = getattr(func, "__qualname__", getattr(func, "__name__", str(func)))
|
|
1153
1244
|
|
|
1154
|
-
# 在 Task 顶层准备 wait_reply
|
|
1245
|
+
# 在 Task 顶层准备 wait_reply 累加器(list 对象,可跨 ContextVar 共享)。
|
|
1155
1246
|
# 注意:ContextVar 的 set()/reset() 必须在同一个 Task Context 内完成,
|
|
1156
1247
|
# 所以下面把 .set() 移入 _safe_run,避免跨 Task token 错误。
|
|
1157
1248
|
_task_waits: list[dict] = []
|
|
1249
|
+
_sem = self._get_handler_semaphore()
|
|
1158
1250
|
|
|
1159
1251
|
async def _safe_run():
|
|
1160
1252
|
_wait_token = handler_waits.set(_task_waits)
|
|
1161
|
-
t0 =
|
|
1253
|
+
t0 = time.monotonic()
|
|
1162
1254
|
try:
|
|
1163
|
-
|
|
1255
|
+
# 并发背压控制:限制同时在途的 handler Task 数量
|
|
1256
|
+
async with _sem:
|
|
1257
|
+
await func(data)
|
|
1164
1258
|
except asyncio.CancelledError:
|
|
1165
1259
|
pass
|
|
1166
1260
|
except Exception as e:
|
|
@@ -1174,7 +1268,7 @@ class AdapterManager(ManagerBase):
|
|
|
1174
1268
|
)
|
|
1175
1269
|
)
|
|
1176
1270
|
finally:
|
|
1177
|
-
elapsed =
|
|
1271
|
+
elapsed = time.monotonic() - t0
|
|
1178
1272
|
handler_waits.reset(_wait_token)
|
|
1179
1273
|
|
|
1180
1274
|
_wait_total = sum(w.get("duration", 0.0) for w in _task_waits)
|
|
@@ -1217,9 +1311,16 @@ class AdapterManager(ManagerBase):
|
|
|
1217
1311
|
)
|
|
1218
1312
|
|
|
1219
1313
|
try:
|
|
1220
|
-
|
|
1314
|
+
task = asyncio.create_task(_safe_run())
|
|
1315
|
+
# 追踪 Task 以便 shutdown 时取消/等待
|
|
1316
|
+
self._pending_handler_tasks.add(task)
|
|
1317
|
+
task.add_done_callback(self._pending_handler_tasks.discard)
|
|
1318
|
+
return task
|
|
1221
1319
|
except RuntimeError:
|
|
1222
|
-
|
|
1320
|
+
task = asyncio.ensure_future(_safe_run())
|
|
1321
|
+
self._pending_handler_tasks.add(task)
|
|
1322
|
+
task.add_done_callback(self._pending_handler_tasks.discard)
|
|
1323
|
+
return task
|
|
1223
1324
|
|
|
1224
1325
|
# ==================== Bot状态管理 ====================
|
|
1225
1326
|
|
|
@@ -1365,6 +1466,43 @@ class AdapterManager(ManagerBase):
|
|
|
1365
1466
|
if bot_meta:
|
|
1366
1467
|
self._bots[platform][bot_id].setdefault("info", {}).update(bot_meta)
|
|
1367
1468
|
|
|
1469
|
+
def _evict_offline_bots(self, expiry_secs: int | None = None) -> int:
|
|
1470
|
+
"""
|
|
1471
|
+
{!--< internal-use >!--}
|
|
1472
|
+
清除过期的离线 Bot 记录
|
|
1473
|
+
|
|
1474
|
+
遍历 _bots,将状态为 offline 且 last_active 距今超过 expiry_secs 的条目移除。
|
|
1475
|
+
|
|
1476
|
+
:param expiry_secs: 过期时间(秒),None 时从框架配置读取
|
|
1477
|
+
:return: int 被清除的 Bot 记录数
|
|
1478
|
+
"""
|
|
1479
|
+
if expiry_secs is None:
|
|
1480
|
+
expiry_secs = DEFAULT_OFFLINE_BOT_EXPIRY_SECS
|
|
1481
|
+
try:
|
|
1482
|
+
from ..runtime import get_framework_config
|
|
1483
|
+
|
|
1484
|
+
framework_config = get_framework_config()
|
|
1485
|
+
expiry_secs = framework_config.get("offline_bot_expiry", expiry_secs)
|
|
1486
|
+
except Exception:
|
|
1487
|
+
pass
|
|
1488
|
+
if expiry_secs <= 0:
|
|
1489
|
+
return 0
|
|
1490
|
+
|
|
1491
|
+
now = time.time()
|
|
1492
|
+
evicted = 0
|
|
1493
|
+
for platform in list(self._bots.keys()):
|
|
1494
|
+
for bot_id in list(self._bots[platform].keys()):
|
|
1495
|
+
bot_info = self._bots[platform][bot_id]
|
|
1496
|
+
if (
|
|
1497
|
+
bot_info.get("status") == "offline"
|
|
1498
|
+
and (now - bot_info.get("last_active", 0)) > expiry_secs
|
|
1499
|
+
):
|
|
1500
|
+
del self._bots[platform][bot_id]
|
|
1501
|
+
evicted += 1
|
|
1502
|
+
if not self._bots[platform]:
|
|
1503
|
+
del self._bots[platform]
|
|
1504
|
+
return evicted
|
|
1505
|
+
|
|
1368
1506
|
def get_bot_info(self, platform: str, bot_id: str) -> dict | None:
|
|
1369
1507
|
"""
|
|
1370
1508
|
获取Bot详细信息
|
|
@@ -22,7 +22,7 @@ from ..runtime.context import current_owner, handler_waits
|
|
|
22
22
|
from .Bases.adapter import BaseAdapter
|
|
23
23
|
from .Bases.manager import ManagerBase
|
|
24
24
|
from .config import config
|
|
25
|
-
from .constants import ADAPTER_RETRY_BACKOFF_INTERVALS, ADAPTER_RETRY_FIXED_DELAY_SECS, CONFIG_KEY_ADAPTER_STATUS, CONFIG_KEY_ADAPTER_STATUS_OF, DEFAULT_ADAPTER_ENABLED, HANDLER_SLOW_THRESHOLD_SECS
|
|
25
|
+
from .constants import ADAPTER_RETRY_BACKOFF_INTERVALS, ADAPTER_RETRY_FIXED_DELAY_SECS, CONFIG_KEY_ADAPTER_STATUS, CONFIG_KEY_ADAPTER_STATUS_OF, DEFAULT_ADAPTER_ENABLED, DEFAULT_HANDLER_DRAIN_TIMEOUT_SECS, DEFAULT_HANDLER_MAX_CONCURRENCY, DEFAULT_OFFLINE_BOT_EXPIRY_SECS, HANDLER_SLOW_THRESHOLD_SECS
|
|
26
26
|
from .i18n import i18n
|
|
27
27
|
from .lifecycle import lifecycle
|
|
28
28
|
from .logger import logger
|
|
@@ -585,3 +585,37 @@ DEFAULT_WS_CLIENT_CONNECT_TIMEOUT_SECS: Final[float] = 10.0
|
|
|
585
585
|
# 才会按数组索引处理。超过此值的索引会被安全跳过,避免分配超大列表导致 OOM。
|
|
586
586
|
# 修改影响: 运行时行为。storage.set() 写入超大列表索引时的安全阈值。
|
|
587
587
|
STORAGE_MAX_LIST_INDEX: Final[int] = 10000
|
|
588
|
+
|
|
589
|
+
# ==============================================================================
|
|
590
|
+
# 性能优化与主动 GC
|
|
591
|
+
#
|
|
592
|
+
# 控制事件处理器并发上限、后台资源回收节奏和内存释放策略。
|
|
593
|
+
# 使用位置: Core/adapter.py -> emit(), shutdown()
|
|
594
|
+
# Core/lifecycle.py -> register()
|
|
595
|
+
# sdk.py -> _do_uninit()
|
|
596
|
+
# ==============================================================================
|
|
597
|
+
|
|
598
|
+
# 事件处理器最大并发 Task 数。
|
|
599
|
+
# 运行时行为。每个事件匹配的处理器在独立 Task 中执行,此值限制同时运行的 Task 数量。
|
|
600
|
+
# 修改影响: 设大提高并发吞吐但增加内存占用,设小限制资源消耗但可能降低事件处理速度。
|
|
601
|
+
DEFAULT_HANDLER_MAX_CONCURRENCY: Final[int] = 64
|
|
602
|
+
|
|
603
|
+
# 路由限流存储的自动清理间隔(秒)。
|
|
604
|
+
# 运行时行为。后台定期扫描 _rate_limit_store,清除过期的 IP 记录。
|
|
605
|
+
# 修改影响: 设大减少 CPU 开销但内存占用更久,设小更积极回收内存。
|
|
606
|
+
DEFAULT_RATE_LIMIT_CLEANUP_INTERVAL_SECS: Final[int] = 300
|
|
607
|
+
|
|
608
|
+
# 离线 Bot 信息的自动过期时间(秒)。
|
|
609
|
+
# 运行时行为。Bot 标记为 offline 后,经过此时间会被自动清除。
|
|
610
|
+
# 修改影响: 设大保留更多历史 Bot 信息,设小更快回收内存。0 表示不过期。
|
|
611
|
+
DEFAULT_OFFLINE_BOT_EXPIRY_SECS: Final[int] = 3600
|
|
612
|
+
|
|
613
|
+
# 主动 GC(垃圾回收)间隔(秒)。
|
|
614
|
+
# 运行时行为。SDK 初始化后启动定期 GC 后台任务,按此间隔触发 Python GC 和内部资源回收。
|
|
615
|
+
# 修改影响: 设大减少 CPU 中断但内存峰值更高,设小更频繁回收但增加开销。0 表示禁用。
|
|
616
|
+
DEFAULT_PROACTIVE_GC_INTERVAL_SECS: Final[int] = 300
|
|
617
|
+
|
|
618
|
+
# shutdown 时等待在途事件处理器完成的最长时间(秒)。
|
|
619
|
+
# 运行时行为。适配器关闭时取消所有 pending handler tasks 后等待它们退出的耐心时间。
|
|
620
|
+
# 修改影响: 设大确保处理器完整结束,设小加速关闭流程。
|
|
621
|
+
DEFAULT_HANDLER_DRAIN_TIMEOUT_SECS: Final[float] = 5.0
|
|
@@ -208,3 +208,13 @@ DEFAULT_WS_CLIENT_HEARTBEAT_SECS: Final[float | None]
|
|
|
208
208
|
DEFAULT_WS_CLIENT_CONNECT_TIMEOUT_SECS: Final[float]
|
|
209
209
|
|
|
210
210
|
STORAGE_MAX_LIST_INDEX: Final[int]
|
|
211
|
+
|
|
212
|
+
DEFAULT_HANDLER_MAX_CONCURRENCY: Final[int]
|
|
213
|
+
|
|
214
|
+
DEFAULT_RATE_LIMIT_CLEANUP_INTERVAL_SECS: Final[int]
|
|
215
|
+
|
|
216
|
+
DEFAULT_OFFLINE_BOT_EXPIRY_SECS: Final[int]
|
|
217
|
+
|
|
218
|
+
DEFAULT_PROACTIVE_GC_INTERVAL_SECS: Final[int]
|
|
219
|
+
|
|
220
|
+
DEFAULT_HANDLER_DRAIN_TIMEOUT_SECS: Final[float]
|
|
@@ -45,6 +45,8 @@ TRANSLATIONS = {
|
|
|
45
45
|
"core.sdk.uninit.timeout_msg": "SDK uninitialization timed out",
|
|
46
46
|
"core.sdk.uninit.failed_msg": "SDK uninitialization failed",
|
|
47
47
|
"core.sdk.uninit.critical_error": "SDK uninitialization critical error: {error}",
|
|
48
|
+
"core.sdk.uninit.client_close_failed": "Failed to close HTTP client connection pool: {error}",
|
|
49
|
+
"core.sdk.gc.collected": "[GC] Python collected {collected} object(s), evicted {evicted} offline bot(s)",
|
|
48
50
|
# ==================== SDK Environment Preparation ====================
|
|
49
51
|
"core.sdk.prepare.start": "Starting initialization",
|
|
50
52
|
"core.sdk.prepare.starting": "Preparing initialization environment...",
|
|
@@ -133,6 +135,8 @@ TRANSLATIONS = {
|
|
|
133
135
|
"core.adapter.bot_online": "Bot {platform}/{bot_id} online",
|
|
134
136
|
"core.adapter.handler_error": "Event handler execution error [{handler}] type={type} platform={platform}: {error}",
|
|
135
137
|
"core.adapter.handler_slow": "Event handler execution slow [{handler}] elapsed {elapsed}s > {threshold}s type={type} platform={platform}{tag}",
|
|
138
|
+
"core.adapter.handler_drain_timeout": "{count} pending event handler(s) did not exit within {timeout}s, force cancelled",
|
|
139
|
+
"core.adapter.lifecycle_hooks_cleaned": "Adapter {platform} cleaned {count} lifecycle hook(s)",
|
|
136
140
|
"core.adapter.auto_discover_bot": "Auto-discovered Bot: {platform}/{bot_id}",
|
|
137
141
|
"core.adapter.bot_status_change": "Bot status change: {platform}/{bot_id} {old} -> {new}",
|
|
138
142
|
"core.adapter.method_not_exist": "Method {method} does not exist",
|
|
@@ -161,6 +165,7 @@ TRANSLATIONS = {
|
|
|
161
165
|
"core.module.on_unload_failed": "Module {name} on_unload method execution failed: {error}",
|
|
162
166
|
"core.module.unload_routes_cleaned": "Cleaned routes for module {name}: HTTP={http}, WebSocket={ws}",
|
|
163
167
|
"core.module.unload_handlers_cleaned": "Cleaned {count} event handlers/commands for module {name}",
|
|
168
|
+
"core.module.lifecycle_hooks_cleaned": "Module {name} cleaned {count} lifecycle hook(s)",
|
|
164
169
|
"core.module.unload_success": "Module {name} unloaded successfully",
|
|
165
170
|
"core.module.unload_failed": "Failed to unload module {name}: {error}",
|
|
166
171
|
"core.module.registered_status": "Module {name} registered and {status}",
|
|
@@ -45,6 +45,8 @@ TRANSLATIONS = {
|
|
|
45
45
|
"core.sdk.uninit.timeout_msg": "SDK の非初期化がタイムアウトしました",
|
|
46
46
|
"core.sdk.uninit.failed_msg": "SDK の非初期化に失敗しました",
|
|
47
47
|
"core.sdk.uninit.critical_error": "SDK 非初期化の重大なエラー: {error}",
|
|
48
|
+
"core.sdk.uninit.client_close_failed": "HTTP クライアント接続プールのクローズに失敗しました: {error}",
|
|
49
|
+
"core.sdk.gc.collected": "[GC] Python が {collected} 個のオブジェクトを回収し、{evicted} 個のオフライン Bot を削除しました",
|
|
48
50
|
# ==================== SDK 環境準備 ====================
|
|
49
51
|
"core.sdk.prepare.start": "初期化を開始します",
|
|
50
52
|
"core.sdk.prepare.starting": "初期化環境を準備しています...",
|
|
@@ -133,6 +135,8 @@ TRANSLATIONS = {
|
|
|
133
135
|
"core.adapter.bot_online": "Bot {platform}/{bot_id} がオンラインになりました",
|
|
134
136
|
"core.adapter.handler_error": "イベントハンドラーの実行エラー [{handler}] type={type} platform={platform}: {error}",
|
|
135
137
|
"core.adapter.handler_slow": "イベントハンドラーの実行が遅いです [{handler}] 経過時間 {elapsed}s > {threshold}s type={type} platform={platform}{tag}",
|
|
138
|
+
"core.adapter.handler_drain_timeout": "{count} 個の保留中イベントハンドラーが {timeout}s 内に終了しなかったため、強制キャンセルされました",
|
|
139
|
+
"core.adapter.lifecycle_hooks_cleaned": "アダプター {platform} が {count} 個のライフサイクルフックをクリーンアップしました",
|
|
136
140
|
"core.adapter.auto_discover_bot": "Bot を自動検出しました: {platform}/{bot_id}",
|
|
137
141
|
"core.adapter.bot_status_change": "Bot の状態変化: {platform}/{bot_id} {old} -> {new}",
|
|
138
142
|
"core.adapter.method_not_exist": "メソッド {method} は存在しません",
|
|
@@ -161,6 +165,7 @@ TRANSLATIONS = {
|
|
|
161
165
|
"core.module.on_unload_failed": "モジュール {name} の on_unload メソッドの実行に失敗しました: {error}",
|
|
162
166
|
"core.module.unload_routes_cleaned": "モジュール {name} のルートをクリーンアップしました: HTTP={http}, WebSocket={ws}",
|
|
163
167
|
"core.module.unload_handlers_cleaned": "モジュール {name} の {count} 個のイベントハンドラー/コマンドをクリーンアップしました",
|
|
168
|
+
"core.module.lifecycle_hooks_cleaned": "モジュール {name} が {count} 個のライフサイクルフックをクリーンアップしました",
|
|
164
169
|
"core.module.unload_success": "モジュール {name} のアンロードに成功しました",
|
|
165
170
|
"core.module.unload_failed": "モジュール {name} のアンロードに失敗しました: {error}",
|
|
166
171
|
"core.module.registered_status": "モジュール {name} は登録され、{status}",
|
|
@@ -45,6 +45,8 @@ TRANSLATIONS = {
|
|
|
45
45
|
"core.sdk.uninit.timeout_msg": "Деинициализация SDK превысила время ожидания",
|
|
46
46
|
"core.sdk.uninit.failed_msg": "Ошибка деинициализации SDK",
|
|
47
47
|
"core.sdk.uninit.critical_error": "Критическая ошибка деинициализации SDK: {error}",
|
|
48
|
+
"core.sdk.uninit.client_close_failed": "Не удалось закрыть пул соединений HTTP-клиента: {error}",
|
|
49
|
+
"core.sdk.gc.collected": "[GC] Python собрал {collected} объект(ов), удалено {evicted} автономных бот(ов)",
|
|
48
50
|
# ==================== Подготовка окружения SDK ====================
|
|
49
51
|
"core.sdk.prepare.start": "Начало инициализации",
|
|
50
52
|
"core.sdk.prepare.starting": "Подготовка окружения инициализации...",
|
|
@@ -133,6 +135,8 @@ TRANSLATIONS = {
|
|
|
133
135
|
"core.adapter.bot_online": "Bot {platform}/{bot_id} в сети",
|
|
134
136
|
"core.adapter.handler_error": "Ошибка выполнения обработчика событий [{handler}] type={type} platform={platform}: {error}",
|
|
135
137
|
"core.adapter.handler_slow": "Медленное выполнение обработчика событий [{handler}] время {elapsed}s > {threshold}s type={type} platform={platform}{tag}",
|
|
138
|
+
"core.adapter.handler_drain_timeout": "{count} ожидающих обработчиков событий не завершились за {timeout}s, принудительно отменены",
|
|
139
|
+
"core.adapter.lifecycle_hooks_cleaned": "Адаптер {platform} очистил {count} перехватчиков жизненного цикла",
|
|
136
140
|
"core.adapter.auto_discover_bot": "Автообнаружение Bot: {platform}/{bot_id}",
|
|
137
141
|
"core.adapter.bot_status_change": "Изменение состояния Bot: {platform}/{bot_id} {old} -> {new}",
|
|
138
142
|
"core.adapter.method_not_exist": "Метод {method} не существует",
|
|
@@ -161,6 +165,7 @@ TRANSLATIONS = {
|
|
|
161
165
|
"core.module.on_unload_failed": "Ошибка выполнения метода on_unload модуля {name}: {error}",
|
|
162
166
|
"core.module.unload_routes_cleaned": "Очищены маршруты для модуля {name}: HTTP={http}, WebSocket={ws}",
|
|
163
167
|
"core.module.unload_handlers_cleaned": "Очищено {count} обработчиков событий/команд для модуля {name}",
|
|
168
|
+
"core.module.lifecycle_hooks_cleaned": "Модуль {name} очистил {count} перехватчиков жизненного цикла",
|
|
164
169
|
"core.module.unload_success": "Модуль {name} успешно выгружен",
|
|
165
170
|
"core.module.unload_failed": "Ошибка выгрузки модуля {name}: {error}",
|
|
166
171
|
"core.module.registered_status": "Модуль {name} зарегистрирован и {status}",
|
|
@@ -45,6 +45,8 @@ TRANSLATIONS = {
|
|
|
45
45
|
"core.sdk.uninit.timeout_msg": "SDK反初始化超时",
|
|
46
46
|
"core.sdk.uninit.failed_msg": "SDK反初始化失败",
|
|
47
47
|
"core.sdk.uninit.critical_error": "SDK反初始化严重错误: {error}",
|
|
48
|
+
"core.sdk.uninit.client_close_failed": "关闭 HTTP 客户端连接池失败: {error}",
|
|
49
|
+
"core.sdk.gc.collected": "[GC] Python 回收了 {collected} 个对象,清理了 {evicted} 个离线 Bot",
|
|
48
50
|
# ==================== SDK 环境准备 ====================
|
|
49
51
|
"core.sdk.prepare.start": "开始初始化",
|
|
50
52
|
"core.sdk.prepare.starting": "准备初始化环境...",
|
|
@@ -133,6 +135,8 @@ TRANSLATIONS = {
|
|
|
133
135
|
"core.adapter.bot_online": "Bot {platform}/{bot_id} 上线",
|
|
134
136
|
"core.adapter.handler_error": "事件处理器执行错误 [{handler}] type={type} platform={platform}: {error}",
|
|
135
137
|
"core.adapter.handler_slow": "事件处理器执行缓慢 [{handler}] 耗时 {elapsed}s > {threshold}s type={type} platform={platform}{tag}",
|
|
138
|
+
"core.adapter.handler_drain_timeout": "{count} 个在途事件处理器未在 {timeout}s 内退出,已强制取消",
|
|
139
|
+
"core.adapter.lifecycle_hooks_cleaned": "适配器 {platform} 清理了 {count} 个生命周期钩子",
|
|
136
140
|
"core.adapter.auto_discover_bot": "自动发现Bot: {platform}/{bot_id}",
|
|
137
141
|
"core.adapter.bot_status_change": "Bot状态变更: {platform}/{bot_id} {old} -> {new}",
|
|
138
142
|
"core.adapter.method_not_exist": "方法 {method} 不存在",
|
|
@@ -161,6 +165,7 @@ TRANSLATIONS = {
|
|
|
161
165
|
"core.module.on_unload_failed": "模块 {name} on_unload 方法执行失败: {error}",
|
|
162
166
|
"core.module.unload_routes_cleaned": "已清理模块 {name} 的路由: HTTP={http}, WebSocket={ws}",
|
|
163
167
|
"core.module.unload_handlers_cleaned": "已清理模块 {name} 的 {count} 个事件处理器/命令",
|
|
168
|
+
"core.module.lifecycle_hooks_cleaned": "模块 {name} 清理了 {count} 个生命周期钩子",
|
|
164
169
|
"core.module.unload_success": "模块 {name} 卸载成功",
|
|
165
170
|
"core.module.unload_failed": "卸载模块 {name} 失败: {error}",
|
|
166
171
|
"core.module.registered_status": "模块 {name} 已注册并{status}",
|
|
@@ -45,6 +45,8 @@ TRANSLATIONS = {
|
|
|
45
45
|
"core.sdk.uninit.timeout_msg": "SDK反初始化逾時",
|
|
46
46
|
"core.sdk.uninit.failed_msg": "SDK反初始化失敗",
|
|
47
47
|
"core.sdk.uninit.critical_error": "SDK反初始化嚴重錯誤: {error}",
|
|
48
|
+
"core.sdk.uninit.client_close_failed": "關閉 HTTP 客戶端連線池失敗: {error}",
|
|
49
|
+
"core.sdk.gc.collected": "[GC] Python 回收了 {collected} 個物件,清理了 {evicted} 個離線 Bot",
|
|
48
50
|
# ==================== SDK 環境準備 ====================
|
|
49
51
|
"core.sdk.prepare.start": "開始初始化",
|
|
50
52
|
"core.sdk.prepare.starting": "準備初始化環境...",
|
|
@@ -133,6 +135,8 @@ TRANSLATIONS = {
|
|
|
133
135
|
"core.adapter.bot_online": "Bot {platform}/{bot_id} 上線",
|
|
134
136
|
"core.adapter.handler_error": "事件處理器執行錯誤 [{handler}] type={type} platform={platform}: {error}",
|
|
135
137
|
"core.adapter.handler_slow": "事件處理器執行緩慢 [{handler}] 耗時 {elapsed}s > {threshold}s type={type} platform={platform}{tag}",
|
|
138
|
+
"core.adapter.handler_drain_timeout": "{count} 個在途事件處理器未在 {timeout}s 內退出,已強制取消",
|
|
139
|
+
"core.adapter.lifecycle_hooks_cleaned": "介面卡 {platform} 清理了 {count} 個生命週期鉤子",
|
|
136
140
|
"core.adapter.auto_discover_bot": "自動發現Bot: {platform}/{bot_id}",
|
|
137
141
|
"core.adapter.bot_status_change": "Bot狀態變更: {platform}/{bot_id} {old} -> {new}",
|
|
138
142
|
"core.adapter.method_not_exist": "方法 {method} 不存在",
|
|
@@ -161,6 +165,7 @@ TRANSLATIONS = {
|
|
|
161
165
|
"core.module.on_unload_failed": "模組 {name} on_unload 方法執行失敗: {error}",
|
|
162
166
|
"core.module.unload_routes_cleaned": "已清理模組 {name} 的路由: HTTP={http}, WebSocket={ws}",
|
|
163
167
|
"core.module.unload_handlers_cleaned": "已清理模組 {name} 的 {count} 個事件處理器/命令",
|
|
168
|
+
"core.module.lifecycle_hooks_cleaned": "模組 {name} 清理了 {count} 個生命週期鉤子",
|
|
164
169
|
"core.module.unload_success": "模組 {name} 卸載成功",
|
|
165
170
|
"core.module.unload_failed": "卸載模組 {name} 失敗: {error}",
|
|
166
171
|
"core.module.registered_status": "模組 {name} 已註冊並{status}",
|
|
@@ -19,6 +19,7 @@ from typing import Any
|
|
|
19
19
|
|
|
20
20
|
from .constants import DEFAULT_EVENT_SOURCE, HANDLER_SLOW_THRESHOLD_SECS
|
|
21
21
|
from .i18n import i18n
|
|
22
|
+
from ..runtime.context import current_owner
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class _NullLogger:
|
|
@@ -108,7 +109,8 @@ class LifecycleManager:
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
def __init__(self):
|
|
111
|
-
|
|
112
|
+
# _hooks 存储 (priority, handler, owner) 三元组
|
|
113
|
+
self._hooks: dict[str, list[tuple[int, Callable, str | None]]] = {}
|
|
112
114
|
self._timers: dict[str, float] = {}
|
|
113
115
|
|
|
114
116
|
# ==================== 注册 API ====================
|
|
@@ -136,7 +138,8 @@ class LifecycleManager:
|
|
|
136
138
|
raise ValueError(i18n.t("core.lifecycle.event_name_required"))
|
|
137
139
|
|
|
138
140
|
def decorator(func: Callable) -> Callable:
|
|
139
|
-
|
|
141
|
+
owner = current_owner.get()
|
|
142
|
+
self._hooks.setdefault(event, []).append((priority, func, owner))
|
|
140
143
|
self._hooks[event].sort(key=lambda x: x[0], reverse=True)
|
|
141
144
|
return func
|
|
142
145
|
|
|
@@ -155,7 +158,8 @@ class LifecycleManager:
|
|
|
155
158
|
"""
|
|
156
159
|
if not isinstance(event, str) or not event:
|
|
157
160
|
raise ValueError(i18n.t("core.lifecycle.event_name_required"))
|
|
158
|
-
|
|
161
|
+
owner = current_owner.get()
|
|
162
|
+
self._hooks.setdefault(event, []).append((priority, handler, owner))
|
|
159
163
|
self._hooks[event].sort(key=lambda x: x[0], reverse=True)
|
|
160
164
|
|
|
161
165
|
def unregister(self, event: str, handler: Callable = None):
|
|
@@ -173,7 +177,30 @@ class LifecycleManager:
|
|
|
173
177
|
self._hooks.pop(event, None)
|
|
174
178
|
else:
|
|
175
179
|
handlers = self._hooks.get(event, [])
|
|
176
|
-
self._hooks[event] = [(p, h) for p, h in handlers if h != handler]
|
|
180
|
+
self._hooks[event] = [(p, h, o) for p, h, o in handlers if h != handler]
|
|
181
|
+
|
|
182
|
+
def unregister_by_owner(self, owner: str) -> int:
|
|
183
|
+
"""
|
|
184
|
+
取消指定 owner 注册的所有事件处理器
|
|
185
|
+
|
|
186
|
+
用于模块/适配器卸载时自动清理其注册的钩子,避免闭包引用导致内存泄漏。
|
|
187
|
+
|
|
188
|
+
:param owner: 模块或适配器名称
|
|
189
|
+
:return: int 被移除的处理器数量
|
|
190
|
+
|
|
191
|
+
:example:
|
|
192
|
+
>>> lifecycle.unregister_by_owner("MyModule")
|
|
193
|
+
"""
|
|
194
|
+
removed = 0
|
|
195
|
+
for event in list(self._hooks.keys()):
|
|
196
|
+
original_len = len(self._hooks[event])
|
|
197
|
+
self._hooks[event] = [
|
|
198
|
+
(p, h, o) for p, h, o in self._hooks[event] if o != owner
|
|
199
|
+
]
|
|
200
|
+
removed += original_len - len(self._hooks[event])
|
|
201
|
+
if not self._hooks[event]:
|
|
202
|
+
del self._hooks[event]
|
|
203
|
+
return removed
|
|
177
204
|
|
|
178
205
|
# ==================== 触发 API ====================
|
|
179
206
|
|
|
@@ -346,7 +373,7 @@ class LifecycleManager:
|
|
|
346
373
|
"""
|
|
347
374
|
import time as _time
|
|
348
375
|
|
|
349
|
-
for priority, handler in self._hooks[hook_name]:
|
|
376
|
+
for priority, handler, _owner in self._hooks[hook_name]:
|
|
350
377
|
try:
|
|
351
378
|
_t = _time.monotonic()
|
|
352
379
|
_hname = getattr(
|
|
@@ -381,7 +408,7 @@ class LifecycleManager:
|
|
|
381
408
|
:param data: Any 事件数据
|
|
382
409
|
:return: Any 处理后的数据
|
|
383
410
|
"""
|
|
384
|
-
for _, handler in self._hooks[hook_name]:
|
|
411
|
+
for _, handler, _owner in self._hooks[hook_name]:
|
|
385
412
|
try:
|
|
386
413
|
if inspect.iscoroutinefunction(handler):
|
|
387
414
|
try:
|