mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 11:16:43 +00:00
Compare commits
146 Commits
v2.0.0-bet
...
v2.0.0-rc.
Author | SHA1 | Date | |
---|---|---|---|
|
3234871b53 | ||
|
03543f01f2 | ||
|
ba5c0303c7 | ||
|
e56fdd04ad | ||
|
9f10bb70db | ||
|
71aad502d1 | ||
|
ab85b8651e | ||
|
4fe8929441 | ||
|
5c303710f6 | ||
|
68d2ada94b | ||
|
75470fe157 | ||
|
47b3fc516a | ||
|
84c24b014f | ||
|
756cde6525 | ||
|
57ef19af94 | ||
|
5927b517e2 | ||
|
132205bfcc | ||
|
b31dfa9ab0 | ||
|
b249802c38 | ||
|
9df705aaa7 | ||
|
a0df535f0c | ||
|
31022a653d | ||
|
ba77443dde | ||
|
984f743097 | ||
|
638a9c94af | ||
|
92f1d5a4d7 | ||
|
248af2ae1a | ||
|
4c37be7312 | ||
|
2cb8eafa81 | ||
|
05bff5ec17 | ||
|
13245cb58f | ||
|
37bc7326b5 | ||
|
f6d189d8c5 | ||
|
600ef7031f | ||
|
7bedf7c8d0 | ||
|
f62ee5893c | ||
|
71234e9a68 | ||
|
3bbca0fa70 | ||
|
20f144ba93 | ||
|
4c8bc9f0cb | ||
|
064509f26b | ||
|
8c42490a7e | ||
|
179d7105c9 | ||
|
1c14e638c8 | ||
|
c6eef06b55 | ||
|
beef564a22 | ||
|
672f2ceecc | ||
|
28142402d7 | ||
|
b886329fb8 | ||
|
a0b186aff3 | ||
|
595c64e760 | ||
|
5114749073 | ||
|
af2d7b5797 | ||
|
56943c0908 | ||
|
45478deb95 | ||
|
d281ec5bf9 | ||
|
291a7cbb8b | ||
|
41259546bd | ||
|
f96038241f | ||
|
b051320d78 | ||
|
d12efac9f4 | ||
|
f87a38a30a | ||
|
bf016b3f69 | ||
|
373f5255f1 | ||
|
5a35015195 | ||
|
d3a2f1dc08 | ||
|
f1aec4eb10 | ||
|
cd30be21ba | ||
|
f150a9ee89 | ||
|
e68281f60f | ||
|
32be64485a | ||
|
c76f492305 | ||
|
29b0351644 | ||
|
ef3350fd9c | ||
|
459699de5c | ||
|
31c3eb8fd6 | ||
|
1cfdee2645 | ||
|
4e76518a58 | ||
|
b53e029df1 | ||
|
c1faf68806 | ||
|
fe64b904ff | ||
|
a621ade449 | ||
|
3fda978064 | ||
|
60ab93164c | ||
|
2b22d5abda | ||
|
e3a4834383 | ||
|
21087036af | ||
|
94d336ef4d | ||
|
07707213a5 | ||
|
7579878fb4 | ||
|
6599b6420e | ||
|
135c6e8168 | ||
|
743e7363ea | ||
|
a101428c81 | ||
|
0d2b1f693e | ||
|
e5a53dfd5c | ||
|
915c2b3e43 | ||
|
767b6a9913 | ||
|
3f8af04803 | ||
|
00af815b8a | ||
|
24df594b97 | ||
|
d6567f9288 | ||
|
4eb158245e | ||
|
ef35266d3e | ||
|
1d1beb100a | ||
|
06ab6093b7 | ||
|
c1ce7fb940 | ||
|
6e03ddbf12 | ||
|
40c8787828 | ||
|
be459e0bbb | ||
|
1056828f90 | ||
|
ef18e8943d | ||
|
92ff1df419 | ||
|
be5ac88a18 | ||
|
fac647370a | ||
|
05a3891903 | ||
|
4deae8f00c | ||
|
0f70e975b0 | ||
|
982680be91 | ||
|
96b0a863e6 | ||
|
d64bb37c6d | ||
|
51d7f1783d | ||
|
64c18379c9 | ||
|
2735f0cba9 | ||
|
660dbaf3b8 | ||
|
898c29d7ee | ||
|
cdc507bab9 | ||
|
f32bcdc1fc | ||
|
013602da21 | ||
|
4974c596ec | ||
|
0620bec51f | ||
|
8870e6a26e | ||
|
0e3ed0e7ab | ||
|
6cc3b68447 | ||
|
549a37b172 | ||
|
16394ad68b | ||
|
57e580c255 | ||
|
6c23d89494 | ||
|
675e70f579 | ||
|
7a098b96f8 | ||
|
c9794bf91d | ||
|
1766d4da69 | ||
|
6583bc8c61 | ||
|
179f16346a | ||
|
badb0c9ff4 | ||
|
ee0ea85e40 |
2
.github/actions/setup-python/action.yml
vendored
2
.github/actions/setup-python/action.yml
vendored
@@ -5,7 +5,7 @@ inputs:
|
||||
python-version:
|
||||
description: Python version
|
||||
required: false
|
||||
default: "3.9"
|
||||
default: "3.10"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
|
2
.github/workflows/codecov.yml
vendored
2
.github/workflows/codecov.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
cancel-in-progress: true
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||
python-version: ["3.8", "3.9", "3.10"]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
fail-fast: false
|
||||
env:
|
||||
|
@@ -13,20 +13,20 @@ repos:
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.6.0
|
||||
rev: 22.8.0
|
||||
hooks:
|
||||
- id: black
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v2.7.1
|
||||
rev: v3.0.0-alpha.0
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [javascript, jsx, ts, tsx, markdown, yaml]
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/nonebot/nonemoji
|
||||
rev: v0.1.2
|
||||
rev: v0.1.3
|
||||
hooks:
|
||||
- id: nonemoji
|
||||
stages: [prepare-commit-msg]
|
||||
|
44
README.md
44
README.md
@@ -21,7 +21,7 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
||||
<a href="https://pypi.python.org/pypi/nonebot2">
|
||||
<img src="https://img.shields.io/pypi/v/nonebot2" alt="pypi">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/python-3.7.3+-blue" alt="python">
|
||||
<img src="https://img.shields.io/badge/python-3.8+-blue" alt="python">
|
||||
<a href="https://codecov.io/gh/nonebot/nonebot2">
|
||||
<img src="https://codecov.io/gh/nonebot/nonebot2/branch/master/graph/badge.svg?token=2P0G0VS7N4" alt="codecov"/>
|
||||
</a>
|
||||
@@ -38,17 +38,20 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
||||
<a href="https://onebot.dev/">
|
||||
<img src="https://img.shields.io/badge/OneBot-v12-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==" alt="onebot">
|
||||
</a>
|
||||
<a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
|
||||
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="dingtalk">
|
||||
</a>
|
||||
<a href="https://core.telegram.org/bots/api">
|
||||
<img src="https://img.shields.io/badge/telegram-Bot-lightgrey?style=social&logo=telegram" alt="telegram">
|
||||
</a>
|
||||
<a href="https://open.feishu.cn/document/home/index">
|
||||
<img src="https://img.shields.io/badge/%E9%A3%9E%E4%B9%A6-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNyAyOUMyMSAyOSAyNSAyNi45MzM5IDI4IDIzLjQwNjVDMzYgMTQgNDEuNDI0MiAxNi44MTY2IDQ0IDE3Ljk5OThDMzguNSAyMC45OTk4IDQwLjUgMjkuNjIzMyAzMyAzNS45OTk4QzI4LjM4MiAzOS45MjU5IDIzLjQ5NDUgNDEuMDE0IDE5IDQxQzEyLjUyMzEgNDAuOTc5OSA2Ljg2MjI2IDM3Ljc2MzcgNCAzNS40MDYzVjE2Ljk5OTgiIHN0cm9rZT0iIzMzMyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBkPSJNNS42NDgwOCAxNS44NjY5QzUuMDIyMzEgMTQuOTU2NyAzLjc3NzE1IDE0LjcyNjEgMi44NjY5NCAxNS4zNTE5QzEuOTU2NzMgMTUuOTc3NyAxLjcyNjE1IDE3LjIyMjggMi4zNTE5MiAxOC4xMzMxTDUuNjQ4MDggMTUuODY2OVpNMzYuMDAyMSAzNS43MzA5QzM2Ljk1OCAzNS4xNzc0IDM3LjI4NDMgMzMuOTUzOSAzNi43MzA5IDMyLjk5NzlDMzYuMTc3NCAzMi4wNDIgMzQuOTUzOSAzMS43MTU3IDMzLjk5NzkgMzIuMjY5MUwzNi4wMDIxIDM1LjczMDlaTTIuMzUxOTIgMTguMTMzMUM1LjI0MzUgMjIuMzM5IDEwLjc5OTIgMjguMTQ0IDE2Ljg4NjUgMzIuMjIzOUMxOS45MzQ1IDM0LjI2NjcgMjMuMjE3IDM1Ljk0NiAyNi40NDkgMzYuNzMyNEMyOS42OTQ2IDM3LjUyMiAzMy4wNDUxIDM3LjQ0MjggMzYuMDAyMSAzNS43MzA5TDMzLjk5NzkgMzIuMjY5MUMzMi4yMDQ5IDMzLjMwNzIgMjkuOTkyOSAzMy40NzggMjcuMzk0NyAzMi44NDU4QzI0Ljc4MyAzMi4yMTAzIDIxLjk0MDUgMzAuNzk1OCAxOS4xMTM1IDI4LjkwMTFDMTMuNDUwOCAyNS4xMDYgOC4yNTY1IDE5LjY2MSA1LjY0ODA4IDE1Ljg2NjlMMi4zNTE5MiAxOC4xMzMxWiIgZmlsbD0iIzMzMyIvPjxwYXRoIGQ9Ik0zMy41OTQ1IDE3QzMyLjgzOTggMTQuNzAyNyAzMC44NTQ5IDkuOTQwNTQgMjcuNTk0NSA3SDExLjU5NDVDMTUuMjE3MSAxMC42NzU3IDIzIDE2IDI3IDI0IiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PC9zdmc+" alt="feishu">
|
||||
</a>
|
||||
<a href="https://docs.github.com/en/developers/apps">
|
||||
<img src="https://img.shields.io/badge/GitHub-Bot-181717?style=social&logo=github" alt="github"/>
|
||||
</a>
|
||||
<a href="https://bot.q.qq.com/wiki/">
|
||||
<img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==" alt="QQ频道">
|
||||
<a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
|
||||
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="dingtalk">
|
||||
</a>
|
||||
</a>
|
||||
<br />
|
||||
<a href="https://jq.qq.com/?_wv=1027&k=5OFifDh">
|
||||
@@ -92,17 +95,28 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
||||
- 生而可靠:100% 类型注解覆盖,配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](https://v2.nonebot.dev/docs/start/editor-support))
|
||||
- 社区丰富:社区用户众多,直接和间接用户超过十万人,每天都有大量的活跃用户 ([社区资源](#社区资源))
|
||||
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
|
||||
- [OneBot 协议](https://onebot.dev/) (QQ 等)
|
||||
- [钉钉](https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p)
|
||||
- [Telegram](https://core.telegram.org/bots/api)
|
||||
- [飞书](https://open.feishu.cn/document/home/index)
|
||||
- [QQ 频道](https://bot.q.qq.com/wiki/)
|
||||
- 坚实后盾:支持多种 web 框架,可自定义替换
|
||||
- [FastAPI](https://fastapi.tiangolo.com/)
|
||||
- [Quart](https://pgjones.gitlab.io/quart/) (异步 Flask)
|
||||
- [aiohttp](https://docs.aiohttp.org/en/stable/)
|
||||
- [httpx](https://www.python-httpx.org/)
|
||||
- [websockets](https://websockets.readthedocs.io/en/stable/)
|
||||
|
||||
| 协议名称 | 状态 | 注释 |
|
||||
| :---------------------------------------------------: | :--: | :----------------------------------------------------------------: |
|
||||
| [OneBot 协议](https://onebot.dev/) | ✅ | 支持 QQ、TG、微信公众号等[平台](https://onebot.dev/ecosystem.html) |
|
||||
| [Telegram](https://core.telegram.org/bots/api) | ✅ | |
|
||||
| [飞书](https://open.feishu.cn/document/home/index) | ✅ | |
|
||||
| [GitHub](https://docs.github.com/en/developers/apps) | ✅ | GitHub APP & OAuth APP |
|
||||
| [QQ 频道](https://bot.q.qq.com/wiki/) | ✅ | 官方接口调整较多 |
|
||||
| [钉钉](https://open.dingtalk.com/document/) | 🤗 | 寻找 Maintainer |
|
||||
| Console | ✅ | 控制台交互 |
|
||||
| [开黑啦](https://developer.kookapp.cn/) | ↗️ | 由社区贡献 |
|
||||
| [Mirai](https://docs.mirai.mamoe.net/mirai-api-http/) | ↗️ | 由社区贡献 |
|
||||
|
||||
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
||||
|
||||
| 驱动框架 | 类型 |
|
||||
| :--------------------------------------------------------: | :----: |
|
||||
| [FastAPI](https://fastapi.tiangolo.com/) | 服务端 |
|
||||
| [Quart](https://pgjones.gitlab.io/quart/) (异步 Flask) | 服务端 |
|
||||
| [aiohttp](https://docs.aiohttp.org/en/stable/) | 客户端 |
|
||||
| [httpx](https://www.python-httpx.org/) | 客户端 |
|
||||
| [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 |
|
||||
|
||||
更多:[概览](https://v2.nonebot.dev/docs/)
|
||||
|
||||
|
@@ -16,6 +16,7 @@
|
||||
- `on_command` => {ref}``on_command` <nonebot.plugin.on.on_command>`
|
||||
- `on_shell_command` => {ref}``on_shell_command` <nonebot.plugin.on.on_shell_command>`
|
||||
- `on_regex` => {ref}``on_regex` <nonebot.plugin.on.on_regex>`
|
||||
- `on_type` => {ref}``on_type` <nonebot.plugin.on.on_type>`
|
||||
- `CommandGroup` => {ref}``CommandGroup` <nonebot.plugin.on.CommandGroup>`
|
||||
- `Matchergroup` => {ref}``MatcherGroup` <nonebot.plugin.on.MatcherGroup>`
|
||||
- `load_plugin` => {ref}``load_plugin` <nonebot.plugin.load.load_plugin>`
|
||||
@@ -29,7 +30,6 @@
|
||||
- `get_plugin_by_module_name` => {ref}``get_plugin_by_module_name` <nonebot.plugin.get_plugin_by_module_name>`
|
||||
- `get_loaded_plugins` => {ref}``get_loaded_plugins` <nonebot.plugin.get_loaded_plugins>`
|
||||
- `get_available_plugin_names` => {ref}``get_available_plugin_names` <nonebot.plugin.get_available_plugin_names>`
|
||||
- `export` => {ref}``export` <nonebot.plugin.export.export>`
|
||||
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
||||
|
||||
FrontMatter:
|
||||
@@ -40,10 +40,12 @@ FrontMatter:
|
||||
import importlib
|
||||
from typing import Any, Dict, Type, Optional
|
||||
|
||||
import loguru
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.utils import escape_tag
|
||||
from nonebot.config import Env, Config
|
||||
from nonebot.log import logger, default_filter
|
||||
from nonebot.drivers import Driver, ReverseDriver, combine_driver
|
||||
|
||||
try:
|
||||
@@ -172,8 +174,7 @@ def get_bots() -> Dict[str, Bot]:
|
||||
bots = nonebot.get_bots()
|
||||
```
|
||||
"""
|
||||
driver = get_driver()
|
||||
return driver.bots
|
||||
return get_driver().bots
|
||||
|
||||
|
||||
def _resolve_dot_notation(
|
||||
@@ -207,6 +208,15 @@ def _resolve_combine_expr(obj_str: str) -> Type[Driver]:
|
||||
return combine_driver(DriverClass, *mixins)
|
||||
|
||||
|
||||
def _log_patcher(record: "loguru.Record"):
|
||||
record["name"] = (
|
||||
plugin.name
|
||||
if (module_name := record["name"])
|
||||
and (plugin := get_plugin_by_module_name(module_name))
|
||||
else (module_name and module_name.split(".")[0])
|
||||
)
|
||||
|
||||
|
||||
def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
|
||||
"""初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。
|
||||
|
||||
@@ -233,7 +243,9 @@ def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
|
||||
_env_file=_env_file or f".env.{env.environment}",
|
||||
)
|
||||
|
||||
default_filter.level = config.log_level
|
||||
logger.configure(
|
||||
extra={"nonebot_log_level": config.log_level}, patcher=_log_patcher
|
||||
)
|
||||
logger.opt(colors=True).info(
|
||||
f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>"
|
||||
)
|
||||
@@ -241,7 +253,7 @@ def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
|
||||
f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}"
|
||||
)
|
||||
|
||||
DriverClass: Type[Driver] = _resolve_combine_expr(config.driver)
|
||||
DriverClass = _resolve_combine_expr(config.driver)
|
||||
_driver = DriverClass(env, config)
|
||||
|
||||
|
||||
@@ -262,7 +274,7 @@ def run(*args: Any, **kwargs: Any) -> None:
|
||||
|
||||
|
||||
from nonebot.plugin import on as on
|
||||
from nonebot.plugin import export as export
|
||||
from nonebot.plugin import on_type as on_type
|
||||
from nonebot.plugin import require as require
|
||||
from nonebot.plugin import on_regex as on_regex
|
||||
from nonebot.plugin import on_notice as on_notice
|
||||
|
@@ -25,7 +25,6 @@ from pydantic.env_settings import (
|
||||
)
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.utils import escape_tag
|
||||
|
||||
|
||||
class CustomEnvSettings(EnvSettingsSource):
|
||||
@@ -85,13 +84,15 @@ class CustomEnvSettings(EnvSettingsSource):
|
||||
if env_file_vars:
|
||||
for env_name in env_file_vars.keys():
|
||||
env_val = env_vars[env_name]
|
||||
try:
|
||||
if env_val:
|
||||
env_val = settings.__config__.json_loads(env_val.strip())
|
||||
except ValueError as e:
|
||||
logger.opt(colors=True, exception=e).trace(
|
||||
f"Error while parsing JSON for {escape_tag(env_name)}. Assumed as string."
|
||||
)
|
||||
if env_val and (val_striped := env_val.strip()):
|
||||
try:
|
||||
env_val = settings.__config__.json_loads(val_striped)
|
||||
except ValueError as e:
|
||||
logger.trace(
|
||||
"Error while parsing JSON for "
|
||||
f"{env_name!r}={val_striped!r}. "
|
||||
"Assumed as string."
|
||||
)
|
||||
|
||||
d[env_name] = env_val
|
||||
|
||||
|
@@ -4,7 +4,7 @@ FrontMatter:
|
||||
sidebar_position: 9
|
||||
description: nonebot.consts 模块
|
||||
"""
|
||||
from typing_extensions import Literal
|
||||
from typing import Literal
|
||||
|
||||
# used by Matcher
|
||||
RECEIVE_KEY: Literal["_receive_{id}"] = "_receive_{id}"
|
||||
|
@@ -6,15 +6,31 @@ FrontMatter:
|
||||
"""
|
||||
|
||||
import abc
|
||||
import asyncio
|
||||
import inspect
|
||||
from typing import Any, Dict, List, Type, Generic, TypeVar, Callable, Optional
|
||||
from dataclasses import field, dataclass
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Generic,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Iterable,
|
||||
Optional,
|
||||
Awaitable,
|
||||
cast,
|
||||
)
|
||||
|
||||
from pydantic import BaseConfig
|
||||
from pydantic.schema import get_annotation_from_field_info
|
||||
from pydantic.fields import Required, FieldInfo, Undefined, ModelField
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.exception import TypeMisMatch
|
||||
from nonebot.typing import _DependentCallable
|
||||
from nonebot.exception import SkippedException
|
||||
from nonebot.utils import run_sync, is_coroutine_callable
|
||||
|
||||
from .utils import check_field_type, get_typed_signature
|
||||
@@ -31,25 +47,29 @@ class Param(abc.ABC, FieldInfo):
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: "Dependent", name: str, param: inspect.Parameter
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type["Param"], ...]
|
||||
) -> Optional["Param"]:
|
||||
return None
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def _check_parameterless(
|
||||
cls, dependent: "Dependent", value: Any
|
||||
cls, value: Any, allow_types: Tuple[Type["Param"], ...]
|
||||
) -> Optional["Param"]:
|
||||
return None
|
||||
return
|
||||
|
||||
@abc.abstractmethod
|
||||
async def _solve(self, **kwargs: Any) -> Any:
|
||||
raise NotImplementedError
|
||||
|
||||
async def _check(self, **kwargs: Any) -> None:
|
||||
return
|
||||
|
||||
|
||||
class CustomConfig(BaseConfig):
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Dependent(Generic[R]):
|
||||
"""依赖注入容器
|
||||
|
||||
@@ -61,101 +81,70 @@ class Dependent(Generic[R]):
|
||||
allow_types: 允许的参数类型
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
call: Callable[..., Any],
|
||||
pre_checkers: Optional[List[Param]] = None,
|
||||
params: Optional[List[ModelField]] = None,
|
||||
parameterless: Optional[List[Param]] = None,
|
||||
allow_types: Optional[List[Type[Param]]] = None,
|
||||
) -> None:
|
||||
self.call = call
|
||||
self.pre_checkers = pre_checkers or []
|
||||
self.params = params or []
|
||||
self.parameterless = parameterless or []
|
||||
self.allow_types = allow_types or []
|
||||
call: _DependentCallable[R]
|
||||
params: Tuple[ModelField] = field(default_factory=tuple)
|
||||
parameterless: Tuple[Param] = field(default_factory=tuple)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if inspect.isfunction(self.call) or inspect.isclass(self.call):
|
||||
call_str = self.call.__name__
|
||||
else:
|
||||
call_str = repr(self.call)
|
||||
return (
|
||||
f"<Dependent call={self.call}, params={self.params},"
|
||||
f" parameterless={self.parameterless}>"
|
||||
f"Dependent(call={call_str}"
|
||||
+ (f", parameterless={self.parameterless}" if self.parameterless else "")
|
||||
+ ")"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
async def __call__(self, **kwargs: Any) -> R:
|
||||
# do pre-check
|
||||
await self.check(**kwargs)
|
||||
|
||||
# solve param values
|
||||
values = await self.solve(**kwargs)
|
||||
|
||||
# call function
|
||||
if is_coroutine_callable(self.call):
|
||||
return await self.call(**values)
|
||||
return await cast(Callable[..., Awaitable[R]], self.call)(**values)
|
||||
else:
|
||||
return await run_sync(self.call)(**values)
|
||||
return await run_sync(cast(Callable[..., R], self.call))(**values)
|
||||
|
||||
def parse_param(self, name: str, param: inspect.Parameter) -> Param:
|
||||
for allow_type in self.allow_types:
|
||||
field_info = allow_type._check_param(self, name, param)
|
||||
if field_info:
|
||||
return field_info
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unknown parameter {name} for function {self.call} with type {param.annotation}"
|
||||
)
|
||||
@staticmethod
|
||||
def parse_params(
|
||||
call: _DependentCallable[R], allow_types: Tuple[Type[Param], ...]
|
||||
) -> Tuple[ModelField]:
|
||||
fields: List[ModelField] = []
|
||||
params = get_typed_signature(call).parameters.values()
|
||||
|
||||
def parse_parameterless(self, value: Any) -> Param:
|
||||
for allow_type in self.allow_types:
|
||||
field_info = allow_type._check_parameterless(self, value)
|
||||
if field_info:
|
||||
return field_info
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unknown parameterless {value} for function {self.call} with type {type(value)}"
|
||||
)
|
||||
|
||||
def prepend_parameterless(self, value: Any) -> None:
|
||||
self.parameterless.insert(0, self.parse_parameterless(value))
|
||||
|
||||
def append_parameterless(self, value: Any) -> None:
|
||||
self.parameterless.append(self.parse_parameterless(value))
|
||||
|
||||
@classmethod
|
||||
def parse(
|
||||
cls: Type[T],
|
||||
*,
|
||||
call: Callable[..., Any],
|
||||
parameterless: Optional[List[Any]] = None,
|
||||
allow_types: Optional[List[Type[Param]]] = None,
|
||||
) -> T:
|
||||
signature = get_typed_signature(call)
|
||||
params = signature.parameters
|
||||
dependent = cls(
|
||||
call=call,
|
||||
allow_types=allow_types,
|
||||
)
|
||||
|
||||
for param_name, param in params.items():
|
||||
for param in params:
|
||||
default_value = Required
|
||||
if param.default != param.empty:
|
||||
default_value = param.default
|
||||
|
||||
if isinstance(default_value, Param):
|
||||
field_info = default_value
|
||||
default_value = field_info.default
|
||||
else:
|
||||
field_info = dependent.parse_param(param_name, param)
|
||||
default_value = field_info.default
|
||||
for allow_type in allow_types:
|
||||
if field_info := allow_type._check_param(param, allow_types):
|
||||
break
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unknown parameter {param.name} for function {call} with type {param.annotation}"
|
||||
)
|
||||
|
||||
default_value = field_info.default
|
||||
|
||||
annotation: Any = Any
|
||||
required = default_value == Required
|
||||
if param.annotation != param.empty:
|
||||
annotation = param.annotation
|
||||
annotation = get_annotation_from_field_info(
|
||||
annotation, field_info, param_name
|
||||
annotation, field_info, param.name
|
||||
)
|
||||
dependent.params.append(
|
||||
|
||||
fields.append(
|
||||
ModelField(
|
||||
name=param_name,
|
||||
name=param.name,
|
||||
type_=annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
@@ -165,49 +154,72 @@ class Dependent(Generic[R]):
|
||||
)
|
||||
)
|
||||
|
||||
parameterless_params = [
|
||||
dependent.parse_parameterless(param) for param in (parameterless or [])
|
||||
]
|
||||
dependent.parameterless.extend(parameterless_params)
|
||||
return tuple(fields)
|
||||
|
||||
logger.trace(
|
||||
f"Parsed dependent with call={call}, "
|
||||
f"params={[param.field_info for param in dependent.params]}, "
|
||||
f"parameterless={dependent.parameterless}"
|
||||
@staticmethod
|
||||
def parse_parameterless(
|
||||
parameterless: Tuple[Any, ...], allow_types: Tuple[Type[Param], ...]
|
||||
) -> Tuple[Param, ...]:
|
||||
parameterless_params: List[Param] = []
|
||||
for value in parameterless:
|
||||
for allow_type in allow_types:
|
||||
if param := allow_type._check_parameterless(value, allow_types):
|
||||
break
|
||||
else:
|
||||
raise ValueError(f"Unknown parameterless {value}")
|
||||
parameterless_params.append(param)
|
||||
return tuple(parameterless_params)
|
||||
|
||||
@classmethod
|
||||
def parse(
|
||||
cls,
|
||||
*,
|
||||
call: _DependentCallable[R],
|
||||
parameterless: Optional[Iterable[Any]] = None,
|
||||
allow_types: Iterable[Type[Param]],
|
||||
) -> "Dependent[R]":
|
||||
allow_types = tuple(allow_types)
|
||||
|
||||
params = cls.parse_params(call, allow_types)
|
||||
parameterless_params = (
|
||||
tuple()
|
||||
if parameterless is None
|
||||
else cls.parse_parameterless(tuple(parameterless), allow_types)
|
||||
)
|
||||
|
||||
return dependent
|
||||
return cls(call, params, parameterless_params)
|
||||
|
||||
async def solve(
|
||||
self,
|
||||
**params: Any,
|
||||
) -> Dict[str, Any]:
|
||||
values: Dict[str, Any] = {}
|
||||
async def check(self, **params: Any) -> None:
|
||||
try:
|
||||
await asyncio.gather(
|
||||
*(param._check(**params) for param in self.parameterless)
|
||||
)
|
||||
await asyncio.gather(
|
||||
*(
|
||||
cast(Param, param.field_info)._check(**params)
|
||||
for param in self.params
|
||||
)
|
||||
)
|
||||
except SkippedException as e:
|
||||
logger.trace(f"{self} skipped due to {e}")
|
||||
raise
|
||||
|
||||
for checker in self.pre_checkers:
|
||||
await checker._solve(**params)
|
||||
async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
|
||||
value = await cast(Param, field.field_info)._solve(**params)
|
||||
if value is Undefined:
|
||||
value = field.get_default()
|
||||
return check_field_type(field, value)
|
||||
|
||||
async def solve(self, **params: Any) -> Dict[str, Any]:
|
||||
# solve parameterless
|
||||
for param in self.parameterless:
|
||||
await param._solve(**params)
|
||||
|
||||
for field in self.params:
|
||||
field_info = field.field_info
|
||||
assert isinstance(field_info, Param), "Params must be subclasses of Param"
|
||||
value = await field_info._solve(**params)
|
||||
if value is Undefined:
|
||||
value = field.get_default()
|
||||
|
||||
try:
|
||||
values[field.name] = check_field_type(field, value)
|
||||
except TypeMisMatch:
|
||||
logger.debug(
|
||||
f"{field_info} "
|
||||
f"type {type(value)} not match depends {self.call} "
|
||||
f"annotation {field._type_display()}, ignored"
|
||||
)
|
||||
raise
|
||||
|
||||
return values
|
||||
# solve param values
|
||||
values = await asyncio.gather(
|
||||
*(self._solve_field(field, params) for field in self.params)
|
||||
)
|
||||
return {field.name: value for field, value in zip(self.params, values)}
|
||||
|
||||
|
||||
__autodoc__ = {"CustomConfig": False}
|
||||
|
@@ -28,8 +28,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
||||
)
|
||||
for param in signature.parameters.values()
|
||||
]
|
||||
typed_signature = inspect.Signature(typed_params)
|
||||
return typed_signature
|
||||
return inspect.Signature(typed_params)
|
||||
|
||||
|
||||
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import signal
|
||||
import asyncio
|
||||
import threading
|
||||
from typing import Set, Union, Callable, Awaitable
|
||||
from typing import Set, Union, Callable, Awaitable, cast
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.drivers import Driver
|
||||
@@ -9,8 +9,7 @@ from nonebot.typing import overrides
|
||||
from nonebot.config import Env, Config
|
||||
from nonebot.utils import run_sync, is_coroutine_callable
|
||||
|
||||
STARTUP_FUNC = Callable[[], Union[None, Awaitable[None]]]
|
||||
SHUTDOWN_FUNC = Callable[[], Union[None, Awaitable[None]]]
|
||||
HOOK_FUNC = Union[Callable[[], None], Callable[[], Awaitable[None]]]
|
||||
HANDLED_SIGNALS = (
|
||||
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
|
||||
signal.SIGTERM, # Unix signal 15. Sent by `kill <pid>`.
|
||||
@@ -20,8 +19,8 @@ HANDLED_SIGNALS = (
|
||||
class BlockDriver(Driver):
|
||||
def __init__(self, env: Env, config: Config):
|
||||
super().__init__(env, config)
|
||||
self.startup_funcs: Set[STARTUP_FUNC] = set()
|
||||
self.shutdown_funcs: Set[SHUTDOWN_FUNC] = set()
|
||||
self.startup_funcs: Set[HOOK_FUNC] = set()
|
||||
self.shutdown_funcs: Set[HOOK_FUNC] = set()
|
||||
self.should_exit: asyncio.Event = asyncio.Event()
|
||||
self.force_exit: bool = False
|
||||
|
||||
@@ -38,7 +37,7 @@ class BlockDriver(Driver):
|
||||
return logger
|
||||
|
||||
@overrides(Driver)
|
||||
def on_startup(self, func: STARTUP_FUNC) -> STARTUP_FUNC:
|
||||
def on_startup(self, func: HOOK_FUNC) -> HOOK_FUNC:
|
||||
"""
|
||||
注册一个启动时执行的函数
|
||||
"""
|
||||
@@ -46,7 +45,7 @@ class BlockDriver(Driver):
|
||||
return func
|
||||
|
||||
@overrides(Driver)
|
||||
def on_shutdown(self, func: SHUTDOWN_FUNC) -> SHUTDOWN_FUNC:
|
||||
def on_shutdown(self, func: HOOK_FUNC) -> HOOK_FUNC:
|
||||
"""
|
||||
注册一个停止时执行的函数
|
||||
"""
|
||||
@@ -71,7 +70,9 @@ class BlockDriver(Driver):
|
||||
async def startup(self):
|
||||
# run startup
|
||||
cors = [
|
||||
startup() if is_coroutine_callable(startup) else run_sync(startup)()
|
||||
cast(Callable[..., Awaitable[None]], startup)()
|
||||
if is_coroutine_callable(startup)
|
||||
else run_sync(startup)()
|
||||
for startup in self.startup_funcs
|
||||
]
|
||||
if cors:
|
||||
@@ -94,7 +95,9 @@ class BlockDriver(Driver):
|
||||
logger.info("Waiting for application shutdown.")
|
||||
# run shutdown
|
||||
cors = [
|
||||
shutdown() if is_coroutine_callable(shutdown) else run_sync(shutdown)()
|
||||
cast(Callable[..., Awaitable[None]], shutdown)()
|
||||
if is_coroutine_callable(shutdown)
|
||||
else run_sync(shutdown)()
|
||||
for shutdown in self.shutdown_funcs
|
||||
]
|
||||
if cors:
|
||||
|
@@ -27,7 +27,7 @@ from nonebot.drivers import HTTPVersion, ForwardMixin, ForwardDriver, combine_dr
|
||||
|
||||
try:
|
||||
import aiohttp
|
||||
except ImportError:
|
||||
except ImportError: # pragma: no cover
|
||||
raise ImportError(
|
||||
"Please install aiohttp first to use this driver. `pip install nonebot2[aiohttp]`"
|
||||
) from None
|
||||
|
@@ -9,7 +9,9 @@ FrontMatter:
|
||||
description: nonebot.drivers.fastapi 模块
|
||||
"""
|
||||
|
||||
|
||||
import logging
|
||||
import contextlib
|
||||
from functools import wraps
|
||||
from typing import Any, List, Tuple, Union, Callable, Optional
|
||||
|
||||
@@ -186,14 +188,12 @@ class Driver(ReverseDriver):
|
||||
setup: HTTPServerSetup,
|
||||
) -> Response:
|
||||
json: Any = None
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
json = await request.json()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
data: Optional[dict] = None
|
||||
files: Optional[List[Tuple[str, FileTypes]]] = None
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
form = await request.form()
|
||||
data = {}
|
||||
files = []
|
||||
@@ -204,8 +204,7 @@ class Driver(ReverseDriver):
|
||||
)
|
||||
else:
|
||||
data[key] = value
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
http_request = BaseRequest(
|
||||
request.method,
|
||||
str(request.url),
|
||||
@@ -219,7 +218,9 @@ class Driver(ReverseDriver):
|
||||
)
|
||||
|
||||
response = await setup.handle_func(http_request)
|
||||
return Response(response.content, response.status_code, dict(response.headers))
|
||||
return Response(
|
||||
response.content, response.status_code, dict(response.headers.items())
|
||||
)
|
||||
|
||||
async def _handle_ws(self, websocket: WebSocket, setup: WebSocketServerSetup):
|
||||
request = BaseRequest(
|
||||
|
@@ -31,7 +31,7 @@ from nonebot.drivers import (
|
||||
|
||||
try:
|
||||
import httpx
|
||||
except ImportError:
|
||||
except ImportError: # pragma: no cover
|
||||
raise ImportError(
|
||||
"Please install httpx by using `pip install nonebot2[httpx]`"
|
||||
) from None
|
||||
|
@@ -37,7 +37,7 @@ try:
|
||||
from quart import Quart, Request, Response
|
||||
from quart.datastructures import FileStorage
|
||||
from quart import Websocket as QuartWebSocket
|
||||
except ImportError:
|
||||
except ImportError: # pragma: no cover
|
||||
raise ImportError(
|
||||
"Please install Quart by using `pip install nonebot2[quart]`"
|
||||
) from None
|
||||
|
@@ -30,10 +30,10 @@ from nonebot.drivers import ForwardMixin, ForwardDriver, combine_driver
|
||||
try:
|
||||
from websockets.exceptions import ConnectionClosed
|
||||
from websockets.legacy.client import Connect, WebSocketClientProtocol
|
||||
except ImportError:
|
||||
except ImportError: # pragma: no cover
|
||||
raise ImportError(
|
||||
"Please install websockets by using `pip install nonebot2[websockets]`"
|
||||
)
|
||||
) from None
|
||||
|
||||
logger = logging.Logger("websockets.client", "INFO")
|
||||
logger.addHandler(LoguruHandler())
|
||||
|
@@ -46,10 +46,14 @@ class ParserExit(NoneBotException):
|
||||
self.status = status
|
||||
self.message = message
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ParserExit status={self.status} message={self.message}>"
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"ParserExit(status={self.status}"
|
||||
+ (f", message={self.message!r}" if self.message else "")
|
||||
+ ")"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
@@ -68,10 +72,10 @@ class IgnoredException(ProcessException):
|
||||
def __init__(self, reason: Any):
|
||||
self.reason: Any = reason
|
||||
|
||||
def __repr__(self):
|
||||
return f"<IgnoredException, reason={self.reason}>"
|
||||
def __repr__(self) -> str:
|
||||
return f"IgnoredException(reason={self.reason!r})"
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
@@ -99,11 +103,14 @@ class TypeMisMatch(SkippedException):
|
||||
self.param: ModelField = param
|
||||
self.value: Any = value
|
||||
|
||||
def __repr__(self):
|
||||
return f"<TypeMisMatch, param={self.param}, value={self.value}>"
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"TypeMisMatch(param={self.param.name}, "
|
||||
f"type={self.param._type_display()}, value={self.value!r}>"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
self.__repr__()
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class MockApiException(ProcessException):
|
||||
@@ -116,10 +123,10 @@ class MockApiException(ProcessException):
|
||||
def __init__(self, result: Any):
|
||||
self.result = result
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ApiCancelledException, result={self.result}>"
|
||||
def __repr__(self) -> str:
|
||||
return f"MockApiException(result={self.result!r})"
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
@@ -195,7 +202,8 @@ class AdapterException(NoneBotException):
|
||||
adapter_name: 标识 adapter
|
||||
"""
|
||||
|
||||
def __init__(self, adapter_name: str) -> None:
|
||||
def __init__(self, adapter_name: str, *args: object) -> None:
|
||||
super().__init__(*args)
|
||||
self.adapter_name: str = adapter_name
|
||||
|
||||
|
||||
@@ -231,4 +239,11 @@ class WebSocketClosed(DriverException):
|
||||
self.reason = reason
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<WebSocketClosed code={self.code} reason={self.reason}>"
|
||||
return (
|
||||
f"WebSocketClosed(code={self.code}"
|
||||
+ (f", reason={self.reason!r}" if self.reason else "")
|
||||
+ ")"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
@@ -33,6 +33,9 @@ class Adapter(abc.ABC):
|
||||
self.bots: Dict[str, Bot] = {}
|
||||
"""本协议适配器已建立连接的 {ref}`nonebot.adapters.Bot` 实例"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Adapter(name={self.get_name()!r})"
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_name(cls) -> str:
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import abc
|
||||
import asyncio
|
||||
from functools import partial
|
||||
from typing_extensions import Protocol
|
||||
from typing import TYPE_CHECKING, Any, Set, Union, Optional
|
||||
from typing import TYPE_CHECKING, Any, Set, Union, Optional, Protocol
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.config import Config
|
||||
@@ -14,10 +13,9 @@ if TYPE_CHECKING:
|
||||
from .adapter import Adapter
|
||||
from .message import Message, MessageSegment
|
||||
|
||||
|
||||
class _ApiCall(Protocol):
|
||||
async def __call__(self, **kwargs: Any) -> Any:
|
||||
...
|
||||
class _ApiCall(Protocol):
|
||||
async def __call__(self, **kwargs: Any) -> Any:
|
||||
...
|
||||
|
||||
|
||||
class Bot(abc.ABC):
|
||||
@@ -41,7 +39,10 @@ class Bot(abc.ABC):
|
||||
self.self_id: str = self_id
|
||||
"""机器人 ID"""
|
||||
|
||||
def __getattr__(self, name: str) -> _ApiCall:
|
||||
def __repr__(self) -> str:
|
||||
return f"Bot(type={self.type!r}, self_id={self.self_id!r})"
|
||||
|
||||
def __getattr__(self, name: str) -> "_ApiCall":
|
||||
return partial(self.call_api, name)
|
||||
|
||||
@property
|
||||
@@ -72,8 +73,7 @@ class Bot(abc.ABC):
|
||||
skip_calling_api: bool = False
|
||||
exception: Optional[Exception] = None
|
||||
|
||||
coros = list(map(lambda x: x(self, api, data), self._calling_api_hook))
|
||||
if coros:
|
||||
if coros := [hook(self, api, data) for hook in self._calling_api_hook]:
|
||||
try:
|
||||
logger.debug("Running CallingAPI hooks...")
|
||||
await asyncio.gather(*coros)
|
||||
@@ -95,10 +95,9 @@ class Bot(abc.ABC):
|
||||
except Exception as e:
|
||||
exception = e
|
||||
|
||||
coros = list(
|
||||
map(lambda x: x(self, exception, api, data, result), self._called_api_hook)
|
||||
)
|
||||
if coros:
|
||||
if coros := [
|
||||
hook(self, exception, api, data, result) for hook in self._called_api_hook
|
||||
]:
|
||||
try:
|
||||
logger.debug("Running CalledAPI hooks...")
|
||||
await asyncio.gather(*coros)
|
||||
|
@@ -186,7 +186,7 @@ class Message(List[TMS], abc.ABC):
|
||||
elif isinstance(other, Iterable):
|
||||
self.extend(other)
|
||||
else:
|
||||
raise ValueError(f"Unsupported type: {type(other)}") # pragma: no cover
|
||||
raise TypeError(f"Unsupported type {type(other)!r}")
|
||||
return self
|
||||
|
||||
@overload
|
||||
|
@@ -56,6 +56,9 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
self.factory: Type[TF] = factory
|
||||
self.format_specs: Dict[str, FormatSpecFunc] = {}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"MessageTemplate({self.template!r}, factory={self.factory!r})"
|
||||
|
||||
def add_format_spec(
|
||||
self, spec: FormatSpecFunc_T, name: Optional[str] = None
|
||||
) -> FormatSpecFunc_T:
|
||||
|
@@ -40,12 +40,18 @@ class Driver(abc.ABC):
|
||||
"""环境名称"""
|
||||
self.config: Config = config
|
||||
"""全局配置对象"""
|
||||
self._clients: Dict[str, "Bot"] = {}
|
||||
self._bots: Dict[str, "Bot"] = {}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"Driver(type={self.type!r}, "
|
||||
f"adapters={len(self._adapters)}, bots={len(self._bots)})"
|
||||
)
|
||||
|
||||
@property
|
||||
def bots(self) -> Dict[str, "Bot"]:
|
||||
"""获取当前所有已连接的 Bot"""
|
||||
return self._clients
|
||||
return self._bots
|
||||
|
||||
def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None:
|
||||
"""注册一个协议适配器
|
||||
@@ -124,9 +130,9 @@ class Driver(abc.ABC):
|
||||
|
||||
def _bot_connect(self, bot: "Bot") -> None:
|
||||
"""在连接成功后,调用该函数来注册 bot 对象"""
|
||||
if bot.self_id in self._clients:
|
||||
if bot.self_id in self._bots:
|
||||
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
|
||||
self._clients[bot.self_id] = bot
|
||||
self._bots[bot.self_id] = bot
|
||||
|
||||
async def _run_hook(bot: "Bot") -> None:
|
||||
coros = list(
|
||||
@@ -148,8 +154,8 @@ class Driver(abc.ABC):
|
||||
|
||||
def _bot_disconnect(self, bot: "Bot") -> None:
|
||||
"""在连接断开后,调用该函数来注销 bot 对象"""
|
||||
if bot.self_id in self._clients:
|
||||
del self._clients[bot.self_id]
|
||||
if bot.self_id in self._bots:
|
||||
del self._bots[bot.self_id]
|
||||
|
||||
async def _run_hook(bot: "Bot") -> None:
|
||||
coros = list(
|
||||
@@ -233,13 +239,11 @@ def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Dr
|
||||
if not mixins:
|
||||
return driver
|
||||
|
||||
class CombinedDriver(*mixins, driver, ForwardDriver): # type: ignore
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return (
|
||||
driver.type.__get__(self)
|
||||
+ "+"
|
||||
+ "+".join(map(lambda x: x.type.__get__(self), mixins))
|
||||
)
|
||||
def type_(self: ForwardDriver) -> str:
|
||||
return (
|
||||
driver.type.__get__(self)
|
||||
+ "+"
|
||||
+ "+".join(map(lambda x: x.type.__get__(self), mixins))
|
||||
)
|
||||
|
||||
return CombinedDriver
|
||||
return type("CombinedDriver", (*mixins, driver, ForwardDriver), {"type": property(type_)}) # type: ignore
|
||||
|
@@ -131,9 +131,7 @@ class Request:
|
||||
self.files.append((name, file_info)) # type: ignore
|
||||
|
||||
def __repr__(self) -> str:
|
||||
class_name = self.__class__.__name__
|
||||
url = str(self.url)
|
||||
return f"<{class_name}({self.method!r}, {url!r})>"
|
||||
return f"{self.__class__.__name__}(method={self.method!r}, url='{self.url!s}')"
|
||||
|
||||
|
||||
class Response:
|
||||
@@ -161,12 +159,18 @@ class Response:
|
||||
# request
|
||||
self.request: Optional[Request] = request
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(status_code={self.status_code!r})"
|
||||
|
||||
|
||||
class WebSocket(abc.ABC):
|
||||
def __init__(self, *, request: Request):
|
||||
# request
|
||||
self.request: Request = request
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}('{self.request.url!s}')"
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def closed(self) -> bool:
|
||||
@@ -320,17 +324,14 @@ class Cookies(MutableMapping):
|
||||
return len(self.jar)
|
||||
|
||||
def __iter__(self) -> Iterator[Cookie]:
|
||||
return (cookie for cookie in self.jar)
|
||||
return iter(self.jar)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
cookies_repr = ", ".join(
|
||||
[
|
||||
f"<Cookie {cookie.name}={cookie.value} for {cookie.domain} />"
|
||||
for cookie in self.jar
|
||||
]
|
||||
f"Cookie({cookie.name}={cookie.value} for {cookie.domain})"
|
||||
for cookie in self.jar
|
||||
)
|
||||
|
||||
return f"<Cookies [{cookies_repr}]>"
|
||||
return f"{self.__class__.__name__}({cookies_repr})"
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@@ -12,8 +12,10 @@ from typing import (
|
||||
Union,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Iterable,
|
||||
NoReturn,
|
||||
Optional,
|
||||
overload,
|
||||
)
|
||||
|
||||
from nonebot.log import logger
|
||||
@@ -34,7 +36,6 @@ from nonebot.typing import (
|
||||
T_PermissionUpdater,
|
||||
)
|
||||
from nonebot.exception import (
|
||||
TypeMisMatch,
|
||||
PausedException,
|
||||
StopPropagation,
|
||||
SkippedException,
|
||||
@@ -43,7 +44,7 @@ from nonebot.exception import (
|
||||
)
|
||||
|
||||
from .rule import Rule
|
||||
from .permission import USER, Permission
|
||||
from .permission import USER, User, Permission
|
||||
from .adapter import Bot, Event, Message, MessageSegment, MessageTemplate
|
||||
from .params import (
|
||||
Depends,
|
||||
@@ -71,29 +72,16 @@ current_handler: ContextVar[Dependent] = ContextVar("current_handler")
|
||||
|
||||
class MatcherMeta(type):
|
||||
if TYPE_CHECKING:
|
||||
module: Optional[str]
|
||||
plugin_name: Optional[str]
|
||||
module_name: Optional[str]
|
||||
module_prefix: Optional[str]
|
||||
type: str
|
||||
rule: Rule
|
||||
permission: Permission
|
||||
handlers: List[T_Handler]
|
||||
priority: int
|
||||
block: bool
|
||||
temp: bool
|
||||
expire_time: Optional[datetime]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<Matcher from {self.module_name or 'unknown'}, "
|
||||
f"type={self.type}, priority={self.priority}, "
|
||||
f"temp={self.temp}>"
|
||||
f"Matcher(type={self.type!r}"
|
||||
+ (f", module={self.module_name}" if self.module_name else "")
|
||||
+ ")"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return repr(self)
|
||||
|
||||
|
||||
class Matcher(metaclass=MatcherMeta):
|
||||
"""事件响应器类"""
|
||||
@@ -132,7 +120,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
_default_permission_updater: Optional[Dependent[Permission]] = None
|
||||
"""事件响应器权限更新函数"""
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
HANDLER_PARAM_TYPES = (
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
@@ -140,7 +128,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
ArgParam,
|
||||
MatcherParam,
|
||||
DefaultParam,
|
||||
]
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.handlers = self.handlers.copy()
|
||||
@@ -148,13 +136,11 @@ class Matcher(metaclass=MatcherMeta):
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<Matcher from {self.module_name or 'unknown'}, type={self.type}, "
|
||||
f"priority={self.priority}, temp={self.temp}>"
|
||||
f"Matcher(type={self.type!r}"
|
||||
+ (f", module={self.module_name}" if self.module_name else "")
|
||||
+ ")"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return repr(self)
|
||||
|
||||
@classmethod
|
||||
def new(
|
||||
cls,
|
||||
@@ -218,27 +204,35 @@ class Matcher(metaclass=MatcherMeta):
|
||||
"temp": temp,
|
||||
"expire_time": (
|
||||
expire_time
|
||||
if isinstance(expire_time, datetime)
|
||||
else expire_time and datetime.now() + expire_time
|
||||
and (
|
||||
expire_time
|
||||
if isinstance(expire_time, datetime)
|
||||
else datetime.now() + expire_time
|
||||
)
|
||||
),
|
||||
"priority": priority,
|
||||
"block": block,
|
||||
"_default_state": default_state or {},
|
||||
"_default_type_updater": (
|
||||
default_type_updater
|
||||
if isinstance(default_type_updater, Dependent)
|
||||
else default_type_updater
|
||||
and Dependent[str].parse(
|
||||
call=default_type_updater, allow_types=cls.HANDLER_PARAM_TYPES
|
||||
and (
|
||||
default_type_updater
|
||||
if isinstance(default_type_updater, Dependent)
|
||||
else Dependent[str].parse(
|
||||
call=default_type_updater,
|
||||
allow_types=cls.HANDLER_PARAM_TYPES,
|
||||
)
|
||||
)
|
||||
),
|
||||
"_default_permission_updater": (
|
||||
default_permission_updater
|
||||
if isinstance(default_permission_updater, Dependent)
|
||||
else default_permission_updater
|
||||
and Dependent[Permission].parse(
|
||||
call=default_permission_updater,
|
||||
allow_types=cls.HANDLER_PARAM_TYPES,
|
||||
and (
|
||||
default_permission_updater
|
||||
if isinstance(default_permission_updater, Dependent)
|
||||
else Dependent[Permission].parse(
|
||||
call=default_permission_updater,
|
||||
allow_types=cls.HANDLER_PARAM_TYPES,
|
||||
)
|
||||
)
|
||||
),
|
||||
},
|
||||
@@ -326,7 +320,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
|
||||
@classmethod
|
||||
def append_handler(
|
||||
cls, handler: T_Handler, parameterless: Optional[List[Any]] = None
|
||||
cls, handler: T_Handler, parameterless: Optional[Iterable[Any]] = None
|
||||
) -> Dependent[Any]:
|
||||
handler_ = Dependent[Any].parse(
|
||||
call=handler,
|
||||
@@ -338,7 +332,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
|
||||
@classmethod
|
||||
def handle(
|
||||
cls, parameterless: Optional[List[Any]] = None
|
||||
cls, parameterless: Optional[Iterable[Any]] = None
|
||||
) -> Callable[[T_Handler], T_Handler]:
|
||||
"""装饰一个函数来向事件响应器直接添加一个处理函数
|
||||
|
||||
@@ -354,7 +348,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
|
||||
@classmethod
|
||||
def receive(
|
||||
cls, id: str = "", parameterless: Optional[List[Any]] = None
|
||||
cls, id: str = "", parameterless: Optional[Iterable[Any]] = None
|
||||
) -> Callable[[T_Handler], T_Handler]:
|
||||
"""装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
|
||||
|
||||
@@ -372,14 +366,21 @@ class Matcher(metaclass=MatcherMeta):
|
||||
return
|
||||
await matcher.reject()
|
||||
|
||||
_parameterless = [Depends(_receive), *(parameterless or [])]
|
||||
_parameterless = (Depends(_receive), *(parameterless or tuple()))
|
||||
|
||||
def _decorator(func: T_Handler) -> T_Handler:
|
||||
|
||||
if cls.handlers and cls.handlers[-1].call is func:
|
||||
func_handler = cls.handlers[-1]
|
||||
for depend in reversed(_parameterless):
|
||||
func_handler.prepend_parameterless(depend)
|
||||
new_handler = Dependent(
|
||||
call=func_handler.call,
|
||||
params=func_handler.params,
|
||||
parameterless=Dependent.parse_parameterless(
|
||||
tuple(_parameterless), cls.HANDLER_PARAM_TYPES
|
||||
)
|
||||
+ func_handler.parameterless,
|
||||
)
|
||||
cls.handlers[-1] = new_handler
|
||||
else:
|
||||
cls.append_handler(func, parameterless=_parameterless)
|
||||
|
||||
@@ -392,7 +393,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
cls,
|
||||
key: str,
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
parameterless: Optional[List[Any]] = None,
|
||||
parameterless: Optional[Iterable[Any]] = None,
|
||||
) -> Callable[[T_Handler], T_Handler]:
|
||||
"""装饰一个函数来指示 NoneBot 获取一个参数 `key`
|
||||
|
||||
@@ -413,17 +414,21 @@ class Matcher(metaclass=MatcherMeta):
|
||||
return
|
||||
await matcher.reject(prompt)
|
||||
|
||||
_parameterless = [
|
||||
Depends(_key_getter),
|
||||
*(parameterless or []),
|
||||
]
|
||||
_parameterless = (Depends(_key_getter), *(parameterless or tuple()))
|
||||
|
||||
def _decorator(func: T_Handler) -> T_Handler:
|
||||
|
||||
if cls.handlers and cls.handlers[-1].call is func:
|
||||
func_handler = cls.handlers[-1]
|
||||
for depend in reversed(_parameterless):
|
||||
func_handler.prepend_parameterless(depend)
|
||||
new_handler = Dependent(
|
||||
call=func_handler.call,
|
||||
params=func_handler.params,
|
||||
parameterless=Dependent.parse_parameterless(
|
||||
tuple(_parameterless), cls.HANDLER_PARAM_TYPES
|
||||
)
|
||||
+ func_handler.parameterless,
|
||||
)
|
||||
cls.handlers[-1] = new_handler
|
||||
else:
|
||||
cls.append_handler(func, parameterless=_parameterless)
|
||||
|
||||
@@ -551,7 +556,17 @@ class Matcher(metaclass=MatcherMeta):
|
||||
"""
|
||||
raise SkippedException
|
||||
|
||||
def get_receive(self, id: str, default: T = None) -> Union[Event, T]:
|
||||
@overload
|
||||
def get_receive(self, id: str) -> Union[Event, None]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def get_receive(self, id: str, default: T) -> Union[Event, T]:
|
||||
...
|
||||
|
||||
def get_receive(
|
||||
self, id: str, default: Optional[T] = None
|
||||
) -> Optional[Union[Event, T]]:
|
||||
"""获取一个 `receive` 事件
|
||||
|
||||
如果没有找到对应的事件,返回 `default` 值
|
||||
@@ -563,14 +578,34 @@ class Matcher(metaclass=MatcherMeta):
|
||||
self.state[RECEIVE_KEY.format(id=id)] = event
|
||||
self.state[LAST_RECEIVE_KEY] = event
|
||||
|
||||
def get_last_receive(self, default: T = None) -> Union[Event, T]:
|
||||
@overload
|
||||
def get_last_receive(self) -> Union[Event, None]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def get_last_receive(self, default: T) -> Union[Event, T]:
|
||||
...
|
||||
|
||||
def get_last_receive(
|
||||
self, default: Optional[T] = None
|
||||
) -> Optional[Union[Event, T]]:
|
||||
"""获取最近一次 `receive` 事件
|
||||
|
||||
如果没有事件,返回 `default` 值
|
||||
"""
|
||||
return self.state.get(LAST_RECEIVE_KEY, default)
|
||||
|
||||
def get_arg(self, key: str, default: T = None) -> Union[Message, T]:
|
||||
@overload
|
||||
def get_arg(self, key: str) -> Union[Message, None]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def get_arg(self, key: str, default: T) -> Union[Message, T]:
|
||||
...
|
||||
|
||||
def get_arg(
|
||||
self, key: str, default: Optional[T] = None
|
||||
) -> Optional[Union[Message, T]]:
|
||||
"""获取一个 `got` 消息
|
||||
|
||||
如果没有找到对应的消息,返回 `default` 值
|
||||
@@ -587,7 +622,15 @@ class Matcher(metaclass=MatcherMeta):
|
||||
else:
|
||||
self.state[REJECT_TARGET] = target
|
||||
|
||||
def get_target(self, default: T = None) -> Union[str, T]:
|
||||
@overload
|
||||
def get_target(self) -> Union[str, None]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def get_target(self, default: T) -> Union[str, T]:
|
||||
...
|
||||
|
||||
def get_target(self, default: Optional[T] = None) -> Optional[Union[str, T]]:
|
||||
return self.state.get(REJECT_TARGET, default)
|
||||
|
||||
def stop_propagation(self):
|
||||
@@ -596,15 +639,21 @@ class Matcher(metaclass=MatcherMeta):
|
||||
|
||||
async def update_type(self, bot: Bot, event: Event) -> str:
|
||||
updater = self.__class__._default_type_updater
|
||||
if not updater:
|
||||
return "message"
|
||||
return await updater(bot=bot, event=event, state=self.state, matcher=self)
|
||||
return (
|
||||
await updater(bot=bot, event=event, state=self.state, matcher=self)
|
||||
if updater
|
||||
else "message"
|
||||
)
|
||||
|
||||
async def update_permission(self, bot: Bot, event: Event) -> Permission:
|
||||
updater = self.__class__._default_permission_updater
|
||||
if not updater:
|
||||
return USER(event.get_session_id(), perm=self.permission)
|
||||
return await updater(bot=bot, event=event, state=self.state, matcher=self)
|
||||
if updater := self.__class__._default_permission_updater:
|
||||
return await updater(bot=bot, event=event, state=self.state, matcher=self)
|
||||
permission = self.permission
|
||||
if len(permission.checkers) == 1 and isinstance(
|
||||
user_perm := tuple(permission.checkers)[0].call, User
|
||||
):
|
||||
permission = user_perm.perm
|
||||
return USER(event.get_session_id(), perm=permission)
|
||||
|
||||
async def resolve_reject(self):
|
||||
handler = current_handler.get()
|
||||
@@ -621,8 +670,8 @@ class Matcher(metaclass=MatcherMeta):
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
):
|
||||
logger.trace(
|
||||
f"Matcher {self} run with incoming args: "
|
||||
f"bot={bot}, event={event}, state={state}"
|
||||
f"{self} run with incoming args: "
|
||||
f"bot={bot}, event={event!r}, state={state!r}"
|
||||
)
|
||||
b_t = current_bot.set(bot)
|
||||
e_t = current_event.set(event)
|
||||
@@ -644,17 +693,12 @@ class Matcher(metaclass=MatcherMeta):
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
except TypeMisMatch as e:
|
||||
logger.debug(
|
||||
f"Handler {handler} param {e.param.name} value {e.value} "
|
||||
f"mismatch type {e.param._type_display()}, skipped"
|
||||
)
|
||||
except SkippedException as e:
|
||||
except SkippedException:
|
||||
logger.debug(f"Handler {handler} skipped")
|
||||
except StopPropagation:
|
||||
self.block = True
|
||||
finally:
|
||||
logger.info(f"Matcher {self} running complete")
|
||||
logger.info(f"{self} running complete")
|
||||
current_bot.reset(b_t)
|
||||
current_event.reset(e_t)
|
||||
current_matcher.reset(m_t)
|
||||
|
@@ -1,14 +1,10 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import warnings
|
||||
from typing_extensions import Literal
|
||||
from typing import TYPE_CHECKING, Any, Callable, Optional, cast
|
||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
||||
from typing import TYPE_CHECKING, Any, Type, Tuple, Literal, Callable, Optional, cast
|
||||
|
||||
from pydantic.fields import Required, Undefined, ModelField
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.exception import TypeMisMatch
|
||||
from nonebot.dependencies.utils import check_field_type
|
||||
from nonebot.dependencies import Param, Dependent, CustomConfig
|
||||
from nonebot.typing import T_State, T_Handler, T_DependencyCache
|
||||
@@ -40,7 +36,7 @@ class DependsInner:
|
||||
def __repr__(self) -> str:
|
||||
dep = get_name(self.dependency)
|
||||
cache = "" if self.use_cache else ", use_cache=False"
|
||||
return f"{self.__class__.__name__}({dep}{cache})"
|
||||
return f"DependsInner({dep}{cache})"
|
||||
|
||||
|
||||
def Depends(
|
||||
@@ -75,12 +71,12 @@ def Depends(
|
||||
class DependParam(Param):
|
||||
"""子依赖参数"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Depends({self.extra['dependent']})"
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls,
|
||||
dependent: Dependent,
|
||||
name: str,
|
||||
param: inspect.Parameter,
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["DependParam"]:
|
||||
if isinstance(param.default, DependsInner):
|
||||
dependency: T_Handler
|
||||
@@ -91,22 +87,20 @@ class DependParam(Param):
|
||||
dependency = param.default.dependency
|
||||
sub_dependent = Dependent[Any].parse(
|
||||
call=dependency,
|
||||
allow_types=dependent.allow_types,
|
||||
allow_types=allow_types,
|
||||
)
|
||||
dependent.pre_checkers.extend(sub_dependent.pre_checkers)
|
||||
sub_dependent.pre_checkers.clear()
|
||||
return cls(
|
||||
Required, use_cache=param.default.use_cache, dependent=sub_dependent
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _check_parameterless(
|
||||
cls, dependent: "Dependent", value: Any
|
||||
cls, value: Any, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["Param"]:
|
||||
if isinstance(value, DependsInner):
|
||||
assert value.dependency, "Dependency cannot be empty"
|
||||
dependent = Dependent[Any].parse(
|
||||
call=value.dependency, allow_types=dependent.allow_types
|
||||
call=value.dependency, allow_types=allow_types
|
||||
)
|
||||
return cls(Required, use_cache=value.use_cache, dependent=dependent)
|
||||
|
||||
@@ -120,8 +114,7 @@ class DependParam(Param):
|
||||
dependency_cache = {} if dependency_cache is None else dependency_cache
|
||||
|
||||
sub_dependent: Dependent = self.extra["dependent"]
|
||||
sub_dependent.call = cast(Callable[..., Any], sub_dependent.call)
|
||||
call = sub_dependent.call
|
||||
call = cast(Callable[..., Any], sub_dependent.call)
|
||||
|
||||
# solve sub dependency with current cache
|
||||
sub_values = await sub_dependent.solve(
|
||||
@@ -133,7 +126,7 @@ class DependParam(Param):
|
||||
# run dependency function
|
||||
task: asyncio.Task[Any]
|
||||
if use_cache and call in dependency_cache:
|
||||
solved = await dependency_cache[call]
|
||||
return await dependency_cache[call]
|
||||
elif is_gen_callable(call) or is_async_gen_callable(call):
|
||||
assert isinstance(
|
||||
stack, AsyncExitStack
|
||||
@@ -144,134 +137,124 @@ class DependParam(Param):
|
||||
cm = asynccontextmanager(call)(**sub_values)
|
||||
task = asyncio.create_task(stack.enter_async_context(cm))
|
||||
dependency_cache[call] = task
|
||||
solved = await task
|
||||
return await task
|
||||
elif is_coroutine_callable(call):
|
||||
task = asyncio.create_task(call(**sub_values))
|
||||
dependency_cache[call] = task
|
||||
solved = await task
|
||||
return await task
|
||||
else:
|
||||
task = asyncio.create_task(run_sync(call)(**sub_values))
|
||||
dependency_cache[call] = task
|
||||
solved = await task
|
||||
return await task
|
||||
|
||||
return solved
|
||||
|
||||
|
||||
class _BotChecker(Param):
|
||||
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
||||
field: ModelField = self.extra["field"]
|
||||
try:
|
||||
return check_field_type(field, bot)
|
||||
except TypeMisMatch:
|
||||
logger.debug(
|
||||
f"Bot type {type(bot)} not match "
|
||||
f"annotation {field._type_display()}, ignored"
|
||||
)
|
||||
raise
|
||||
async def _check(self, **kwargs: Any) -> None:
|
||||
# run sub dependent pre-checkers
|
||||
sub_dependent: Dependent = self.extra["dependent"]
|
||||
await sub_dependent.check(**kwargs)
|
||||
|
||||
|
||||
class BotParam(Param):
|
||||
"""{ref}`nonebot.adapters.Bot` 参数"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
"BotParam("
|
||||
+ (
|
||||
repr(cast(ModelField, checker).type_)
|
||||
if (checker := self.extra.get("checker"))
|
||||
else ""
|
||||
)
|
||||
+ ")"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["BotParam"]:
|
||||
from nonebot.adapters import Bot
|
||||
|
||||
if param.default == param.empty:
|
||||
if generic_check_issubclass(param.annotation, Bot):
|
||||
checker: Optional[ModelField] = None
|
||||
if param.annotation is not Bot:
|
||||
dependent.pre_checkers.append(
|
||||
_BotChecker(
|
||||
Required,
|
||||
field=ModelField(
|
||||
name=name,
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
),
|
||||
)
|
||||
checker = ModelField(
|
||||
name=param.name,
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
)
|
||||
return cls(Required)
|
||||
elif param.annotation == param.empty and name == "bot":
|
||||
return cls(Required, checker=checker)
|
||||
elif param.annotation == param.empty and param.name == "bot":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
||||
return bot
|
||||
|
||||
|
||||
class _EventChecker(Param):
|
||||
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
||||
field: ModelField = self.extra["field"]
|
||||
try:
|
||||
return check_field_type(field, event)
|
||||
except TypeMisMatch:
|
||||
logger.debug(
|
||||
f"Event type {type(event)} not match "
|
||||
f"annotation {field._type_display()}, ignored"
|
||||
)
|
||||
raise
|
||||
async def _check(self, bot: "Bot", **kwargs: Any) -> None:
|
||||
if checker := self.extra.get("checker"):
|
||||
check_field_type(checker, bot)
|
||||
|
||||
|
||||
class EventParam(Param):
|
||||
"""{ref}`nonebot.adapters.Event` 参数"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
"EventParam("
|
||||
+ (
|
||||
repr(cast(ModelField, checker).type_)
|
||||
if (checker := self.extra.get("checker"))
|
||||
else ""
|
||||
)
|
||||
+ ")"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["EventParam"]:
|
||||
from nonebot.adapters import Event
|
||||
|
||||
if param.default == param.empty:
|
||||
if generic_check_issubclass(param.annotation, Event):
|
||||
checker: Optional[ModelField] = None
|
||||
if param.annotation is not Event:
|
||||
dependent.pre_checkers.append(
|
||||
_EventChecker(
|
||||
Required,
|
||||
field=ModelField(
|
||||
name=name,
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
),
|
||||
)
|
||||
checker = ModelField(
|
||||
name=param.name,
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
)
|
||||
return cls(Required)
|
||||
elif param.annotation == param.empty and name == "event":
|
||||
return cls(Required, checker=checker)
|
||||
elif param.annotation == param.empty and param.name == "event":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
||||
return event
|
||||
|
||||
|
||||
class StateInner(T_State):
|
||||
...
|
||||
|
||||
|
||||
def State() -> T_State:
|
||||
"""**Deprecated**: 事件处理状态参数,请直接使用 {ref}`nonebot.typing.T_State`"""
|
||||
warnings.warn("State() is deprecated, use `T_State` instead", DeprecationWarning)
|
||||
return StateInner()
|
||||
async def _check(self, event: "Event", **kwargs: Any) -> Any:
|
||||
if checker := self.extra.get("checker", None):
|
||||
check_field_type(checker, event)
|
||||
|
||||
|
||||
class StateParam(Param):
|
||||
"""事件处理状态参数"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "StateParam()"
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["StateParam"]:
|
||||
if isinstance(param.default, StateInner):
|
||||
return cls(Required)
|
||||
elif param.default == param.empty:
|
||||
if param.default == param.empty:
|
||||
if param.annotation is T_State:
|
||||
return cls(Required)
|
||||
elif param.annotation == param.empty and name == "state":
|
||||
elif param.annotation == param.empty and param.name == "state":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
||||
@@ -281,14 +264,17 @@ class StateParam(Param):
|
||||
class MatcherParam(Param):
|
||||
"""事件响应器实例参数"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "MatcherParam()"
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["MatcherParam"]:
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
if generic_check_issubclass(param.annotation, Matcher) or (
|
||||
param.annotation == param.empty and name == "matcher"
|
||||
param.annotation == param.empty and param.name == "matcher"
|
||||
):
|
||||
return cls(Required)
|
||||
|
||||
@@ -303,6 +289,9 @@ class ArgInner:
|
||||
self.key = key
|
||||
self.type = type
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ArgInner(key={self.key!r}, type={self.type!r})"
|
||||
|
||||
|
||||
def Arg(key: Optional[str] = None) -> Any:
|
||||
"""`got` 的 Arg 参数消息"""
|
||||
@@ -322,12 +311,17 @@ def ArgPlainText(key: Optional[str] = None) -> str:
|
||||
class ArgParam(Param):
|
||||
"""`got` 的 Arg 参数"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["ArgParam"]:
|
||||
if isinstance(param.default, ArgInner):
|
||||
return cls(Required, key=param.default.key or name, type=param.default.type)
|
||||
return cls(
|
||||
Required, key=param.default.key or param.name, type=param.default.type
|
||||
)
|
||||
|
||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
message = matcher.get_arg(self.extra["key"])
|
||||
@@ -344,12 +338,15 @@ class ArgParam(Param):
|
||||
class ExceptionParam(Param):
|
||||
"""`run_postprocessor` 的异常参数"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "ExceptionParam()"
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["ExceptionParam"]:
|
||||
if generic_check_issubclass(param.annotation, Exception) or (
|
||||
param.annotation == param.empty and name == "exception"
|
||||
param.annotation == param.empty and param.name == "exception"
|
||||
):
|
||||
return cls(Required)
|
||||
|
||||
@@ -360,9 +357,12 @@ class ExceptionParam(Param):
|
||||
class DefaultParam(Param):
|
||||
"""默认值参数"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"DefaultParam(default={self.default!r})"
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
) -> Optional["DefaultParam"]:
|
||||
if param.default != param.empty:
|
||||
return cls(param.default)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import asyncio
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import Any, Set, Tuple, Union, NoReturn, Optional, Coroutine
|
||||
from typing import Set, Tuple, Union, NoReturn, Optional
|
||||
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.utils import run_coro_with_catch
|
||||
@@ -37,16 +37,19 @@ class Permission:
|
||||
]
|
||||
|
||||
def __init__(self, *checkers: Union[T_PermissionChecker, Dependent[bool]]) -> None:
|
||||
self.checkers: Set[Dependent[bool]] = set(
|
||||
self.checkers: Set[Dependent[bool]] = {
|
||||
checker
|
||||
if isinstance(checker, Dependent)
|
||||
else Dependent[bool].parse(
|
||||
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
||||
)
|
||||
for checker in checkers
|
||||
)
|
||||
}
|
||||
"""存储 `PermissionChecker`"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Permission({', '.join(repr(checker) for checker in self.checkers)})"
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
bot: Bot,
|
||||
@@ -121,10 +124,20 @@ class User:
|
||||
self.users = users
|
||||
self.perm = perm
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"User(users={self.users}"
|
||||
+ (f", permission={self.perm})" if self.perm else "")
|
||||
+ ")"
|
||||
)
|
||||
|
||||
async def __call__(self, bot: Bot, event: Event) -> bool:
|
||||
try:
|
||||
session = event.get_session_id()
|
||||
except Exception:
|
||||
return False
|
||||
return bool(
|
||||
event.get_session_id() in self.users
|
||||
and (self.perm is None or await self.perm(bot, event))
|
||||
session in self.users and (self.perm is None or await self.perm(bot, event))
|
||||
)
|
||||
|
||||
|
||||
|
@@ -37,16 +37,19 @@ class Rule:
|
||||
]
|
||||
|
||||
def __init__(self, *checkers: Union[T_RuleChecker, Dependent[bool]]) -> None:
|
||||
self.checkers: Set[Dependent[bool]] = set(
|
||||
self.checkers: Set[Dependent[bool]] = {
|
||||
checker
|
||||
if isinstance(checker, Dependent)
|
||||
else Dependent[bool].parse(
|
||||
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
||||
)
|
||||
for checker in checkers
|
||||
)
|
||||
}
|
||||
"""存储 `RuleChecker`"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Rule({', '.join(repr(checker) for checker in self.checkers)})"
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
bot: Bot,
|
||||
|
@@ -14,16 +14,14 @@ FrontMatter:
|
||||
|
||||
import sys
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Union
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import loguru
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# avoid sphinx autodoc resolve annotation failed
|
||||
# because loguru module do not have `Logger` class actually
|
||||
from loguru import Logger
|
||||
|
||||
from nonebot.plugin import Plugin
|
||||
from loguru import Logger, Record
|
||||
|
||||
# logger = logging.getLogger("nonebot")
|
||||
logger: "Logger" = loguru.logger
|
||||
@@ -47,26 +45,10 @@ logger: "Logger" = loguru.logger
|
||||
# logger.addHandler(default_handler)
|
||||
|
||||
|
||||
class Filter:
|
||||
def __init__(self) -> None:
|
||||
self.level: Union[int, str] = "INFO"
|
||||
|
||||
def __call__(self, record):
|
||||
module_name: str = record["name"]
|
||||
# TODO: get plugin name instead of module name
|
||||
# module = sys.modules.get(module_name)
|
||||
# if module and hasattr(module, "__plugin__"):
|
||||
# plugin: "Plugin" = getattr(module, "__plugin__")
|
||||
# module_name = plugin.module_name
|
||||
record["name"] = module_name.split(".")[0]
|
||||
levelno = (
|
||||
logger.level(self.level).no if isinstance(self.level, str) else self.level
|
||||
)
|
||||
return record["level"].no >= levelno
|
||||
|
||||
|
||||
class LoguruHandler(logging.Handler): # pragma: no cover
|
||||
def emit(self, record):
|
||||
"""logging 与 loguru 之间的桥梁,将 logging 的日志转发到 loguru。"""
|
||||
|
||||
def emit(self, record: logging.LogRecord):
|
||||
try:
|
||||
level = logger.level(record.levelname).name
|
||||
except ValueError:
|
||||
@@ -82,9 +64,13 @@ class LoguruHandler(logging.Handler): # pragma: no cover
|
||||
)
|
||||
|
||||
|
||||
logger.remove()
|
||||
default_filter: Filter = Filter()
|
||||
"""默认日志等级过滤器"""
|
||||
def default_filter(record: "Record"):
|
||||
"""默认的日志过滤器,根据 `config.log_level` 配置改变日志等级。"""
|
||||
log_level = record["extra"].get("nonebot_log_level", "INFO")
|
||||
levelno = logger.level(log_level).no if isinstance(log_level, str) else log_level
|
||||
return record["level"].no >= levelno
|
||||
|
||||
|
||||
default_format: str = (
|
||||
"<g>{time:MM-DD HH:mm:ss}</g> "
|
||||
"[<lvl>{level}</lvl>] "
|
||||
@@ -93,6 +79,8 @@ default_format: str = (
|
||||
"{message}"
|
||||
)
|
||||
"""默认日志格式"""
|
||||
|
||||
logger.remove()
|
||||
logger_id = logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
@@ -101,4 +89,4 @@ logger_id = logger.add(
|
||||
format=default_format,
|
||||
)
|
||||
|
||||
__autodoc__ = {"Filter": False, "LoguruHandler": False}
|
||||
__autodoc__ = {"logger_id": False}
|
||||
|
@@ -8,9 +8,10 @@ FrontMatter:
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
from datetime import datetime
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional, Coroutine
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.rule import TrieRule
|
||||
@@ -50,14 +51,14 @@ _event_postprocessors: Set[Dependent[Any]] = set()
|
||||
_run_preprocessors: Set[Dependent[Any]] = set()
|
||||
_run_postprocessors: Set[Dependent[Any]] = set()
|
||||
|
||||
EVENT_PCS_PARAMS = [
|
||||
EVENT_PCS_PARAMS = (
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
StateParam,
|
||||
DefaultParam,
|
||||
]
|
||||
RUN_PREPCS_PARAMS = [
|
||||
)
|
||||
RUN_PREPCS_PARAMS = (
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
@@ -65,8 +66,8 @@ RUN_PREPCS_PARAMS = [
|
||||
ArgParam,
|
||||
MatcherParam,
|
||||
DefaultParam,
|
||||
]
|
||||
RUN_POSTPCS_PARAMS = [
|
||||
)
|
||||
RUN_POSTPCS_PARAMS = (
|
||||
DependParam,
|
||||
ExceptionParam,
|
||||
BotParam,
|
||||
@@ -75,7 +76,7 @@ RUN_POSTPCS_PARAMS = [
|
||||
ArgParam,
|
||||
MatcherParam,
|
||||
DefaultParam,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
|
||||
@@ -120,10 +121,8 @@ async def _check_matcher(
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> None:
|
||||
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
matchers[priority].remove(Matcher)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -138,11 +137,8 @@ async def _check_matcher(
|
||||
return
|
||||
|
||||
if Matcher.temp:
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
matchers[priority].remove(Matcher)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
await _run_matcher(Matcher, bot, event, state, stack, dependency_cache)
|
||||
|
||||
|
||||
@@ -157,67 +153,58 @@ async def _run_matcher(
|
||||
logger.info(f"Event will be handled by {Matcher}")
|
||||
|
||||
matcher = Matcher()
|
||||
|
||||
coros = list(
|
||||
map(
|
||||
lambda x: run_coro_with_catch(
|
||||
x(
|
||||
matcher=matcher,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
matcher=matcher,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
_run_preprocessors,
|
||||
(SkippedException,),
|
||||
)
|
||||
)
|
||||
if coros:
|
||||
for proc in _run_preprocessors
|
||||
]:
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except IgnoredException:
|
||||
logger.opt(colors=True).info(
|
||||
f"Matcher {matcher} running is <b>cancelled</b>"
|
||||
)
|
||||
logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
|
||||
return
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
"<r><bg #f8bbd0>Error when running RunPreProcessors. Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
exception = None
|
||||
|
||||
try:
|
||||
logger.debug(f"Running matcher {matcher}")
|
||||
logger.debug(f"Running {matcher}")
|
||||
await matcher.run(bot, event, state, stack, dependency_cache)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f"<r><bg #f8bbd0>Running matcher {matcher} failed.</bg #f8bbd0></r>"
|
||||
f"<r><bg #f8bbd0>Running {matcher} failed.</bg #f8bbd0></r>"
|
||||
)
|
||||
exception = e
|
||||
|
||||
coros = list(
|
||||
map(
|
||||
lambda x: run_coro_with_catch(
|
||||
x(
|
||||
matcher=matcher,
|
||||
exception=exception,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=matcher.state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
matcher=matcher,
|
||||
exception=exception,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=matcher.state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
_run_postprocessors,
|
||||
(SkippedException,),
|
||||
)
|
||||
)
|
||||
if coros:
|
||||
for proc in _run_postprocessors
|
||||
]:
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except Exception as e:
|
||||
@@ -244,7 +231,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
```
|
||||
"""
|
||||
show_log = True
|
||||
log_msg = f"<m>{escape_tag(bot.type.upper())} {escape_tag(bot.self_id)}</m> | "
|
||||
log_msg = f"<m>{escape_tag(bot.type)} {escape_tag(bot.self_id)}</m> | "
|
||||
try:
|
||||
log_msg += event.get_log_string()
|
||||
except NoLogException:
|
||||
@@ -256,22 +243,19 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
dependency_cache: T_DependencyCache = {}
|
||||
|
||||
async with AsyncExitStack() as stack:
|
||||
coros = list(
|
||||
map(
|
||||
lambda x: run_coro_with_catch(
|
||||
x(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
_event_preprocessors,
|
||||
(SkippedException,),
|
||||
)
|
||||
)
|
||||
if coros:
|
||||
for proc in _event_preprocessors
|
||||
]:
|
||||
try:
|
||||
if show_log:
|
||||
logger.debug("Running PreProcessors...")
|
||||
@@ -324,22 +308,19 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
coros = list(
|
||||
map(
|
||||
lambda x: run_coro_with_catch(
|
||||
x(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
_event_postprocessors,
|
||||
(SkippedException,),
|
||||
)
|
||||
)
|
||||
if coros:
|
||||
for proc in _event_postprocessors
|
||||
]:
|
||||
try:
|
||||
if show_log:
|
||||
logger.debug("Running PostProcessors...")
|
||||
|
@@ -5,17 +5,16 @@ FrontMatter:
|
||||
description: nonebot.params 模块
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, List, Tuple, Optional
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters import Event, Message
|
||||
from nonebot.internal.params import Arg as Arg
|
||||
from nonebot.internal.params import State as State
|
||||
from nonebot.internal.params import ArgStr as ArgStr
|
||||
from nonebot.internal.params import Depends as Depends
|
||||
from nonebot.internal.params import ArgParam as ArgParam
|
||||
from nonebot.internal.params import BotParam as BotParam
|
||||
from nonebot.adapters import Event, Message, MessageSegment
|
||||
from nonebot.internal.params import EventParam as EventParam
|
||||
from nonebot.internal.params import StateParam as StateParam
|
||||
from nonebot.internal.params import DependParam as DependParam
|
||||
@@ -110,15 +109,15 @@ def CommandStart() -> str:
|
||||
|
||||
|
||||
def _shell_command_args(state: T_State) -> Any:
|
||||
return state[SHELL_ARGS]
|
||||
return state[SHELL_ARGS] # Namespace or ParserExit
|
||||
|
||||
|
||||
def ShellCommandArgs():
|
||||
def ShellCommandArgs() -> Any:
|
||||
"""shell 命令解析后的参数字典"""
|
||||
return Depends(_shell_command_args, use_cache=False)
|
||||
|
||||
|
||||
def _shell_command_argv(state: T_State) -> List[str]:
|
||||
def _shell_command_argv(state: T_State) -> List[Union[str, MessageSegment]]:
|
||||
return state[SHELL_ARGV]
|
||||
|
||||
|
||||
@@ -174,7 +173,6 @@ def LastReceived(default: Any = None) -> Any:
|
||||
|
||||
__autodoc__ = {
|
||||
"Arg": True,
|
||||
"State": True,
|
||||
"ArgStr": True,
|
||||
"Depends": True,
|
||||
"ArgParam": True,
|
||||
|
@@ -20,6 +20,9 @@ class Message:
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "Message()"
|
||||
|
||||
async def __call__(self, type: str = EventType()) -> bool:
|
||||
return type == "message"
|
||||
|
||||
@@ -29,6 +32,9 @@ class Notice:
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "Notice()"
|
||||
|
||||
async def __call__(self, type: str = EventType()) -> bool:
|
||||
return type == "notice"
|
||||
|
||||
@@ -38,6 +44,9 @@ class Request:
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "Request()"
|
||||
|
||||
async def __call__(self, type: str = EventType()) -> bool:
|
||||
return type == "request"
|
||||
|
||||
@@ -47,6 +56,9 @@ class MetaEvent:
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "MetaEvent()"
|
||||
|
||||
async def __call__(self, type: str = EventType()) -> bool:
|
||||
return type == "meta_event"
|
||||
|
||||
@@ -78,16 +90,23 @@ class SuperUser:
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "Superuser()"
|
||||
|
||||
async def __call__(self, bot: Bot, event: Event) -> bool:
|
||||
return event.get_type() == "message" and (
|
||||
f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}"
|
||||
try:
|
||||
user_id = event.get_user_id()
|
||||
except Exception:
|
||||
return False
|
||||
return (
|
||||
f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{user_id}"
|
||||
in bot.config.superusers
|
||||
or event.get_user_id() in bot.config.superusers # 兼容旧配置
|
||||
or user_id in bot.config.superusers # 兼容旧配置
|
||||
)
|
||||
|
||||
|
||||
SUPERUSER: Permission = Permission(SuperUser())
|
||||
"""匹配任意超级用户消息类型事件"""
|
||||
"""匹配任意超级用户事件"""
|
||||
|
||||
__autodoc__ = {
|
||||
"Permission": True,
|
||||
|
@@ -16,6 +16,7 @@
|
||||
- `on_command` => {ref}``on_command` <nonebot.plugin.on.on_command>`
|
||||
- `on_shell_command` => {ref}``on_shell_command` <nonebot.plugin.on.on_shell_command>`
|
||||
- `on_regex` => {ref}``on_regex` <nonebot.plugin.on.on_regex>`
|
||||
- `on_type` => {ref}``on_type` <nonebot.plugin.on.on_type>`
|
||||
- `CommandGroup` => {ref}``CommandGroup` <nonebot.plugin.on.CommandGroup>`
|
||||
- `Matchergroup` => {ref}``MatcherGroup` <nonebot.plugin.on.MatcherGroup>`
|
||||
- `load_plugin` => {ref}``load_plugin` <nonebot.plugin.load.load_plugin>`
|
||||
@@ -25,8 +26,8 @@
|
||||
- `load_from_toml` => {ref}``load_from_toml` <nonebot.plugin.load.load_from_toml>`
|
||||
- `load_builtin_plugin` => {ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
|
||||
- `load_builtin_plugins` => {ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
|
||||
- `export` => {ref}``export` <nonebot.plugin.export.export>`
|
||||
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
||||
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.plugin.PluginMetadata>`
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 0
|
||||
@@ -85,13 +86,12 @@ def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]:
|
||||
参数:
|
||||
module_name: 模块名,即 {ref}`nonebot.plugin.plugin.Plugin.module_name`。
|
||||
"""
|
||||
splits = module_name.split(".")
|
||||
loaded = {plugin.module_name: plugin for plugin in _plugins.values()}
|
||||
while splits:
|
||||
name = ".".join(splits)
|
||||
if name in loaded:
|
||||
return loaded[name]
|
||||
splits.pop()
|
||||
has_parent = True
|
||||
while has_parent:
|
||||
if module_name in loaded:
|
||||
return loaded[module_name]
|
||||
module_name, *has_parent = module_name.rsplit(".", 1)
|
||||
|
||||
|
||||
def get_loaded_plugins() -> Set["Plugin"]:
|
||||
@@ -106,8 +106,7 @@ def get_available_plugin_names() -> Set[str]:
|
||||
|
||||
from .on import on as on
|
||||
from .manager import PluginManager
|
||||
from .export import Export as Export
|
||||
from .export import export as export
|
||||
from .on import on_type as on_type
|
||||
from .load import require as require
|
||||
from .on import on_regex as on_regex
|
||||
from .plugin import Plugin as Plugin
|
||||
|
@@ -1,64 +0,0 @@
|
||||
"""本模块定义了插件导出的内容对象。
|
||||
|
||||
在新版插件系统中,推荐优先使用直接 import 所需要的插件内容。
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 4
|
||||
description: nonebot.plugin.export 模块
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
from . import _current_plugin_chain
|
||||
|
||||
|
||||
class Export(dict):
|
||||
"""插件导出内容以使得其他插件可以获得。
|
||||
|
||||
用法:
|
||||
```python
|
||||
nonebot.export().default = "bar"
|
||||
|
||||
@nonebot.export()
|
||||
def some_function():
|
||||
pass
|
||||
|
||||
# this doesn't work before python 3.9
|
||||
# use
|
||||
# export = nonebot.export(); @export.sub
|
||||
# instead
|
||||
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
|
||||
@nonebot.export().sub
|
||||
def something_else():
|
||||
pass
|
||||
```
|
||||
"""
|
||||
|
||||
def __call__(self, func, **kwargs):
|
||||
self[func.__name__] = func
|
||||
self.update(kwargs)
|
||||
return func
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
super().__setitem__(key, Export(value) if isinstance(value, dict) else value)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
self[name] = Export(value) if isinstance(value, dict) else value
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name not in self:
|
||||
self[name] = Export()
|
||||
return self[name]
|
||||
|
||||
|
||||
def export() -> Export:
|
||||
"""获取当前插件的导出内容对象"""
|
||||
warnings.warn(
|
||||
"nonebot.export() is deprecated. "
|
||||
"See https://github.com/nonebot/nonebot2/issues/935.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
plugins = _current_plugin_chain.get()
|
||||
if not plugins:
|
||||
raise RuntimeError("Export outside of the plugin!")
|
||||
return plugins[-1].export
|
@@ -5,24 +5,30 @@ FrontMatter:
|
||||
description: nonebot.plugin.load 模块
|
||||
"""
|
||||
import json
|
||||
import warnings
|
||||
from typing import Set, Iterable, Optional
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Set, Union, Iterable, Optional
|
||||
|
||||
import tomlkit
|
||||
|
||||
from .export import Export
|
||||
from nonebot.utils import path_to_module_name
|
||||
|
||||
from .plugin import Plugin
|
||||
from .manager import PluginManager
|
||||
from . import _managers, get_plugin, _module_name_to_plugin_name
|
||||
|
||||
|
||||
def load_plugin(module_path: str) -> Optional[Plugin]:
|
||||
def load_plugin(module_path: Union[str, Path]) -> Optional[Plugin]:
|
||||
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
||||
|
||||
参数:
|
||||
module_path: 插件名称 `path.to.your.plugin`
|
||||
module_path: 插件名称 `path.to.your.plugin` 或插件路径 `pathlib.Path(path/to/your/plugin)`
|
||||
"""
|
||||
|
||||
module_path = (
|
||||
path_to_module_name(module_path)
|
||||
if isinstance(module_path, Path)
|
||||
else module_path
|
||||
)
|
||||
manager = PluginManager([module_path])
|
||||
_managers.append(manager)
|
||||
return manager.load_plugin(module_path)
|
||||
@@ -74,6 +80,8 @@ def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
"""
|
||||
with open(file_path, "r", encoding=encoding) as f:
|
||||
data = json.load(f)
|
||||
if not isinstance(data, dict):
|
||||
raise TypeError("json file must contains a dict!")
|
||||
plugins = data.get("plugins")
|
||||
plugin_dirs = data.get("plugin_dirs")
|
||||
assert isinstance(plugins, list), "plugins must be a list of plugin name"
|
||||
@@ -103,15 +111,10 @@ def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
data = tomlkit.parse(f.read()) # type: ignore
|
||||
|
||||
nonebot_data = data.get("tool", {}).get("nonebot")
|
||||
if not nonebot_data:
|
||||
nonebot_data = data.get("nonebot", {}).get("plugins")
|
||||
if nonebot_data:
|
||||
warnings.warn(
|
||||
"[nonebot.plugins] table is deprecated. Use [tool.nonebot] instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
else:
|
||||
raise ValueError("Cannot find '[tool.nonebot]' in given toml file!")
|
||||
if nonebot_data is None:
|
||||
raise ValueError("Cannot find '[tool.nonebot]' in given toml file!")
|
||||
if not isinstance(nonebot_data, dict):
|
||||
raise TypeError("'[tool.nonebot]' must be a Table!")
|
||||
plugins = nonebot_data.get("plugins", [])
|
||||
plugin_dirs = nonebot_data.get("plugin_dirs", [])
|
||||
assert isinstance(plugins, list), "plugins must be a list of plugin name"
|
||||
@@ -143,7 +146,7 @@ def _find_manager_by_name(name: str) -> Optional[PluginManager]:
|
||||
return manager
|
||||
|
||||
|
||||
def require(name: str) -> Export:
|
||||
def require(name: str) -> ModuleType:
|
||||
"""获取一个插件的导出内容。
|
||||
|
||||
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
||||
@@ -156,11 +159,10 @@ def require(name: str) -> Export:
|
||||
"""
|
||||
plugin = get_plugin(_module_name_to_plugin_name(name))
|
||||
if not plugin:
|
||||
manager = _find_manager_by_name(name)
|
||||
if manager:
|
||||
if manager := _find_manager_by_name(name):
|
||||
plugin = manager.load_plugin(name)
|
||||
else:
|
||||
plugin = load_plugin(name)
|
||||
if not plugin:
|
||||
raise RuntimeError(f'Cannot load plugin "{name}"!')
|
||||
return plugin.export
|
||||
if not plugin:
|
||||
raise RuntimeError(f'Cannot load plugin "{name}"!')
|
||||
return plugin.module
|
||||
|
@@ -17,7 +17,7 @@ from importlib.machinery import PathFinder, SourceFileLoader
|
||||
from typing import Set, Dict, List, Union, Iterable, Optional, Sequence
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.utils import escape_tag
|
||||
from nonebot.utils import escape_tag, path_to_module_name
|
||||
|
||||
from .plugin import Plugin, PluginMetadata
|
||||
from . import (
|
||||
@@ -51,6 +51,9 @@ class PluginManager:
|
||||
self._searched_plugin_names: Dict[str, Path] = {}
|
||||
self.prepare_plugins()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"PluginManager(plugins={self.plugins}, search_path={self.search_path})"
|
||||
|
||||
@property
|
||||
def third_party_plugins(self) -> Set[str]:
|
||||
"""返回所有独立插件名称。"""
|
||||
@@ -66,13 +69,6 @@ class PluginManager:
|
||||
"""返回当前插件管理器中可用的插件名称。"""
|
||||
return self.third_party_plugins | self.searched_plugins
|
||||
|
||||
def _path_to_module_name(self, path: Path) -> str:
|
||||
rel_path = path.resolve().relative_to(Path(".").resolve())
|
||||
if rel_path.stem == "__init__":
|
||||
return ".".join(rel_path.parts[:-1])
|
||||
else:
|
||||
return ".".join(rel_path.parts[:-1] + (rel_path.stem,))
|
||||
|
||||
def _previous_plugins(self) -> Set[str]:
|
||||
_pre_managers: List[PluginManager]
|
||||
if self in _managers:
|
||||
@@ -86,7 +82,6 @@ class PluginManager:
|
||||
|
||||
def prepare_plugins(self) -> Set[str]:
|
||||
"""搜索插件并缓存插件名称。"""
|
||||
|
||||
# get all previous ready to load plugins
|
||||
previous_plugins = self._previous_plugins()
|
||||
searched_plugins: Dict[str, Path] = {}
|
||||
@@ -118,11 +113,13 @@ class PluginManager:
|
||||
f"Plugin already exists: {module_info.name}! Check your plugin name"
|
||||
)
|
||||
|
||||
module_spec = module_info.module_finder.find_spec(module_info.name, None)
|
||||
if not module_spec:
|
||||
if not (
|
||||
module_spec := module_info.module_finder.find_spec(
|
||||
module_info.name, None
|
||||
)
|
||||
):
|
||||
continue
|
||||
module_path = module_spec.origin
|
||||
if not module_path:
|
||||
if not (module_path := module_spec.origin):
|
||||
continue
|
||||
searched_plugins[module_info.name] = Path(module_path).resolve()
|
||||
|
||||
@@ -146,7 +143,7 @@ class PluginManager:
|
||||
module = importlib.import_module(self._third_party_plugin_names[name])
|
||||
elif name in self._searched_plugin_names:
|
||||
module = importlib.import_module(
|
||||
self._path_to_module_name(self._searched_plugin_names[name])
|
||||
path_to_module_name(self._searched_plugin_names[name])
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name")
|
||||
@@ -154,8 +151,7 @@ class PluginManager:
|
||||
logger.opt(colors=True).success(
|
||||
f'Succeeded to import "<y>{escape_tag(name)}</y>"'
|
||||
)
|
||||
plugin = getattr(module, "__plugin__", None)
|
||||
if plugin is None:
|
||||
if (plugin := getattr(module, "__plugin__", None)) is None:
|
||||
raise RuntimeError(
|
||||
f"Module {module.__name__} is not loaded as a plugin! "
|
||||
"Make sure not to import it before loading."
|
||||
|
@@ -10,6 +10,7 @@ from types import ModuleType
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.permission import Permission
|
||||
from nonebot.dependencies import Dependent
|
||||
@@ -19,6 +20,7 @@ from nonebot.rule import (
|
||||
ArgumentParser,
|
||||
regex,
|
||||
command,
|
||||
is_type,
|
||||
keyword,
|
||||
endswith,
|
||||
fullmatch,
|
||||
@@ -30,9 +32,8 @@ from .manager import _current_plugin_chain
|
||||
|
||||
|
||||
def _store_matcher(matcher: Type[Matcher]) -> None:
|
||||
plugins = _current_plugin_chain.get()
|
||||
# only store the matcher defined in the plugin
|
||||
if plugins:
|
||||
if plugins := _current_plugin_chain.get():
|
||||
plugins[-1].matcher.add(matcher)
|
||||
|
||||
|
||||
@@ -368,7 +369,7 @@ def on_command(
|
||||
state: 默认 state
|
||||
"""
|
||||
|
||||
commands = set([cmd]) | (aliases or set())
|
||||
commands = {cmd} | (aliases or set())
|
||||
block = kwargs.pop("block", False)
|
||||
return on_message(
|
||||
command(*commands) & rule, block=block, **kwargs, _depth=_depth + 1
|
||||
@@ -403,7 +404,7 @@ def on_shell_command(
|
||||
state: 默认 state
|
||||
"""
|
||||
|
||||
commands = set([cmd]) | (aliases or set())
|
||||
commands = {cmd} | (aliases or set())
|
||||
return on_message(
|
||||
shell_command(*commands, parser=parser) & rule,
|
||||
**kwargs,
|
||||
@@ -437,7 +438,57 @@ def on_regex(
|
||||
return on_message(regex(pattern, flags) & rule, **kwargs, _depth=_depth + 1)
|
||||
|
||||
|
||||
class CommandGroup:
|
||||
def on_type(
|
||||
types: Union[Type[Event], Tuple[Type[Event]]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
*,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""注册一个事件响应器,并且当事件为指定类型时响应。
|
||||
|
||||
参数:
|
||||
types: 事件类型
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
expire_time: 事件响应器最终有效时间点,过时即被删除
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
event_types = types if isinstance(types, tuple) else (types,)
|
||||
return on(rule=is_type(*event_types) & rule, **kwargs, _depth=_depth + 1)
|
||||
|
||||
|
||||
class _Group:
|
||||
def __init__(self, **kwargs):
|
||||
"""创建一个事件响应器组合,参数为默认值,与 `on` 一致"""
|
||||
self.matchers: List[Type[Matcher]] = []
|
||||
"""组内事件响应器列表"""
|
||||
self.base_kwargs: Dict[str, Any] = kwargs
|
||||
"""其他传递给 `on` 的参数默认值"""
|
||||
|
||||
def _get_final_kwargs(
|
||||
self, update: Dict[str, Any], *, exclude: Optional[Set[str]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""获取最终传递给 `on` 的参数
|
||||
|
||||
参数:
|
||||
update: 更新的关键字参数
|
||||
exclude: 需要排除的参数
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(update)
|
||||
if exclude:
|
||||
for key in exclude:
|
||||
final_kwargs.pop(key, None)
|
||||
final_kwargs["_depth"] = 1
|
||||
return final_kwargs
|
||||
|
||||
|
||||
class CommandGroup(_Group):
|
||||
"""命令组,用于声明一组有相同名称前缀的命令。
|
||||
|
||||
参数:
|
||||
@@ -453,12 +504,13 @@ class CommandGroup:
|
||||
"""
|
||||
|
||||
def __init__(self, cmd: Union[str, Tuple[str, ...]], **kwargs):
|
||||
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
|
||||
"""命令前缀"""
|
||||
if "aliases" in kwargs:
|
||||
del kwargs["aliases"]
|
||||
self.base_kwargs: Dict[str, Any] = kwargs
|
||||
"""其他传递给 `on_command` 的参数默认值"""
|
||||
super().__init__(**kwargs)
|
||||
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
|
||||
self.base_kwargs.pop("aliases", None)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"CommandGroup(cmd={self.basecmd}, matchers={len(self.matchers)})"
|
||||
|
||||
def command(self, cmd: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
|
||||
"""注册一个新的命令。新参数将会覆盖命令组默认值
|
||||
@@ -477,10 +529,9 @@ class CommandGroup:
|
||||
"""
|
||||
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
|
||||
cmd = self.basecmd + sub_cmd
|
||||
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
return on_command(cmd, **final_kwargs, _depth=1)
|
||||
matcher = on_command(cmd, **self._get_final_kwargs(kwargs))
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def shell_command(
|
||||
self, cmd: Union[str, Tuple[str, ...]], **kwargs
|
||||
@@ -502,21 +553,16 @@ class CommandGroup:
|
||||
"""
|
||||
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
|
||||
cmd = self.basecmd + sub_cmd
|
||||
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
return on_shell_command(cmd, **final_kwargs, _depth=1)
|
||||
matcher = on_shell_command(cmd, **self._get_final_kwargs(kwargs))
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
|
||||
class MatcherGroup:
|
||||
class MatcherGroup(_Group):
|
||||
"""事件响应器组合,统一管理。为 `Matcher` 创建提供默认属性。"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""创建一个事件响应器组合,参数为默认值,与 `on` 一致"""
|
||||
self.matchers: List[Type[Matcher]] = []
|
||||
"""组内事件响应器列表"""
|
||||
self.base_kwargs: Dict[str, Any] = kwargs
|
||||
"""其他传递给 `on` 的参数默认值"""
|
||||
def __repr__(self) -> str:
|
||||
return f"MatcherGroup(matchers={len(self.matchers)})"
|
||||
|
||||
def on(self, **kwargs) -> Type[Matcher]:
|
||||
"""注册一个基础事件响应器,可自定义类型。
|
||||
@@ -532,9 +578,7 @@ class MatcherGroup:
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
matcher = on(**final_kwargs, _depth=1)
|
||||
matcher = on(**self._get_final_kwargs(kwargs))
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
@@ -550,11 +594,8 @@ class MatcherGroup:
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
final_kwargs.pop("permission", None)
|
||||
matcher = on_metaevent(**final_kwargs, _depth=1)
|
||||
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type", "permission"})
|
||||
matcher = on_metaevent(**final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
@@ -571,10 +612,8 @@ class MatcherGroup:
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
matcher = on_message(**final_kwargs, _depth=1)
|
||||
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||
matcher = on_message(**final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
@@ -590,10 +629,8 @@ class MatcherGroup:
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
matcher = on_notice(**final_kwargs, _depth=1)
|
||||
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type", "permission"})
|
||||
matcher = on_notice(**final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
@@ -609,10 +646,8 @@ class MatcherGroup:
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
matcher = on_request(**final_kwargs, _depth=1)
|
||||
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type", "permission"})
|
||||
matcher = on_request(**final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
@@ -633,10 +668,8 @@ class MatcherGroup:
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
matcher = on_startswith(msg, **final_kwargs, _depth=1)
|
||||
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||
matcher = on_startswith(msg, **final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
@@ -655,10 +688,8 @@ class MatcherGroup:
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
matcher = on_endswith(msg, **final_kwargs, _depth=1)
|
||||
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||
matcher = on_endswith(msg, **final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
@@ -677,10 +708,8 @@ class MatcherGroup:
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
matcher = on_fullmatch(msg, **final_kwargs, _depth=1)
|
||||
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||
matcher = on_fullmatch(msg, **final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
@@ -698,10 +727,8 @@ class MatcherGroup:
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
matcher = on_keyword(keywords, **final_kwargs, _depth=1)
|
||||
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||
matcher = on_keyword(keywords, **final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
@@ -727,10 +754,8 @@ class MatcherGroup:
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
matcher = on_command(cmd, aliases=aliases, **final_kwargs, _depth=1)
|
||||
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||
matcher = on_command(cmd, aliases=aliases, **final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
@@ -760,12 +785,8 @@ class MatcherGroup:
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
matcher = on_shell_command(
|
||||
cmd, aliases=aliases, parser=parser, **final_kwargs, _depth=1
|
||||
)
|
||||
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||
matcher = on_shell_command(cmd, aliases=aliases, parser=parser, **final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
@@ -788,9 +809,28 @@ class MatcherGroup:
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
matcher = on_regex(pattern, flags=flags, **final_kwargs, _depth=1)
|
||||
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||
matcher = on_regex(pattern, flags=flags, **final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_type(
|
||||
self, types: Union[Type[Event], Tuple[Type[Event]]], **kwargs
|
||||
) -> Type[Matcher]:
|
||||
"""注册一个事件响应器,并且当事件为指定类型时响应。
|
||||
|
||||
参数:
|
||||
types: 事件类型
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
expire_time: 事件响应器最终有效时间点,过时即被删除
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
|
||||
matcher = on_type(types, **final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
@@ -2,6 +2,7 @@ import re
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Set, List, Type, Tuple, Union, Optional
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.permission import Permission
|
||||
from nonebot.dependencies import Dependent
|
||||
@@ -152,6 +153,18 @@ def on_regex(
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_type(
|
||||
types: Union[Type[Event], Tuple[Type[Event]]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
*,
|
||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
|
||||
class CommandGroup:
|
||||
def __init__(
|
||||
@@ -171,8 +184,8 @@ class CommandGroup:
|
||||
self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
*,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
||||
temp: bool = ...,
|
||||
@@ -186,7 +199,7 @@ class CommandGroup:
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
*,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]],
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
||||
parser: Optional[ArgumentParser] = ...,
|
||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
||||
@@ -367,3 +380,16 @@ class MatcherGroup:
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_type(
|
||||
self,
|
||||
types: Union[Type[Event], Tuple[Type[Event]]],
|
||||
*,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: Optional[Union[datetime, timedelta]] = ...,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
|
@@ -12,7 +12,6 @@ from pydantic import BaseModel
|
||||
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
from .export import Export
|
||||
from . import _plugins as plugins # FIXME: backport for nonebug
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -46,8 +45,6 @@ class Plugin:
|
||||
"""点分割模块路径"""
|
||||
manager: "PluginManager"
|
||||
"""导入该插件的插件管理器"""
|
||||
export: Export = field(default_factory=Export)
|
||||
"""**Deprecated:** 插件内定义的导出内容"""
|
||||
matcher: Set[Type[Matcher]] = field(default_factory=set)
|
||||
"""插件内定义的 `Matcher`"""
|
||||
parent_plugin: Optional["Plugin"] = None
|
||||
|
@@ -7,5 +7,5 @@ echo = on_command("echo", to_me())
|
||||
|
||||
|
||||
@echo.handle()
|
||||
async def echo_escape(message: Message = CommandArg()):
|
||||
async def handle_echo(message: Message = CommandArg()):
|
||||
await echo.send(message=message)
|
||||
|
@@ -15,8 +15,7 @@ async def matcher_mutex(event: Event) -> AsyncGenerator[bool, None]:
|
||||
yield result
|
||||
else:
|
||||
current_event_id = id(event)
|
||||
event_id = _running_matcher.get(session_id, None)
|
||||
if event_id:
|
||||
if event_id := _running_matcher.get(session_id, None):
|
||||
result = event_id != current_event_id
|
||||
else:
|
||||
_running_matcher[session_id] = current_event_id
|
||||
|
315
nonebot/rule.py
315
nonebot/rule.py
@@ -10,11 +10,27 @@ FrontMatter:
|
||||
|
||||
import re
|
||||
import shlex
|
||||
from itertools import product
|
||||
from argparse import Namespace
|
||||
from typing_extensions import TypedDict
|
||||
from argparse import Action
|
||||
from argparse import ArgumentError
|
||||
from itertools import chain, product
|
||||
from argparse import Namespace as Namespace
|
||||
from argparse import ArgumentParser as ArgParser
|
||||
from typing import Any, List, Tuple, Union, Optional, Sequence, NamedTuple
|
||||
from typing import (
|
||||
IO,
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
TypeVar,
|
||||
Optional,
|
||||
Sequence,
|
||||
TypedDict,
|
||||
NamedTuple,
|
||||
cast,
|
||||
overload,
|
||||
)
|
||||
|
||||
from pygtrie import CharTrie
|
||||
|
||||
@@ -23,15 +39,8 @@ from nonebot.log import logger
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.exception import ParserExit
|
||||
from nonebot.internal.rule import Rule as Rule
|
||||
from nonebot.params import Command, EventToMe, CommandArg
|
||||
from nonebot.adapters import Bot, Event, Message, MessageSegment
|
||||
from nonebot.params import (
|
||||
Command,
|
||||
EventToMe,
|
||||
EventType,
|
||||
CommandArg,
|
||||
EventMessage,
|
||||
EventPlainText,
|
||||
)
|
||||
from nonebot.consts import (
|
||||
CMD_KEY,
|
||||
PREFIX_KEY,
|
||||
@@ -45,6 +54,8 @@ from nonebot.consts import (
|
||||
REGEX_MATCHED,
|
||||
)
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
CMD_RESULT = TypedDict(
|
||||
"CMD_RESULT",
|
||||
{
|
||||
@@ -83,8 +94,7 @@ class TrieRule:
|
||||
message_seg: MessageSegment = message[0]
|
||||
if message_seg.is_text():
|
||||
segment_text = str(message_seg).lstrip()
|
||||
pf = cls.prefix.longest_prefix(segment_text)
|
||||
if pf:
|
||||
if pf := cls.prefix.longest_prefix(segment_text):
|
||||
value: TRIE_VALUE = pf.value
|
||||
prefix[RAW_CMD_KEY] = pf.key
|
||||
prefix[CMD_START_KEY] = value.command_start
|
||||
@@ -113,10 +123,25 @@ class StartswithRule:
|
||||
self.msg = msg
|
||||
self.ignorecase = ignorecase
|
||||
|
||||
async def __call__(
|
||||
self, type: str = EventType(), text: str = EventPlainText()
|
||||
) -> Any:
|
||||
if type != "message":
|
||||
def __repr__(self) -> str:
|
||||
return f"Startswith(msg={self.msg}, ignorecase={self.ignorecase})"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return (
|
||||
isinstance(other, StartswithRule)
|
||||
and frozenset(self.msg) == frozenset(other.msg)
|
||||
and self.ignorecase == other.ignorecase
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((frozenset(self.msg), self.ignorecase))
|
||||
|
||||
async def __call__(self, event: Event) -> bool:
|
||||
if event.get_type() != "message":
|
||||
return False
|
||||
try:
|
||||
text = event.get_plaintext()
|
||||
except Exception:
|
||||
return False
|
||||
return bool(
|
||||
re.match(
|
||||
@@ -154,10 +179,25 @@ class EndswithRule:
|
||||
self.msg = msg
|
||||
self.ignorecase = ignorecase
|
||||
|
||||
async def __call__(
|
||||
self, type: str = EventType(), text: str = EventPlainText()
|
||||
) -> Any:
|
||||
if type != "message":
|
||||
def __repr__(self) -> str:
|
||||
return f"Endswith(msg={self.msg}, ignorecase={self.ignorecase})"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return (
|
||||
isinstance(other, EndswithRule)
|
||||
and frozenset(self.msg) == frozenset(other.msg)
|
||||
and self.ignorecase == other.ignorecase
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((frozenset(self.msg), self.ignorecase))
|
||||
|
||||
async def __call__(self, event: Event) -> bool:
|
||||
if event.get_type() != "message":
|
||||
return False
|
||||
try:
|
||||
text = event.get_plaintext()
|
||||
except Exception:
|
||||
return False
|
||||
return bool(
|
||||
re.search(
|
||||
@@ -192,17 +232,31 @@ class FullmatchRule:
|
||||
__slots__ = ("msg", "ignorecase")
|
||||
|
||||
def __init__(self, msg: Tuple[str, ...], ignorecase: bool = False):
|
||||
self.msg = frozenset(map(str.casefold, msg) if ignorecase else msg)
|
||||
self.msg = tuple(map(str.casefold, msg) if ignorecase else msg)
|
||||
self.ignorecase = ignorecase
|
||||
|
||||
async def __call__(
|
||||
self, type_: str = EventType(), text: str = EventPlainText()
|
||||
) -> bool:
|
||||
def __repr__(self) -> str:
|
||||
return f"Fullmatch(msg={self.msg}, ignorecase={self.ignorecase})"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return (
|
||||
type_ == "message"
|
||||
and (text.casefold() if self.ignorecase else text) in self.msg
|
||||
isinstance(other, FullmatchRule)
|
||||
and frozenset(self.msg) == frozenset(other.msg)
|
||||
and self.ignorecase == other.ignorecase
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((frozenset(self.msg), self.ignorecase))
|
||||
|
||||
async def __call__(self, event: Event) -> bool:
|
||||
if event.get_type() != "message":
|
||||
return False
|
||||
try:
|
||||
text = event.get_plaintext()
|
||||
except Exception:
|
||||
return False
|
||||
return (text.casefold() if self.ignorecase else text) in self.msg
|
||||
|
||||
|
||||
def fullmatch(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
"""完全匹配消息。
|
||||
@@ -229,10 +283,23 @@ class KeywordsRule:
|
||||
def __init__(self, *keywords: str):
|
||||
self.keywords = keywords
|
||||
|
||||
async def __call__(
|
||||
self, type: str = EventType(), text: str = EventPlainText()
|
||||
) -> bool:
|
||||
if type != "message":
|
||||
def __repr__(self) -> str:
|
||||
return f"Keywords(keywords={self.keywords})"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, KeywordsRule) and frozenset(
|
||||
self.keywords
|
||||
) == frozenset(other.keywords)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(frozenset(self.keywords))
|
||||
|
||||
async def __call__(self, event: Event) -> bool:
|
||||
if event.get_type() != "message":
|
||||
return False
|
||||
try:
|
||||
text = event.get_plaintext()
|
||||
except Exception:
|
||||
return False
|
||||
return bool(text and any(keyword in text for keyword in self.keywords))
|
||||
|
||||
@@ -257,14 +324,22 @@ class CommandRule:
|
||||
__slots__ = ("cmds",)
|
||||
|
||||
def __init__(self, cmds: List[Tuple[str, ...]]):
|
||||
self.cmds = cmds
|
||||
self.cmds = tuple(cmds)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Command(cmds={self.cmds})"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, CommandRule) and frozenset(self.cmds) == frozenset(
|
||||
other.cmds
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((frozenset(self.cmds),))
|
||||
|
||||
async def __call__(self, cmd: Optional[Tuple[str, ...]] = Command()) -> bool:
|
||||
return cmd in self.cmds
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Command {self.cmds}>"
|
||||
|
||||
|
||||
def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule:
|
||||
"""匹配消息命令。
|
||||
@@ -320,25 +395,48 @@ class ArgumentParser(ArgParser):
|
||||
参考文档: [argparse](https://docs.python.org/3/library/argparse.html)
|
||||
"""
|
||||
|
||||
def _print_message(self, message, file=None):
|
||||
old_message: str = getattr(self, "message", "")
|
||||
if old_message:
|
||||
old_message += "\n"
|
||||
old_message += message
|
||||
setattr(self, "message", old_message)
|
||||
if TYPE_CHECKING:
|
||||
|
||||
def exit(self, status: int = 0, message: Optional[str] = None):
|
||||
raise ParserExit(
|
||||
status=status, message=message or getattr(self, "message", None)
|
||||
@overload
|
||||
def parse_args(
|
||||
self, args: Optional[Sequence[Union[str, MessageSegment]]] = ...
|
||||
) -> Namespace:
|
||||
...
|
||||
|
||||
@overload
|
||||
def parse_args(
|
||||
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: None
|
||||
) -> Namespace:
|
||||
... # type: ignore[misc]
|
||||
|
||||
@overload
|
||||
def parse_args(
|
||||
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
|
||||
) -> T:
|
||||
...
|
||||
|
||||
def parse_args(
|
||||
self,
|
||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||
namespace: Optional[T] = None,
|
||||
) -> Union[Namespace, T]:
|
||||
...
|
||||
|
||||
def _parse_optional(
|
||||
self, arg_string: Union[str, MessageSegment]
|
||||
) -> Optional[Tuple[Optional[Action], str, Optional[str]]]:
|
||||
return (
|
||||
super()._parse_optional(arg_string) if isinstance(arg_string, str) else None
|
||||
)
|
||||
|
||||
def parse_args(
|
||||
self,
|
||||
args: Optional[Sequence[str]] = None,
|
||||
namespace: Optional[Namespace] = None,
|
||||
) -> Namespace:
|
||||
setattr(self, "message", "")
|
||||
return super().parse_args(args=args, namespace=namespace) # type: ignore
|
||||
def _print_message(self, message: str, file: Optional[IO[str]] = None):
|
||||
if message:
|
||||
setattr(self, "_message", getattr(self, "_message", "") + message)
|
||||
|
||||
def exit(self, status: int = 0, message: Optional[str] = None):
|
||||
if message:
|
||||
self._print_message(message)
|
||||
raise ParserExit(status=status, message=getattr(self, "_message", None))
|
||||
|
||||
|
||||
class ShellCommandRule:
|
||||
@@ -352,28 +450,48 @@ class ShellCommandRule:
|
||||
__slots__ = ("cmds", "parser")
|
||||
|
||||
def __init__(self, cmds: List[Tuple[str, ...]], parser: Optional[ArgumentParser]):
|
||||
self.cmds = cmds
|
||||
self.cmds = tuple(cmds)
|
||||
self.parser = parser
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ShellCommand(cmds={self.cmds}, parser={self.parser})"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return (
|
||||
isinstance(other, ShellCommandRule)
|
||||
and frozenset(self.cmds) == frozenset(other.cmds)
|
||||
and self.parser is other.parser
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((frozenset(self.cmds), self.parser))
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
state: T_State,
|
||||
cmd: Optional[Tuple[str, ...]] = Command(),
|
||||
msg: Optional[Message] = CommandArg(),
|
||||
) -> bool:
|
||||
if cmd in self.cmds and msg is not None:
|
||||
message = str(msg)
|
||||
state[SHELL_ARGV] = shlex.split(message)
|
||||
if self.parser:
|
||||
try:
|
||||
args = self.parser.parse_args(state[SHELL_ARGV])
|
||||
state[SHELL_ARGS] = args
|
||||
except ParserExit as e:
|
||||
state[SHELL_ARGS] = e
|
||||
return True
|
||||
else:
|
||||
if cmd not in self.cmds or msg is None:
|
||||
return False
|
||||
|
||||
state[SHELL_ARGV] = list(
|
||||
chain.from_iterable(
|
||||
shlex.split(str(seg)) if cast(MessageSegment, seg).is_text() else (seg,)
|
||||
for seg in msg
|
||||
)
|
||||
)
|
||||
|
||||
if self.parser:
|
||||
try:
|
||||
args = self.parser.parse_args(state[SHELL_ARGV])
|
||||
state[SHELL_ARGS] = args
|
||||
except ArgumentError as e:
|
||||
state[SHELL_ARGS] = ParserExit(status=2, message=str(e))
|
||||
except ParserExit as e:
|
||||
state[SHELL_ARGS] = e
|
||||
return True
|
||||
|
||||
|
||||
def shell_command(
|
||||
*cmds: Union[str, Tuple[str, ...]], parser: Optional[ArgumentParser] = None
|
||||
@@ -452,16 +570,27 @@ class RegexRule:
|
||||
self.regex = regex
|
||||
self.flags = flags
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
state: T_State,
|
||||
type: str = EventType(),
|
||||
msg: Message = EventMessage(),
|
||||
) -> bool:
|
||||
if type != "message":
|
||||
def __repr__(self) -> str:
|
||||
return f"Regex(regex={self.regex!r}, flags={self.flags})"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return (
|
||||
isinstance(other, RegexRule)
|
||||
and self.regex == other.regex
|
||||
and self.flags == other.flags
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.regex, self.flags))
|
||||
|
||||
async def __call__(self, event: Event, state: T_State) -> bool:
|
||||
if event.get_type() != "message":
|
||||
return False
|
||||
matched = re.search(self.regex, str(msg), self.flags)
|
||||
if matched:
|
||||
try:
|
||||
msg = event.get_message()
|
||||
except Exception:
|
||||
return False
|
||||
if matched := re.search(self.regex, str(msg), self.flags):
|
||||
state[REGEX_MATCHED] = matched.group()
|
||||
state[REGEX_GROUP] = matched.groups()
|
||||
state[REGEX_DICT] = matched.groupdict()
|
||||
@@ -498,6 +627,15 @@ class ToMeRule:
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "ToMe()"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, ToMeRule)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.__class__,))
|
||||
|
||||
async def __call__(self, to_me: bool = EventToMe()) -> bool:
|
||||
return to_me
|
||||
|
||||
@@ -508,6 +646,37 @@ def to_me() -> Rule:
|
||||
return Rule(ToMeRule())
|
||||
|
||||
|
||||
class IsTypeRule:
|
||||
"""检查事件类型是否为指定类型。"""
|
||||
|
||||
__slots__ = ("types",)
|
||||
|
||||
def __init__(self, *types: Type[Event]):
|
||||
self.types = types
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"IsType(types={tuple(type.__name__ for type in self.types)})"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, IsTypeRule) and self.types == other.types
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.types,))
|
||||
|
||||
async def __call__(self, event: Event) -> bool:
|
||||
return isinstance(event, self.types)
|
||||
|
||||
|
||||
def is_type(*types: Type[Event]) -> Rule:
|
||||
"""匹配事件类型。
|
||||
|
||||
参数:
|
||||
types: 事件类型
|
||||
"""
|
||||
|
||||
return Rule(IsTypeRule(*types))
|
||||
|
||||
|
||||
__autodoc__ = {
|
||||
"Rule": True,
|
||||
"Rule.__call__": True,
|
||||
|
@@ -11,6 +11,7 @@ FrontMatter:
|
||||
sidebar_position: 11
|
||||
description: nonebot.typing 模块
|
||||
"""
|
||||
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
@@ -28,6 +29,8 @@ if TYPE_CHECKING:
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.permission import Permission
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
T_Wrapped = TypeVar("T_Wrapped", bound=Callable)
|
||||
|
||||
|
||||
@@ -41,10 +44,14 @@ def overrides(InterfaceClass: object) -> Callable[[T_Wrapped], T_Wrapped]:
|
||||
return overrider
|
||||
|
||||
|
||||
# state
|
||||
T_State = Dict[Any, Any]
|
||||
"""事件处理状态 State 类型"""
|
||||
|
||||
T_BotConnectionHook = Callable[..., Awaitable[Any]]
|
||||
_DependentCallable = Union[Callable[..., T], Callable[..., Awaitable[T]]]
|
||||
|
||||
# driver hooks
|
||||
T_BotConnectionHook = _DependentCallable[Any]
|
||||
"""Bot 连接建立时钩子函数
|
||||
|
||||
依赖参数:
|
||||
@@ -53,7 +60,7 @@ T_BotConnectionHook = Callable[..., Awaitable[Any]]
|
||||
- BotParam: Bot 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_BotDisconnectionHook = Callable[..., Awaitable[Any]]
|
||||
T_BotDisconnectionHook = _DependentCallable[Any]
|
||||
"""Bot 连接断开时钩子函数
|
||||
|
||||
依赖参数:
|
||||
@@ -62,6 +69,8 @@ T_BotDisconnectionHook = Callable[..., Awaitable[Any]]
|
||||
- BotParam: Bot 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
|
||||
# api hooks
|
||||
T_CallingAPIHook = Callable[["Bot", str, Dict[str, Any]], Awaitable[Any]]
|
||||
"""`bot.call_api` 钩子函数"""
|
||||
T_CalledAPIHook = Callable[
|
||||
@@ -69,7 +78,8 @@ T_CalledAPIHook = Callable[
|
||||
]
|
||||
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
|
||||
|
||||
T_EventPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
||||
# event hooks
|
||||
T_EventPreProcessor = _DependentCallable[Any]
|
||||
"""事件预处理函数 EventPreProcessor 类型
|
||||
|
||||
依赖参数:
|
||||
@@ -80,7 +90,7 @@ T_EventPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
||||
- StateParam: State 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_EventPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
||||
T_EventPostProcessor = _DependentCallable[Any]
|
||||
"""事件预处理函数 EventPostProcessor 类型
|
||||
|
||||
依赖参数:
|
||||
@@ -91,7 +101,9 @@ T_EventPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
||||
- StateParam: State 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_RunPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
||||
|
||||
# matcher run hooks
|
||||
T_RunPreProcessor = _DependentCallable[Any]
|
||||
"""事件响应器运行前预处理函数 RunPreProcessor 类型
|
||||
|
||||
依赖参数:
|
||||
@@ -103,7 +115,7 @@ T_RunPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
||||
- MatcherParam: Matcher 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_RunPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
||||
T_RunPostProcessor = _DependentCallable[Any]
|
||||
"""事件响应器运行后后处理函数 RunPostProcessor 类型
|
||||
|
||||
依赖参数:
|
||||
@@ -117,7 +129,8 @@ T_RunPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
|
||||
T_RuleChecker = Callable[..., Union[bool, Awaitable[bool]]]
|
||||
# rule, permission
|
||||
T_RuleChecker = _DependentCallable[bool]
|
||||
"""RuleChecker 即判断是否响应事件的处理函数。
|
||||
|
||||
依赖参数:
|
||||
@@ -128,7 +141,7 @@ T_RuleChecker = Callable[..., Union[bool, Awaitable[bool]]]
|
||||
- StateParam: State 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_PermissionChecker = Callable[..., Union[bool, Awaitable[bool]]]
|
||||
T_PermissionChecker = _DependentCallable[bool]
|
||||
"""PermissionChecker 即判断事件是否满足权限的处理函数。
|
||||
|
||||
依赖参数:
|
||||
@@ -139,9 +152,9 @@ T_PermissionChecker = Callable[..., Union[bool, Awaitable[bool]]]
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
|
||||
T_Handler = Callable[..., Any]
|
||||
T_Handler = _DependentCallable[Any]
|
||||
"""Handler 处理函数。"""
|
||||
T_TypeUpdater = Callable[..., Union[str, Awaitable[str]]]
|
||||
T_TypeUpdater = _DependentCallable[str]
|
||||
"""TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。默认会更新为 `message`。
|
||||
|
||||
依赖参数:
|
||||
@@ -153,7 +166,7 @@ T_TypeUpdater = Callable[..., Union[str, Awaitable[str]]]
|
||||
- MatcherParam: Matcher 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_PermissionUpdater = Callable[..., Union["Permission", Awaitable["Permission"]]]
|
||||
T_PermissionUpdater = _DependentCallable["Permission"]
|
||||
"""PermissionUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新会话对象权限。默认会更新为当前事件的触发对象。
|
||||
|
||||
依赖参数:
|
||||
@@ -165,5 +178,5 @@ T_PermissionUpdater = Callable[..., Union["Permission", Awaitable["Permission"]]
|
||||
- MatcherParam: Matcher 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_DependencyCache = Dict[Callable[..., Any], "Task[Any]"]
|
||||
T_DependencyCache = Dict[_DependentCallable[Any], "Task[Any]"]
|
||||
"""依赖缓存, 用于存储依赖函数的返回值"""
|
||||
|
@@ -10,6 +10,7 @@ import json
|
||||
import asyncio
|
||||
import inspect
|
||||
import dataclasses
|
||||
from pathlib import Path
|
||||
from functools import wraps, partial
|
||||
from contextlib import asynccontextmanager
|
||||
from typing_extensions import ParamSpec, get_args, get_origin
|
||||
@@ -24,6 +25,7 @@ from typing import (
|
||||
Coroutine,
|
||||
AsyncGenerator,
|
||||
ContextManager,
|
||||
overload,
|
||||
)
|
||||
|
||||
from pydantic.typing import is_union, is_none_type
|
||||
@@ -129,11 +131,28 @@ async def run_sync_ctx_manager(
|
||||
await run_sync(cm.__exit__)(None, None, None)
|
||||
|
||||
|
||||
@overload
|
||||
async def run_coro_with_catch(
|
||||
coro: Coroutine[Any, Any, T],
|
||||
exc: Tuple[Type[Exception], ...],
|
||||
return_on_err: R = None,
|
||||
) -> Union[T, None]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
async def run_coro_with_catch(
|
||||
coro: Coroutine[Any, Any, T],
|
||||
exc: Tuple[Type[Exception], ...],
|
||||
return_on_err: R,
|
||||
) -> Union[T, R]:
|
||||
...
|
||||
|
||||
|
||||
async def run_coro_with_catch(
|
||||
coro: Coroutine[Any, Any, T],
|
||||
exc: Tuple[Type[Exception], ...],
|
||||
return_on_err: Optional[R] = None,
|
||||
) -> Optional[Union[T, R]]:
|
||||
try:
|
||||
return await coro
|
||||
except exc:
|
||||
@@ -147,6 +166,14 @@ def get_name(obj: Any) -> str:
|
||||
return obj.__class__.__name__
|
||||
|
||||
|
||||
def path_to_module_name(path: Path) -> str:
|
||||
rel_path = path.resolve().relative_to(Path(".").resolve())
|
||||
if rel_path.stem == "__init__":
|
||||
return ".".join(rel_path.parts[:-1])
|
||||
else:
|
||||
return ".".join(rel_path.parts[:-1] + (rel_path.stem,))
|
||||
|
||||
|
||||
class DataclassEncoder(json.JSONEncoder):
|
||||
"""在JSON序列化 {re}`nonebot.adapters._message.Message` (List[Dataclass]) 时使用的 `JSONEncoder`"""
|
||||
|
||||
@@ -173,7 +200,7 @@ def logger_wrapper(logger_name: str):
|
||||
|
||||
def log(level: str, message: str, exception: Optional[Exception] = None):
|
||||
logger.opt(colors=True, exception=exception).log(
|
||||
level, f"<m>{escape_tag(logger_name)}</m> | " + message
|
||||
level, f"<m>{escape_tag(logger_name)}</m> | {message}"
|
||||
)
|
||||
|
||||
return log
|
||||
|
@@ -17,7 +17,7 @@ _✨ NoneBot 本地文档插件 ✨_
|
||||
<a href="https://pypi.python.org/pypi/nonebot-plugin-docs">
|
||||
<img src="https://img.shields.io/pypi/v/nonebot-plugin-docs.svg" alt="pypi">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/python-3.7+-blue.svg" alt="python">
|
||||
<img src="https://img.shields.io/badge/python-3.8+-blue.svg" alt="python">
|
||||
</p>
|
||||
|
||||
## 使用方式
|
||||
|
@@ -12,7 +12,7 @@ include = ["nonebot_plugin_docs/dist/**/*"]
|
||||
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7.3"
|
||||
python = "^3.8"
|
||||
nonebot2 = "^2.0.0-beta.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
470
poetry.lock
generated
470
poetry.lock
generated
@@ -11,15 +11,15 @@ pycares = ">=4.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "aiofiles"
|
||||
version = "0.8.0"
|
||||
version = "22.1.0"
|
||||
description = "File support for asyncio."
|
||||
category = "main"
|
||||
optional = true
|
||||
python-versions = ">=3.6,<4.0"
|
||||
python-versions = ">=3.7,<4.0"
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
version = "3.8.1"
|
||||
version = "3.8.3"
|
||||
description = "Async http client/server framework (asyncio)"
|
||||
category = "main"
|
||||
optional = true
|
||||
@@ -29,14 +29,12 @@ python-versions = ">=3.6"
|
||||
aiodns = {version = "*", optional = true, markers = "extra == \"speedups\""}
|
||||
aiosignal = ">=1.1.2"
|
||||
async-timeout = ">=4.0.0a3,<5.0"
|
||||
asynctest = {version = "0.13.0", markers = "python_version < \"3.8\""}
|
||||
attrs = ">=17.3.0"
|
||||
Brotli = {version = "*", optional = true, markers = "extra == \"speedups\""}
|
||||
cchardet = {version = "*", optional = true, markers = "extra == \"speedups\""}
|
||||
cchardet = {version = "*", optional = true, markers = "python_version < \"3.10\" and extra == \"speedups\""}
|
||||
charset-normalizer = ">=2.0,<3.0"
|
||||
frozenlist = ">=1.1.1"
|
||||
multidict = ">=4.5,<7.0"
|
||||
typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
|
||||
yarl = ">=1.0,<2.0"
|
||||
|
||||
[package.extras]
|
||||
@@ -64,7 +62,6 @@ python-versions = ">=3.6.2"
|
||||
[package.dependencies]
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
|
||||
@@ -79,9 +76,6 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
|
||||
|
||||
@@ -105,25 +99,6 @@ category = "main"
|
||||
optional = true
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
|
||||
|
||||
[[package]]
|
||||
name = "asynctest"
|
||||
version = "0.13.0"
|
||||
description = "Enhance the standard unittest package with features for testing asyncio libraries"
|
||||
category = "main"
|
||||
optional = true
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "atomicwrites"
|
||||
version = "1.4.1"
|
||||
description = "Atomic file writes."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "21.4.0"
|
||||
@@ -140,7 +115,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "22.6.0"
|
||||
version = "22.8.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -152,7 +127,6 @@ mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
|
||||
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
|
||||
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
||||
|
||||
[package.extras]
|
||||
@@ -187,7 +161,7 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.6.15"
|
||||
version = "2022.9.24"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -214,7 +188,7 @@ python-versions = ">=3.6.1"
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "2.1.0"
|
||||
version = "2.1.1"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -233,7 +207,6 @@ python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
@@ -245,7 +218,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "6.4.2"
|
||||
version = "6.5.0"
|
||||
description = "Code coverage measurement for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -259,7 +232,7 @@ toml = ["tomli"]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
description = "Distribution utilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -278,7 +251,7 @@ testing = ["pre-commit"]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.79.0"
|
||||
version = "0.79.1"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -296,15 +269,15 @@ test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.91
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.7.1"
|
||||
version = "3.8.0"
|
||||
description = "A platform independent file lock."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
|
||||
docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"]
|
||||
testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "frozenlist"
|
||||
@@ -362,7 +335,7 @@ socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
description = "A collection of framework independent HTTP protocol utils."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -394,7 +367,7 @@ socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "hypercorn"
|
||||
version = "0.13.2"
|
||||
version = "0.14.3"
|
||||
description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn"
|
||||
category = "main"
|
||||
optional = true
|
||||
@@ -405,11 +378,11 @@ h11 = "*"
|
||||
h2 = ">=3.1.0"
|
||||
priority = "*"
|
||||
toml = "*"
|
||||
typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
|
||||
wsproto = ">=0.14.0"
|
||||
|
||||
[package.extras]
|
||||
h3 = ["aioquic (>=0.9.0,<1.0)"]
|
||||
docs = ["pydata-sphinx-theme"]
|
||||
trio = ["trio (>=0.11.0)"]
|
||||
uvloop = ["uvloop"]
|
||||
|
||||
@@ -423,7 +396,7 @@ python-versions = ">=3.6.1"
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.5.3"
|
||||
version = "2.5.5"
|
||||
description = "File identification library for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -434,29 +407,12 @@ license = ["ukkonen"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.3"
|
||||
version = "3.4"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "4.12.0"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
|
||||
perf = ["ipython"]
|
||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "1.1.1"
|
||||
@@ -592,7 +548,7 @@ resolved_reference = "3cb2474a22bd1a633805dfa6edbe741e44ddc851"
|
||||
|
||||
[[package]]
|
||||
name = "nonemoji"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
description = "Simple gitmoji cli written in python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -625,11 +581,11 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.9.0"
|
||||
version = "0.10.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
@@ -651,9 +607,6 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
@@ -669,7 +622,6 @@ python-versions = ">=3.7"
|
||||
[package.dependencies]
|
||||
cfgv = ">=2.0.0"
|
||||
identify = ">=1.0.0"
|
||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
nodeenv = ">=0.11.1"
|
||||
pyyaml = ">=5.1"
|
||||
toml = "*"
|
||||
@@ -685,7 +637,7 @@ python-versions = ">=3.6.1"
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.30"
|
||||
version = "3.0.31"
|
||||
description = "Library for building powerful interactive command lines in Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -704,7 +656,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pycares"
|
||||
version = "4.2.1"
|
||||
version = "4.2.2"
|
||||
description = "Python interface for c-ares"
|
||||
category = "main"
|
||||
optional = true
|
||||
@@ -726,7 +678,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.9.1"
|
||||
version = "1.9.2"
|
||||
description = "Data validation and settings management using python type hints"
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -761,17 +713,15 @@ diagrams = ["railroad-diagrams", "jinja2"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.1.2"
|
||||
version = "7.1.3"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
||||
attrs = ">=19.2.0"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
@@ -791,7 +741,6 @@ python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=6.1.0"
|
||||
typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
|
||||
@@ -843,11 +792,11 @@ testing = ["filelock"]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "0.20.0"
|
||||
version = "0.21.0"
|
||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
cli = ["click (>=5.0)"]
|
||||
@@ -873,12 +822,10 @@ aiofiles = "*"
|
||||
blinker = "*"
|
||||
click = "*"
|
||||
hypercorn = ">=0.11.2"
|
||||
importlib_metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
itsdangerous = "*"
|
||||
jinja2 = "*"
|
||||
markupsafe = "*"
|
||||
toml = "*"
|
||||
typing_extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||
werkzeug = ">=2.0.0"
|
||||
|
||||
[package.extras]
|
||||
@@ -926,11 +873,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
@@ -965,20 +912,12 @@ python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "tomlkit"
|
||||
version = "0.11.1"
|
||||
version = "0.11.5"
|
||||
description = "Style preserving TOML library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6,<4.0"
|
||||
|
||||
[[package]]
|
||||
name = "typed-ast"
|
||||
version = "1.5.4"
|
||||
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.3.0"
|
||||
@@ -989,7 +928,7 @@ python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.11"
|
||||
version = "1.26.12"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -997,12 +936,12 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*,
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
|
||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.18.2"
|
||||
version = "0.18.3"
|
||||
description = "The lightning-fast ASGI server."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -1014,49 +953,47 @@ colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win
|
||||
h11 = ">=0.8"
|
||||
httptools = {version = ">=0.4.0", optional = true, markers = "extra == \"standard\""}
|
||||
python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
|
||||
PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
|
||||
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||
pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
|
||||
uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
|
||||
watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
|
||||
websockets = {version = ">=10.0", optional = true, markers = "extra == \"standard\""}
|
||||
|
||||
[package.extras]
|
||||
standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchfiles (>=0.13)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"]
|
||||
standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "uvloop"
|
||||
version = "0.16.0"
|
||||
version = "0.17.0"
|
||||
description = "Fast implementation of asyncio event loop on top of libuv"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
|
||||
dev = ["Cython (>=0.29.32,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=22.0.0,<22.1.0)", "mypy (>=0.800)", "aiohttp"]
|
||||
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"]
|
||||
test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
|
||||
test = ["flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=22.0.0,<22.1.0)", "mypy (>=0.800)", "Cython (>=0.29.32,<0.30.0)", "aiohttp"]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.16.2"
|
||||
version = "20.16.5"
|
||||
description = "Virtual Python Environment builder"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
distlib = ">=0.3.1,<1"
|
||||
filelock = ">=3.2,<4"
|
||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
platformdirs = ">=2,<3"
|
||||
distlib = ">=0.3.5,<1"
|
||||
filelock = ">=3.4.1,<4"
|
||||
platformdirs = ">=2.4,<3"
|
||||
|
||||
[package.extras]
|
||||
docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
|
||||
testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"]
|
||||
docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"]
|
||||
testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "0.16.1"
|
||||
version = "0.17.0"
|
||||
description = "Simple, modern and high performance file watching and code reload in python."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -1083,7 +1020,7 @@ python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "2.2.1"
|
||||
version = "2.2.2"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
category = "main"
|
||||
optional = true
|
||||
@@ -1108,7 +1045,7 @@ dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"]
|
||||
|
||||
[[package]]
|
||||
name = "wsproto"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
description = "WebSockets state-machine based protocol implementation"
|
||||
category = "main"
|
||||
optional = true
|
||||
@@ -1128,19 +1065,6 @@ python-versions = ">=3.7"
|
||||
[package.dependencies]
|
||||
idna = ">=2.0"
|
||||
multidict = ">=4.0"
|
||||
typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.8.1"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"]
|
||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
|
||||
|
||||
[extras]
|
||||
aiohttp = ["aiohttp"]
|
||||
@@ -1151,92 +1075,16 @@ websockets = ["websockets"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7.3"
|
||||
content-hash = "8775c8091ff054a3fb807281e0cfebde4a0409d18970a1001a7a06fad595addf"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "3b21339de9113224b61700da359ea53ef866677d19c0bdbad7126e33ceceb856"
|
||||
|
||||
[metadata.files]
|
||||
aiodns = [
|
||||
{file = "aiodns-3.0.0-py3-none-any.whl", hash = "sha256:2b19bc5f97e5c936638d28e665923c093d8af2bf3aa88d35c43417fa25d136a2"},
|
||||
{file = "aiodns-3.0.0.tar.gz", hash = "sha256:946bdfabe743fceeeb093c8a010f5d1645f708a241be849e17edfb0e49e08cd6"},
|
||||
]
|
||||
aiofiles = [
|
||||
{file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"},
|
||||
{file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"},
|
||||
]
|
||||
aiohttp = [
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"},
|
||||
{file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"},
|
||||
{file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"},
|
||||
{file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"},
|
||||
{file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"},
|
||||
{file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"},
|
||||
{file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"},
|
||||
]
|
||||
aiofiles = []
|
||||
aiohttp = []
|
||||
aiosignal = [
|
||||
{file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"},
|
||||
{file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"},
|
||||
@@ -1256,11 +1104,6 @@ async-timeout = [
|
||||
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
|
||||
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
|
||||
]
|
||||
asynctest = [
|
||||
{file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"},
|
||||
{file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"},
|
||||
]
|
||||
atomicwrites = []
|
||||
attrs = [
|
||||
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
|
||||
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
||||
@@ -1362,10 +1205,7 @@ cchardet = [
|
||||
{file = "cchardet-2.1.7-cp39-cp39-win_amd64.whl", hash = "sha256:24974b3e40fee9e7557bb352be625c39ec6f50bc2053f44a3d1191db70b51675"},
|
||||
{file = "cchardet-2.1.7.tar.gz", hash = "sha256:c428b6336545053c2589f6caf24ea32276c6664cb86db817e03a94c60afa0eaf"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
|
||||
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
|
||||
]
|
||||
certifi = []
|
||||
cffi = [
|
||||
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
|
||||
@@ -1436,10 +1276,7 @@ cfgv = [
|
||||
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
|
||||
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
|
||||
]
|
||||
charset-normalizer = [
|
||||
{file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"},
|
||||
{file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"},
|
||||
]
|
||||
charset-normalizer = []
|
||||
click = [
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
@@ -1455,10 +1292,7 @@ execnet = [
|
||||
{file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"},
|
||||
]
|
||||
fastapi = []
|
||||
filelock = [
|
||||
{file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"},
|
||||
{file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"},
|
||||
]
|
||||
filelock = []
|
||||
frozenlist = []
|
||||
h11 = [
|
||||
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
|
||||
@@ -1476,60 +1310,18 @@ httpcore = [
|
||||
{file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"},
|
||||
{file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"},
|
||||
]
|
||||
httptools = [
|
||||
{file = "httptools-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcddfe70553be717d9745990dfdb194e22ee0f60eb8f48c0794e7bfeda30d2d5"},
|
||||
{file = "httptools-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1ee0b459257e222b878a6c09ccf233957d3a4dcb883b0847640af98d2d9aac23"},
|
||||
{file = "httptools-0.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceafd5e960b39c7e0d160a1936b68eb87c5e79b3979d66e774f0c77d4d8faaed"},
|
||||
{file = "httptools-0.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fdb9f9ed79bc6f46b021b3319184699ba1a22410a82204e6e89c774530069683"},
|
||||
{file = "httptools-0.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:abe829275cdd4174b4c4e65ad718715d449e308d59793bf3a931ee1bf7e7b86c"},
|
||||
{file = "httptools-0.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7af6bdbd21a2a25d6784f6d67f44f5df33ef39b6159543b9f9064d365c01f919"},
|
||||
{file = "httptools-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5d1fe6b6661022fd6cac541f54a4237496b246e6f1c0a6b41998ee08a1135afe"},
|
||||
{file = "httptools-0.4.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:48e48530d9b995a84d1d89ae6b3ec4e59ea7d494b150ac3bbc5e2ac4acce92cd"},
|
||||
{file = "httptools-0.4.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a113789e53ac1fa26edf99856a61e4c493868e125ae0dd6354cf518948fbbd5c"},
|
||||
{file = "httptools-0.4.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e2eb957787cbb614a0f006bfc5798ff1d90ac7c4dd24854c84edbdc8c02369e"},
|
||||
{file = "httptools-0.4.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:7ee9f226acab9085037582c059d66769862706e8e8cd2340470ceb8b3850873d"},
|
||||
{file = "httptools-0.4.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:701e66b59dd21a32a274771238025d58db7e2b6ecebbab64ceff51b8e31527ae"},
|
||||
{file = "httptools-0.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6a1a7dfc1f9c78a833e2c4904757a0f47ce25d08634dd2a52af394eefe5f9777"},
|
||||
{file = "httptools-0.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:903f739c9fb78dab8970b0f3ea51f21955b24b45afa77b22ff0e172fc11ef111"},
|
||||
{file = "httptools-0.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54bbd295f031b866b9799dd39cb45deee81aca036c9bff9f58ca06726f6494f1"},
|
||||
{file = "httptools-0.4.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3194f6d6443befa8d4db16c1946b2fc428a3ceb8ab32eb6f09a59f86104dc1a0"},
|
||||
{file = "httptools-0.4.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cd1295f52971097f757edfbfce827b6dbbfb0f7a74901ee7d4933dff5ad4c9af"},
|
||||
{file = "httptools-0.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:20a45bcf22452a10fa8d58b7dbdb474381f6946bf5b8933e3662d572bc61bae4"},
|
||||
{file = "httptools-0.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d1f27bb0f75bef722d6e22dc609612bfa2f994541621cd2163f8c943b6463dfe"},
|
||||
{file = "httptools-0.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7f7bfb74718f52d5ed47d608d507bf66d3bc01d4a8b3e6dd7134daaae129357b"},
|
||||
{file = "httptools-0.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a522d12e2ddbc2e91842ffb454a1aeb0d47607972c7d8fc88bd0838d97fb8a2a"},
|
||||
{file = "httptools-0.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2db44a0b294d317199e9f80123e72c6b005c55b625b57fae36de68670090fa48"},
|
||||
{file = "httptools-0.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c286985b5e194ca0ebb2908d71464b9be8f17cc66d6d3e330e8d5407248f56ad"},
|
||||
{file = "httptools-0.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3a4e165ca6204f34856b765d515d558dc84f1352033b8721e8d06c3e44930c3"},
|
||||
{file = "httptools-0.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:72aa3fbe636b16d22e04b5a9d24711b043495e0ecfe58080addf23a1a37f3409"},
|
||||
{file = "httptools-0.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9967d9758df505975913304c434cb9ab21e2c609ad859eb921f2f615a038c8de"},
|
||||
{file = "httptools-0.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f72b5d24d6730035128b238decdc4c0f2104b7056a7ca55cf047c106842ec890"},
|
||||
{file = "httptools-0.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:29bf97a5c532da9c7a04de2c7a9c31d1d54f3abd65a464119b680206bbbb1055"},
|
||||
{file = "httptools-0.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98993805f1e3cdb53de4eed02b55dcc953cdf017ba7bbb2fd89226c086a6d855"},
|
||||
{file = "httptools-0.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d9b90bf58f3ba04e60321a23a8723a1ff2a9377502535e70495e5ada8e6e6722"},
|
||||
{file = "httptools-0.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a99346ebcb801b213c591540837340bdf6fd060a8687518d01c607d338b7424"},
|
||||
{file = "httptools-0.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:645373c070080e632480a3d251d892cb795be3d3a15f86975d0f1aca56fd230d"},
|
||||
{file = "httptools-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:34d2903dd2a3dd85d33705b6fde40bf91fc44411661283763fd0746723963c83"},
|
||||
{file = "httptools-0.4.0.tar.gz", hash = "sha256:2c9a930c378b3d15d6b695fb95ebcff81a7395b4f9775c4f10a076beb0b2c1ff"},
|
||||
]
|
||||
httptools = []
|
||||
httpx = [
|
||||
{file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"},
|
||||
{file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
|
||||
]
|
||||
hypercorn = [
|
||||
{file = "Hypercorn-0.13.2-py3-none-any.whl", hash = "sha256:ca18f91ab3fa823cbe9e949738f9f2cc07027cd647c80d8f93e4b1a2a175f112"},
|
||||
{file = "Hypercorn-0.13.2.tar.gz", hash = "sha256:6307be5cbdf6ba411967d4661202dc4f79bd511b5d318bc4eed88b09418427f8"},
|
||||
]
|
||||
hypercorn = []
|
||||
hyperframe = [
|
||||
{file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"},
|
||||
{file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"},
|
||||
]
|
||||
identify = []
|
||||
idna = [
|
||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
||||
]
|
||||
importlib-metadata = []
|
||||
idna = []
|
||||
iniconfig = [
|
||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
||||
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
|
||||
@@ -1669,10 +1461,7 @@ packaging = [
|
||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||
]
|
||||
pathspec = [
|
||||
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
pathspec = []
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
||||
@@ -1686,97 +1475,23 @@ priority = [
|
||||
{file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"},
|
||||
{file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"},
|
||||
]
|
||||
prompt-toolkit = [
|
||||
{file = "prompt_toolkit-3.0.30-py3-none-any.whl", hash = "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289"},
|
||||
{file = "prompt_toolkit-3.0.30.tar.gz", hash = "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0"},
|
||||
]
|
||||
prompt-toolkit = []
|
||||
py = [
|
||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
||||
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
|
||||
]
|
||||
pycares = [
|
||||
{file = "pycares-4.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d83f193563b42360528167705b1c7bb91e2a09f990b98e3d6378835b72cd5c96"},
|
||||
{file = "pycares-4.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b03f69df69f0ab3bfb8dbe54444afddff6ff9389561a08aade96b4f91207a655"},
|
||||
{file = "pycares-4.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3b78bdee2f2f1351d5fccc2d1b667aea2d15a55d74d52cb9fd5bea8b5e74c4dc"},
|
||||
{file = "pycares-4.2.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f05223de13467bb26f9a1594a1799ce2d08ad8ea241489fecd9d8ed3bbbfc672"},
|
||||
{file = "pycares-4.2.1-cp310-cp310-win32.whl", hash = "sha256:1f37f762414680063b4dfec5be809a84f74cd8e203d939aaf3ba9c807a9e7013"},
|
||||
{file = "pycares-4.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:1a9506d496efeb809a1b63647cb2f3f33c67fcf62bf80a2359af692fef2c1755"},
|
||||
{file = "pycares-4.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2fd53eb5b441c4f6f9c78d7900e05883e9998b34a14b804be4fc4c6f9fea89f3"},
|
||||
{file = "pycares-4.2.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:061dd4c80fec73feb150455b159704cd51a122f20d36790033bd6375d4198579"},
|
||||
{file = "pycares-4.2.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a521d7f54f3e52ded4d34c306ba05cfe9eb5aaa2e5aaf83c96564b9369495588"},
|
||||
{file = "pycares-4.2.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:99e00e397d07a79c9f43e4303e67f4f97bcabd013bda0d8f2d430509b7aef8a0"},
|
||||
{file = "pycares-4.2.1-cp36-cp36m-win32.whl", hash = "sha256:d9cd826d8e0c270059450709bff994bfeb072f79d82fd3f11c701690ff65d0e7"},
|
||||
{file = "pycares-4.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f8e6942965465ca98e212376c4afb9aec501d8129054929744b2f4a487c8c14b"},
|
||||
{file = "pycares-4.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e75cbd4d3b3d9b02bba6e170846e39893a825e7a5fb1b96728fc6d7b964f8945"},
|
||||
{file = "pycares-4.2.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2e8ec4c8e07c986b70a3cc8f5b297c53b08ac755e5b9797512002a466e2de86"},
|
||||
{file = "pycares-4.2.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5333b51ef4ff3e8973b4a1b57cad5ada13e15552445ee3cd74bd77407dec9d44"},
|
||||
{file = "pycares-4.2.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2113529004df4894783eaa61e9abc3a680756b6f033d942f2800301ae8c71c29"},
|
||||
{file = "pycares-4.2.1-cp37-cp37m-win32.whl", hash = "sha256:e7a95763cdc20cf9ec357066e656ea30b8de6b03de6175cbb50890e22aa01868"},
|
||||
{file = "pycares-4.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a901776163a04de5d67c42bd63a287cff9cb05fc041668ad1681fe3daa36445"},
|
||||
{file = "pycares-4.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:66b5390a4885a578e687d3f2683689c35e1d4573f4d0ecf217431f7bb55c49a0"},
|
||||
{file = "pycares-4.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15dd5cf21bc73ad539e8aabf7afe370d1df8af7bc6944cd7298f3bfef0c1a27c"},
|
||||
{file = "pycares-4.2.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4ee625d7571039038bca51ae049b047cbfcfc024b302aae6cc53d5d9aa8648a8"},
|
||||
{file = "pycares-4.2.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:396ee487178e9de06ca4122a35a157474db3ce0a0db6038a31c831ebb9863315"},
|
||||
{file = "pycares-4.2.1-cp38-cp38-win32.whl", hash = "sha256:e4dc37f732f7110ca6368e0128cbbd0a54f5211515a061b2add64da2ddb8e5ca"},
|
||||
{file = "pycares-4.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:3636fccf643c5192c34ee0183c514a2d09419e3a76ca2717cef626638027cb21"},
|
||||
{file = "pycares-4.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6724573e830ea2345f4bcf0f968af64cc6d491dc2133e9c617f603445dcdfa58"},
|
||||
{file = "pycares-4.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dbfcacbde6c21380c412c13d53ea44b257dea3f7b9d80be2c873bb20e21fee"},
|
||||
{file = "pycares-4.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8a46839da642b281ac5f56d3c6336528e128b3c41eab9c5330d250f22325e9d"},
|
||||
{file = "pycares-4.2.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9b05c2cec644a6c66b55bcf6c24d4dfdaf2f7205b16e5c4ceee31db104fac958"},
|
||||
{file = "pycares-4.2.1-cp39-cp39-win32.whl", hash = "sha256:8bd6ed3ad3a5358a635c1acf5d0f46be9afb095772b84427ff22283d2f31db1b"},
|
||||
{file = "pycares-4.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:fbd53728d798d07811898e11991e22209229c090eab265a53d12270b95d70d1a"},
|
||||
{file = "pycares-4.2.1.tar.gz", hash = "sha256:735b4f75fd0f595c4e9184da18cd87737f46bc81a64ea41f4edce2b6b68d46d2"},
|
||||
]
|
||||
pycares = []
|
||||
pycparser = [
|
||||
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
|
||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||
]
|
||||
pydantic = [
|
||||
{file = "pydantic-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8098a724c2784bf03e8070993f6d46aa2eeca031f8d8a048dff277703e6e193"},
|
||||
{file = "pydantic-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c320c64dd876e45254bdd350f0179da737463eea41c43bacbee9d8c9d1021f11"},
|
||||
{file = "pydantic-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18f3e912f9ad1bdec27fb06b8198a2ccc32f201e24174cec1b3424dda605a310"},
|
||||
{file = "pydantic-1.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11951b404e08b01b151222a1cb1a9f0a860a8153ce8334149ab9199cd198131"},
|
||||
{file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8bc541a405423ce0e51c19f637050acdbdf8feca34150e0d17f675e72d119580"},
|
||||
{file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e565a785233c2d03724c4dc55464559639b1ba9ecf091288dd47ad9c629433bd"},
|
||||
{file = "pydantic-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a4a88dcd6ff8fd47c18b3a3709a89adb39a6373f4482e04c1b765045c7e282fd"},
|
||||
{file = "pydantic-1.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:447d5521575f18e18240906beadc58551e97ec98142266e521c34968c76c8761"},
|
||||
{file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:985ceb5d0a86fcaa61e45781e567a59baa0da292d5ed2e490d612d0de5796918"},
|
||||
{file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059b6c1795170809103a1538255883e1983e5b831faea6558ef873d4955b4a74"},
|
||||
{file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d12f96b5b64bec3f43c8e82b4aab7599d0157f11c798c9f9c528a72b9e0b339a"},
|
||||
{file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ae72f8098acb368d877b210ebe02ba12585e77bd0db78ac04a1ee9b9f5dd2166"},
|
||||
{file = "pydantic-1.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:79b485767c13788ee314669008d01f9ef3bc05db9ea3298f6a50d3ef596a154b"},
|
||||
{file = "pydantic-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:494f7c8537f0c02b740c229af4cb47c0d39840b829ecdcfc93d91dcbb0779892"},
|
||||
{file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0f047e11febe5c3198ed346b507e1d010330d56ad615a7e0a89fae604065a0e"},
|
||||
{file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:969dd06110cb780da01336b281f53e2e7eb3a482831df441fb65dd30403f4608"},
|
||||
{file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:177071dfc0df6248fd22b43036f936cfe2508077a72af0933d0c1fa269b18537"},
|
||||
{file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9bcf8b6e011be08fb729d110f3e22e654a50f8a826b0575c7196616780683380"},
|
||||
{file = "pydantic-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a955260d47f03df08acf45689bd163ed9df82c0e0124beb4251b1290fa7ae728"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9ce157d979f742a915b75f792dbd6aa63b8eccaf46a1005ba03aa8a986bde34a"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0bf07cab5b279859c253d26a9194a8906e6f4a210063b84b433cf90a569de0c1"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d93d4e95eacd313d2c765ebe40d49ca9dd2ed90e5b37d0d421c597af830c195"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1542636a39c4892c4f4fa6270696902acb186a9aaeac6f6cf92ce6ae2e88564b"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a9af62e9b5b9bc67b2a195ebc2c2662fdf498a822d62f902bf27cccb52dbbf49"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fe4670cb32ea98ffbf5a1262f14c3e102cccd92b1869df3bb09538158ba90fe6"},
|
||||
{file = "pydantic-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:9f659a5ee95c8baa2436d392267988fd0f43eb774e5eb8739252e5a7e9cf07e0"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b83ba3825bc91dfa989d4eed76865e71aea3a6ca1388b59fc801ee04c4d8d0d6"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1dd8fecbad028cd89d04a46688d2fcc14423e8a196d5b0a5c65105664901f810"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02eefd7087268b711a3ff4db528e9916ac9aa18616da7bca69c1871d0b7a091f"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb57ba90929bac0b6cc2af2373893d80ac559adda6933e562dcfb375029acee"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4ce9ae9e91f46c344bec3b03d6ee9612802682c1551aaf627ad24045ce090761"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72ccb318bf0c9ab97fc04c10c37683d9eea952ed526707fabf9ac5ae59b701fd"},
|
||||
{file = "pydantic-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:61b6760b08b7c395975d893e0b814a11cf011ebb24f7d869e7118f5a339a82e1"},
|
||||
{file = "pydantic-1.9.1-py3-none-any.whl", hash = "sha256:4988c0f13c42bfa9ddd2fe2f569c9d54646ce84adc5de84228cfe83396f3bd58"},
|
||||
{file = "pydantic-1.9.1.tar.gz", hash = "sha256:1ed987c3ff29fff7fd8c3ea3a3ea877ad310aae2ef9889a119e22d3f2db0691a"},
|
||||
]
|
||||
pydantic = []
|
||||
pygtrie = []
|
||||
pyparsing = [
|
||||
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
||||
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
|
||||
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
|
||||
]
|
||||
pytest = []
|
||||
pytest-asyncio = []
|
||||
pytest-cov = [
|
||||
{file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
|
||||
@@ -1790,10 +1505,7 @@ pytest-xdist = [
|
||||
{file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"},
|
||||
{file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"},
|
||||
]
|
||||
python-dotenv = [
|
||||
{file = "python-dotenv-0.20.0.tar.gz", hash = "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f"},
|
||||
{file = "python_dotenv-0.20.0-py3-none-any.whl", hash = "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"},
|
||||
]
|
||||
python-dotenv = []
|
||||
pyyaml = [
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
|
||||
@@ -1845,10 +1557,7 @@ six = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
sniffio = [
|
||||
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
|
||||
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
|
||||
]
|
||||
sniffio = []
|
||||
starlette = [
|
||||
{file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"},
|
||||
{file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"},
|
||||
@@ -1862,56 +1571,13 @@ tomli = [
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
tomlkit = []
|
||||
typed-ast = [
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
|
||||
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
|
||||
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
|
||||
]
|
||||
urllib3 = []
|
||||
uvicorn = []
|
||||
uvloop = [
|
||||
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"},
|
||||
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c"},
|
||||
{file = "uvloop-0.16.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bd53f7f5db562f37cd64a3af5012df8cac2c464c97e732ed556800129505bd64"},
|
||||
{file = "uvloop-0.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:772206116b9b57cd625c8a88f2413df2fcfd0b496eb188b82a43bed7af2c2ec9"},
|
||||
{file = "uvloop-0.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b572256409f194521a9895aef274cea88731d14732343da3ecdb175228881638"},
|
||||
{file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04ff57aa137230d8cc968f03481176041ae789308b4d5079118331ab01112450"},
|
||||
{file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a19828c4f15687675ea912cc28bbcb48e9bb907c801873bd1519b96b04fb805"},
|
||||
{file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e814ac2c6f9daf4c36eb8e85266859f42174a4ff0d71b99405ed559257750382"},
|
||||
{file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bd8f42ea1ea8f4e84d265769089964ddda95eb2bb38b5cbe26712b0616c3edee"},
|
||||
{file = "uvloop-0.16.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:647e481940379eebd314c00440314c81ea547aa636056f554d491e40503c8464"},
|
||||
{file = "uvloop-0.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e0d26fa5875d43ddbb0d9d79a447d2ace4180d9e3239788208527c4784f7cab"},
|
||||
{file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ccd57ae8db17d677e9e06192e9c9ec4bd2066b77790f9aa7dede2cc4008ee8f"},
|
||||
{file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:089b4834fd299d82d83a25e3335372f12117a7d38525217c2258e9b9f4578897"},
|
||||
{file = "uvloop-0.16.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98d117332cc9e5ea8dfdc2b28b0a23f60370d02e1395f88f40d1effd2cb86c4f"},
|
||||
{file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"},
|
||||
{file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"},
|
||||
]
|
||||
uvloop = []
|
||||
virtualenv = []
|
||||
watchfiles = []
|
||||
wcwidth = [
|
||||
@@ -1973,9 +1639,5 @@ win32-setctime = [
|
||||
{file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
|
||||
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
|
||||
]
|
||||
wsproto = [
|
||||
{file = "wsproto-1.1.0-py3-none-any.whl", hash = "sha256:2218cb57952d90b9fca325c0dcfb08c3bda93e8fd8070b0a17f048e2e47a521b"},
|
||||
{file = "wsproto-1.1.0.tar.gz", hash = "sha256:a2e56bfd5c7cd83c1369d83b5feccd6d37798b74872866e62616e0ecf111bda8"},
|
||||
]
|
||||
wsproto = []
|
||||
yarl = []
|
||||
zipp = []
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "nonebot2"
|
||||
version = "2.0.0-beta.5"
|
||||
version = "2.0.0-rc.1"
|
||||
description = "An asynchronous python bot framework."
|
||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||
license = "MIT"
|
||||
@@ -22,7 +22,7 @@ packages = [
|
||||
include = ["nonebot/py.typed"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7.3"
|
||||
python = "^3.8"
|
||||
yarl = "^1.7.2"
|
||||
loguru = "^0.6.0"
|
||||
pygtrie = "^2.4.1"
|
||||
|
@@ -1,6 +1,7 @@
|
||||
[report]
|
||||
exclude_lines =
|
||||
def __repr__
|
||||
def __str__
|
||||
pragma: no cover
|
||||
if TYPE_CHECKING:
|
||||
@(abc\.)?abstractmethod
|
||||
|
1
tests/plugins.invalid.json
Normal file
1
tests/plugins.invalid.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
2
tests/plugins.invalid.toml
Normal file
2
tests/plugins.invalid.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[tool]
|
||||
nonebot = []
|
4
tests/plugins.json
Normal file
4
tests/plugins.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"plugins": [],
|
||||
"plugin_dirs": ["plugins"]
|
||||
}
|
3
tests/plugins.toml
Normal file
3
tests/plugins.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[tool.nonebot]
|
||||
plugins = []
|
||||
plugin_dirs = ["plugins"]
|
@@ -1,6 +1,2 @@
|
||||
from nonebot import export
|
||||
|
||||
|
||||
@export()
|
||||
def test():
|
||||
return "export"
|
||||
|
@@ -3,5 +3,7 @@ from datetime import datetime, timedelta
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
test_temp_matcher = Matcher.new("test", temp=True)
|
||||
test_datetime_matcher = Matcher.new("test", expire_time=datetime.now())
|
||||
test_datetime_matcher = Matcher.new(
|
||||
"test", expire_time=datetime.now() - timedelta(seconds=1)
|
||||
)
|
||||
test_timedelta_matcher = Matcher.new("test", expire_time=timedelta(seconds=-1))
|
||||
|
@@ -1,10 +1,14 @@
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.permission import Permission
|
||||
from nonebot.permission import USER, Permission
|
||||
|
||||
default_permission = Permission()
|
||||
|
||||
test_permission_updater = Matcher.new(permission=default_permission)
|
||||
|
||||
test_user_permission_updater = Matcher.new(
|
||||
permission=USER("test", perm=default_permission)
|
||||
)
|
||||
|
||||
test_custom_updater = Matcher.new(permission=default_permission)
|
||||
|
||||
|
||||
|
1
tests/plugins/plugin/__init__.py
Normal file
1
tests/plugins/plugin/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import matchers
|
243
tests/plugins/plugin/matchers.py
Normal file
243
tests/plugins/plugin/matchers.py
Normal file
@@ -0,0 +1,243 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot import (
|
||||
CommandGroup,
|
||||
MatcherGroup,
|
||||
on,
|
||||
on_type,
|
||||
on_regex,
|
||||
on_notice,
|
||||
on_command,
|
||||
on_keyword,
|
||||
on_message,
|
||||
on_request,
|
||||
on_endswith,
|
||||
on_fullmatch,
|
||||
on_metaevent,
|
||||
on_startswith,
|
||||
on_shell_command,
|
||||
)
|
||||
|
||||
|
||||
async def rule() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def permission() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def handler():
|
||||
return
|
||||
|
||||
|
||||
expire_time = datetime.now(timezone.utc)
|
||||
priority = 100
|
||||
state = {"test": "test"}
|
||||
|
||||
|
||||
matcher_on = on(
|
||||
"test",
|
||||
rule=rule,
|
||||
permission=permission,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
matcher_on_metaevent = on_metaevent(
|
||||
rule=rule,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
matcher_on_message = on_message(
|
||||
rule=rule,
|
||||
permission=permission,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
matcher_on_notice = on_notice(
|
||||
rule=rule,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
matcher_on_request = on_request(
|
||||
rule=rule,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
matcher_on_startswith = on_startswith(
|
||||
"test",
|
||||
rule=rule,
|
||||
permission=permission,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
matcher_on_endswith = on_endswith(
|
||||
"test",
|
||||
rule=rule,
|
||||
permission=permission,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
matcher_on_fullmatch = on_fullmatch(
|
||||
"test",
|
||||
rule=rule,
|
||||
permission=permission,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
matcher_on_keyword = on_keyword(
|
||||
{"test"},
|
||||
rule=rule,
|
||||
permission=permission,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
matcher_on_command = on_command(
|
||||
"test",
|
||||
rule=rule,
|
||||
permission=permission,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
matcher_on_shell_command = on_shell_command(
|
||||
"test",
|
||||
rule=rule,
|
||||
permission=permission,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
matcher_on_regex = on_regex(
|
||||
"test",
|
||||
rule=rule,
|
||||
permission=permission,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
class TestEvent(Event):
|
||||
...
|
||||
|
||||
|
||||
matcher_on_type = on_type(
|
||||
TestEvent,
|
||||
rule=rule,
|
||||
permission=permission,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
||||
cmd_group = CommandGroup(
|
||||
"test",
|
||||
rule=rule,
|
||||
permission=permission,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
matcher_sub_cmd = cmd_group.command("sub")
|
||||
matcher_sub_shell_cmd = cmd_group.shell_command("sub")
|
||||
|
||||
|
||||
matcher_group = MatcherGroup(
|
||||
rule=rule,
|
||||
permission=permission,
|
||||
handlers=[handler],
|
||||
temp=True,
|
||||
expire_time=expire_time,
|
||||
priority=priority,
|
||||
block=True,
|
||||
state=state,
|
||||
)
|
||||
matcher_group_on = matcher_group.on(type="test")
|
||||
matcher_group_on_metaevent = matcher_group.on_metaevent()
|
||||
matcher_group_on_message = matcher_group.on_message()
|
||||
matcher_group_on_notice = matcher_group.on_notice()
|
||||
matcher_group_on_request = matcher_group.on_request()
|
||||
matcher_group_on_startswith = matcher_group.on_startswith("test")
|
||||
matcher_group_on_endswith = matcher_group.on_endswith("test")
|
||||
matcher_group_on_fullmatch = matcher_group.on_fullmatch("test")
|
||||
matcher_group_on_keyword = matcher_group.on_keyword({"test"})
|
||||
matcher_group_on_command = matcher_group.on_command("test")
|
||||
matcher_group_on_shell_command = matcher_group.on_shell_command("test")
|
||||
matcher_group_on_regex = matcher_group.on_regex("test")
|
||||
matcher_group_on_type = matcher_group.on_type(TestEvent)
|
@@ -11,11 +11,11 @@ def test_template_basis():
|
||||
|
||||
def test_template_message():
|
||||
Message = make_fake_message()
|
||||
template = Message.template("{a:custom}{b:text}{c:image}")
|
||||
template = Message.template("{a:custom}{b:text}{c:image}/{d}")
|
||||
|
||||
@template.add_format_spec
|
||||
def custom(input: str) -> str:
|
||||
return input + "-custom!"
|
||||
return f"{input}-custom!"
|
||||
|
||||
try:
|
||||
template.add_format_spec(custom)
|
||||
@@ -24,12 +24,17 @@ def test_template_message():
|
||||
else:
|
||||
raise AssertionError("Should raise ValueError")
|
||||
|
||||
format_args = {"a": "custom", "b": "text", "c": "https://example.com/test"}
|
||||
format_args = {
|
||||
"a": "custom",
|
||||
"b": "text",
|
||||
"c": "https://example.com/test",
|
||||
"d": 114,
|
||||
}
|
||||
formatted = template.format(**format_args)
|
||||
|
||||
assert template.format_map(format_args) == formatted
|
||||
assert formatted.extract_plain_text() == "custom-custom!text"
|
||||
assert str(formatted) == "custom-custom!text[fake:image]"
|
||||
assert formatted.extract_plain_text() == "custom-custom!text/114"
|
||||
assert str(formatted) == "custom-custom!text[fake:image]/114"
|
||||
|
||||
|
||||
def test_rich_template_message():
|
||||
|
@@ -78,3 +78,27 @@ async def test_reverse_driver(app: App):
|
||||
assert await ws.receive_bytes() == b"pong"
|
||||
|
||||
await ws.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"nonebug_init, driver_type",
|
||||
[
|
||||
pytest.param(
|
||||
{"driver": "nonebot.drivers.fastapi:Driver+nonebot.drivers.aiohttp:Mixin"},
|
||||
"fastapi+aiohttp",
|
||||
id="fastapi+aiohttp",
|
||||
),
|
||||
pytest.param(
|
||||
{"driver": "~httpx:Driver+~websockets"},
|
||||
"block_driver+httpx+websockets",
|
||||
id="httpx+websockets",
|
||||
),
|
||||
],
|
||||
indirect=["nonebug_init"],
|
||||
)
|
||||
async def test_combine_driver(app: App, driver_type: str):
|
||||
import nonebot
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
assert driver.type == driver_type
|
||||
|
@@ -61,7 +61,7 @@ async def test_get(monkeypatch: pytest.MonkeyPatch, nonebug_clear):
|
||||
with pytest.raises(ValueError):
|
||||
get_bot()
|
||||
|
||||
monkeypatch.setattr(driver, "_clients", {"test": "test"})
|
||||
monkeypatch.setattr(driver, "_bots", {"test": "test"})
|
||||
assert get_bot() == "test"
|
||||
assert get_bot("test") == "test"
|
||||
assert get_bots() == {"test": "test"}
|
||||
|
@@ -104,6 +104,7 @@ async def test_permission_updater(app: App, load_plugin):
|
||||
default_permission,
|
||||
test_custom_updater,
|
||||
test_permission_updater,
|
||||
test_user_permission_updater,
|
||||
)
|
||||
|
||||
event = make_fake_event(_session_id="test")()
|
||||
@@ -119,6 +120,19 @@ async def test_permission_updater(app: App, load_plugin):
|
||||
assert checker.users == ("test",)
|
||||
assert checker.perm is default_permission
|
||||
|
||||
user_permission = list(test_user_permission_updater.permission.checkers)[0].call
|
||||
assert isinstance(user_permission, User)
|
||||
assert user_permission.perm is default_permission
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
matcher = test_user_permission_updater()
|
||||
new_perm = await matcher.update_permission(bot, event)
|
||||
assert len(new_perm.checkers) == 1
|
||||
checker = list(new_perm.checkers)[0].call
|
||||
assert isinstance(checker, User)
|
||||
assert checker.users == ("test",)
|
||||
assert checker.perm is default_permission
|
||||
|
||||
assert test_custom_updater.permission is default_permission
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
|
@@ -1,3 +1,5 @@
|
||||
from typing import Tuple, Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
@@ -142,10 +144,11 @@ async def test_metaevent(
|
||||
("message", "test", True),
|
||||
("message", "foo", False),
|
||||
("message", "faketest", True),
|
||||
("notice", "test", False),
|
||||
("message", None, False),
|
||||
("notice", "test", True),
|
||||
],
|
||||
)
|
||||
async def test_startswith(
|
||||
async def test_superuser(
|
||||
app: App,
|
||||
type: str,
|
||||
user_id: str,
|
||||
@@ -163,3 +166,29 @@ async def test_startswith(
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
assert await dependent(bot=bot, event=event) == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"session_ids,session_id,expected",
|
||||
[
|
||||
(("user", "foo"), "user", True),
|
||||
(("user", "foo"), "bar", False),
|
||||
(("user", "foo"), None, False),
|
||||
],
|
||||
)
|
||||
async def test_user(
|
||||
app: App, session_ids: Tuple[str, ...], session_id: Optional[str], expected: bool
|
||||
):
|
||||
from nonebot.permission import USER, User
|
||||
|
||||
dependent = list(USER(*session_ids).checkers)[0]
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, User)
|
||||
|
||||
event = make_fake_event(_session_id=session_id)()
|
||||
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
assert await dependent(bot=bot, event=event) == expected
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from dataclasses import asdict
|
||||
from typing import TYPE_CHECKING, Set
|
||||
|
||||
@@ -10,7 +11,21 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_plugin(app: App, load_plugin: Set["Plugin"]):
|
||||
async def test_load_plugin(app: App):
|
||||
import nonebot
|
||||
|
||||
# check regular
|
||||
assert nonebot.load_plugin("plugins.metadata")
|
||||
|
||||
# check path
|
||||
assert nonebot.load_plugin(Path("plugins/export"))
|
||||
|
||||
# check not found
|
||||
assert nonebot.load_plugin("some_plugin_not_exist") is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_plugins(app: App, load_plugin: Set["Plugin"]):
|
||||
import nonebot
|
||||
from nonebot.plugin import PluginManager
|
||||
|
||||
@@ -34,9 +49,6 @@ async def test_load_plugin(app: App, load_plugin: Set["Plugin"]):
|
||||
with pytest.raises(RuntimeError):
|
||||
PluginManager(search_path=["plugins"]).load_all_plugins()
|
||||
|
||||
# check not found
|
||||
assert nonebot.load_plugin("some_plugin_not_exist") is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_nested_plugin(app: App, load_plugin: Set["Plugin"]):
|
||||
@@ -51,6 +63,29 @@ async def test_load_nested_plugin(app: App, load_plugin: Set["Plugin"]):
|
||||
assert parent_plugin.sub_plugins == {sub_plugin, sub_plugin2}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_json(app: App):
|
||||
import nonebot
|
||||
|
||||
nonebot.load_from_json("./plugins.json")
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
nonebot.load_from_json("./plugins.invalid.json")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_toml(app: App):
|
||||
import nonebot
|
||||
|
||||
nonebot.load_from_toml("./plugins.toml")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
nonebot.load_from_toml("./plugins.empty.toml")
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
nonebot.load_from_toml("./plugins.invalid.toml")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bad_plugin(app: App):
|
||||
import nonebot
|
||||
|
116
tests/test_plugin/test_on.py
Normal file
116
tests/test_plugin/test_on.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from typing import Type, Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_on(app: App, load_plugin):
|
||||
import nonebot
|
||||
import plugins.plugin.matchers as module
|
||||
from nonebot.typing import T_RuleChecker
|
||||
from nonebot.matcher import Matcher, matchers
|
||||
from nonebot.rule import (
|
||||
RegexRule,
|
||||
IsTypeRule,
|
||||
CommandRule,
|
||||
EndswithRule,
|
||||
KeywordsRule,
|
||||
FullmatchRule,
|
||||
StartswithRule,
|
||||
ShellCommandRule,
|
||||
)
|
||||
from plugins.plugin.matchers import (
|
||||
TestEvent,
|
||||
rule,
|
||||
state,
|
||||
handler,
|
||||
priority,
|
||||
matcher_on,
|
||||
permission,
|
||||
expire_time,
|
||||
matcher_on_type,
|
||||
matcher_sub_cmd,
|
||||
matcher_group_on,
|
||||
matcher_on_regex,
|
||||
matcher_on_notice,
|
||||
matcher_on_command,
|
||||
matcher_on_keyword,
|
||||
matcher_on_message,
|
||||
matcher_on_request,
|
||||
matcher_on_endswith,
|
||||
matcher_on_fullmatch,
|
||||
matcher_on_metaevent,
|
||||
matcher_group_on_type,
|
||||
matcher_on_startswith,
|
||||
matcher_sub_shell_cmd,
|
||||
matcher_group_on_regex,
|
||||
matcher_group_on_notice,
|
||||
matcher_group_on_command,
|
||||
matcher_group_on_keyword,
|
||||
matcher_group_on_message,
|
||||
matcher_group_on_request,
|
||||
matcher_on_shell_command,
|
||||
matcher_group_on_endswith,
|
||||
matcher_group_on_fullmatch,
|
||||
matcher_group_on_metaevent,
|
||||
matcher_group_on_startswith,
|
||||
matcher_group_on_shell_command,
|
||||
)
|
||||
|
||||
plugin = nonebot.get_plugin("plugin")
|
||||
|
||||
def _check(
|
||||
matcher: Type[Matcher],
|
||||
pre_rule: Optional[T_RuleChecker],
|
||||
has_permission: bool,
|
||||
):
|
||||
assert {dependent.call for dependent in matcher.rule.checkers} == (
|
||||
{pre_rule, rule} if pre_rule else {rule}
|
||||
)
|
||||
if has_permission:
|
||||
assert {dependent.call for dependent in matcher.permission.checkers} == {
|
||||
permission
|
||||
}
|
||||
else:
|
||||
assert not matcher.permission.checkers
|
||||
assert [dependent.call for dependent in matcher.handlers] == [handler]
|
||||
assert matcher.temp is True
|
||||
assert matcher.expire_time == expire_time
|
||||
assert matcher in matchers[priority]
|
||||
assert matcher.block is True
|
||||
assert matcher._default_state == state
|
||||
|
||||
assert matcher.plugin is plugin
|
||||
assert matcher.module is module
|
||||
assert matcher.plugin_name == "plugin"
|
||||
assert matcher.module_name == "plugins.plugin.matchers"
|
||||
|
||||
_check(matcher_on, None, True)
|
||||
_check(matcher_on_metaevent, None, False)
|
||||
_check(matcher_on_message, None, True)
|
||||
_check(matcher_on_notice, None, False)
|
||||
_check(matcher_on_request, None, False)
|
||||
_check(matcher_on_startswith, StartswithRule(("test",)), True)
|
||||
_check(matcher_on_endswith, EndswithRule(("test",)), True)
|
||||
_check(matcher_on_fullmatch, FullmatchRule(("test",)), True)
|
||||
_check(matcher_on_keyword, KeywordsRule("test"), True)
|
||||
_check(matcher_on_command, CommandRule([("test",)]), True)
|
||||
_check(matcher_on_shell_command, ShellCommandRule([("test",)], None), True)
|
||||
_check(matcher_on_regex, RegexRule("test"), True)
|
||||
_check(matcher_on_type, IsTypeRule(TestEvent), True)
|
||||
_check(matcher_sub_cmd, CommandRule([("test", "sub")]), True)
|
||||
_check(matcher_sub_shell_cmd, ShellCommandRule([("test", "sub")], None), True)
|
||||
_check(matcher_group_on, None, True)
|
||||
_check(matcher_group_on_metaevent, None, False)
|
||||
_check(matcher_group_on_message, None, True)
|
||||
_check(matcher_group_on_notice, None, False)
|
||||
_check(matcher_group_on_request, None, False)
|
||||
_check(matcher_group_on_startswith, StartswithRule(("test",)), True)
|
||||
_check(matcher_group_on_endswith, EndswithRule(("test",)), True)
|
||||
_check(matcher_group_on_fullmatch, FullmatchRule(("test",)), True)
|
||||
_check(matcher_group_on_keyword, KeywordsRule("test"), True)
|
||||
_check(matcher_group_on_command, CommandRule([("test",)]), True)
|
||||
_check(matcher_group_on_shell_command, ShellCommandRule([("test",)], None), True)
|
||||
_check(matcher_group_on_regex, RegexRule("test"), True)
|
||||
_check(matcher_group_on_type, IsTypeRule(TestEvent), True)
|
@@ -1,4 +1,5 @@
|
||||
from typing import Tuple, Union
|
||||
import sys
|
||||
from typing import Dict, Tuple, Union, Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
@@ -51,6 +52,7 @@ async def test_rule(app: App):
|
||||
("prefix", True, "message", "Prefix_", True),
|
||||
("prefix", False, "message", "prefoo", False),
|
||||
("prefix", False, "message", "fooprefix", False),
|
||||
("prefix", False, "message", None, False),
|
||||
(("prefix", "foo"), False, "message", "fooprefix", True),
|
||||
("prefix", False, "notice", "foo", False),
|
||||
],
|
||||
@@ -60,7 +62,7 @@ async def test_startswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
ignorecase: bool,
|
||||
type: str,
|
||||
text: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
from nonebot.rule import StartswithRule, startswith
|
||||
@@ -73,7 +75,7 @@ async def test_startswith(
|
||||
assert checker.msg == (msg,) if isinstance(msg, str) else msg
|
||||
assert checker.ignorecase == ignorecase
|
||||
|
||||
message = make_fake_message()(text)
|
||||
message = text if text is None else make_fake_message()(text)
|
||||
event = make_fake_event(_type=type, _message=message)()
|
||||
assert await dependent(event=event) == expected
|
||||
|
||||
@@ -88,6 +90,7 @@ async def test_startswith(
|
||||
("suffix", True, "message", "_Suffix", True),
|
||||
("suffix", False, "message", "suffoo", False),
|
||||
("suffix", False, "message", "suffixfoo", False),
|
||||
("suffix", False, "message", None, False),
|
||||
(("suffix", "foo"), False, "message", "suffixfoo", True),
|
||||
("suffix", False, "notice", "foo", False),
|
||||
],
|
||||
@@ -97,7 +100,7 @@ async def test_endswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
ignorecase: bool,
|
||||
type: str,
|
||||
text: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
from nonebot.rule import EndswithRule, endswith
|
||||
@@ -110,7 +113,7 @@ async def test_endswith(
|
||||
assert checker.msg == (msg,) if isinstance(msg, str) else msg
|
||||
assert checker.ignorecase == ignorecase
|
||||
|
||||
message = make_fake_message()(text)
|
||||
message = text if text is None else make_fake_message()(text)
|
||||
event = make_fake_event(_type=type, _message=message)()
|
||||
assert await dependent(event=event) == expected
|
||||
|
||||
@@ -125,6 +128,7 @@ async def test_endswith(
|
||||
("fullmatch", True, "message", "Fullmatch", True),
|
||||
("fullmatch", False, "message", "fullfoo", False),
|
||||
("fullmatch", False, "message", "_fullmatch_", False),
|
||||
("fullmatch", False, "message", None, False),
|
||||
(("fullmatch", "foo"), False, "message", "fullmatchfoo", False),
|
||||
("fullmatch", False, "notice", "foo", False),
|
||||
],
|
||||
@@ -134,7 +138,7 @@ async def test_fullmatch(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
ignorecase: bool,
|
||||
type: str,
|
||||
text: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
from nonebot.rule import FullmatchRule, fullmatch
|
||||
@@ -144,10 +148,10 @@ async def test_fullmatch(
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, FullmatchRule)
|
||||
assert checker.msg == {msg} if isinstance(msg, str) else {*msg}
|
||||
assert checker.msg == ((msg,) if isinstance(msg, str) else msg)
|
||||
assert checker.ignorecase == ignorecase
|
||||
|
||||
message = make_fake_message()(text)
|
||||
message = text if text is None else make_fake_message()(text)
|
||||
event = make_fake_event(_type=type, _message=message)()
|
||||
assert await dependent(event=event) == expected
|
||||
|
||||
@@ -158,6 +162,7 @@ async def test_fullmatch(
|
||||
[
|
||||
(("key",), "message", "_key_", True),
|
||||
(("key", "foo"), "message", "_foo_", True),
|
||||
(("key",), "message", None, False),
|
||||
(("key",), "notice", "foo", False),
|
||||
],
|
||||
)
|
||||
@@ -165,7 +170,7 @@ async def test_keyword(
|
||||
app: App,
|
||||
kws: Tuple[str, ...],
|
||||
type: str,
|
||||
text: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
from nonebot.rule import KeywordsRule, keyword
|
||||
@@ -177,7 +182,7 @@ async def test_keyword(
|
||||
assert isinstance(checker, KeywordsRule)
|
||||
assert checker.keywords == kws
|
||||
|
||||
message = make_fake_message()(text)
|
||||
message = text if text is None else make_fake_message()(text)
|
||||
event = make_fake_event(_type=type, _message=message)()
|
||||
assert await dependent(event=event) == expected
|
||||
|
||||
@@ -195,16 +200,157 @@ async def test_command(app: App, cmds: Tuple[Tuple[str, ...]]):
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, CommandRule)
|
||||
assert checker.cmds == list(cmds)
|
||||
assert checker.cmds == cmds
|
||||
|
||||
for cmd in cmds:
|
||||
state = {PREFIX_KEY: {CMD_KEY: cmd}}
|
||||
assert await dependent(state=state)
|
||||
|
||||
|
||||
# TODO: shell command
|
||||
@pytest.mark.asyncio
|
||||
async def test_shell_command(app: App):
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.exception import ParserExit
|
||||
from nonebot.consts import CMD_KEY, PREFIX_KEY, SHELL_ARGS, SHELL_ARGV, CMD_ARG_KEY
|
||||
from nonebot.rule import Namespace, ArgumentParser, ShellCommandRule, shell_command
|
||||
|
||||
# TODO: regex
|
||||
state: T_State
|
||||
CMD = ("test",)
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
test_not_cmd = shell_command(CMD)
|
||||
dependent = list(test_not_cmd.checkers)[0]
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
event = make_fake_event(_message=message)()
|
||||
state = {PREFIX_KEY: {CMD_KEY: ("not",), CMD_ARG_KEY: message}}
|
||||
assert not await dependent(event=event, state=state)
|
||||
|
||||
test_no_parser = shell_command(CMD)
|
||||
dependent = list(test_no_parser.checkers)[0]
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
event = make_fake_event(_message=message)()
|
||||
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||
assert await dependent(event=event, state=state)
|
||||
assert state[SHELL_ARGV] == []
|
||||
assert SHELL_ARGS not in state
|
||||
|
||||
parser = ArgumentParser("test")
|
||||
parser.add_argument("-a", required=True)
|
||||
|
||||
test_simple_parser = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_simple_parser.checkers)[0]
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message("-a 1")
|
||||
event = make_fake_event(_message=message)()
|
||||
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||
assert await dependent(event=event, state=state)
|
||||
assert state[SHELL_ARGV] == ["-a", "1"]
|
||||
assert state[SHELL_ARGS] == Namespace(a="1")
|
||||
|
||||
test_parser_help = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_parser_help.checkers)[0]
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message("-h")
|
||||
event = make_fake_event(_message=message)()
|
||||
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||
assert await dependent(event=event, state=state)
|
||||
assert state[SHELL_ARGV] == ["-h"]
|
||||
assert isinstance(state[SHELL_ARGS], ParserExit)
|
||||
assert state[SHELL_ARGS].status == 0
|
||||
assert state[SHELL_ARGS].message == parser.format_help()
|
||||
|
||||
test_parser_error = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_parser_error.checkers)[0]
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
event = make_fake_event(_message=message)()
|
||||
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||
assert await dependent(event=event, state=state)
|
||||
assert state[SHELL_ARGV] == []
|
||||
assert isinstance(state[SHELL_ARGS], ParserExit)
|
||||
assert state[SHELL_ARGS].status != 0
|
||||
|
||||
test_message_parser = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_message_parser.checkers)[0]
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = MessageSegment.text("-a") + MessageSegment.image("test")
|
||||
event = make_fake_event(_message=message)()
|
||||
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||
assert await dependent(event=event, state=state)
|
||||
assert state[SHELL_ARGV] == ["-a", MessageSegment.image("test")]
|
||||
assert state[SHELL_ARGS] == Namespace(a=MessageSegment.image("test"))
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
parser = ArgumentParser("test", exit_on_error=False)
|
||||
parser.add_argument("-a", required=True)
|
||||
|
||||
test_not_exit = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_not_exit.checkers)[0]
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
event = make_fake_event(_message=message)()
|
||||
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||
assert await dependent(event=event, state=state)
|
||||
assert state[SHELL_ARGV] == []
|
||||
assert isinstance(state[SHELL_ARGS], ParserExit)
|
||||
assert state[SHELL_ARGS].status != 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"pattern,type,text,expected,matched,group,dict",
|
||||
[
|
||||
(
|
||||
r"(?P<key>key\d)",
|
||||
"message",
|
||||
"_key1_",
|
||||
True,
|
||||
"key1",
|
||||
("key1",),
|
||||
{"key": "key1"},
|
||||
),
|
||||
(r"foo", "message", None, False, None, None, None),
|
||||
(r"foo", "notice", "foo", False, None, None, None),
|
||||
],
|
||||
)
|
||||
async def test_regex(
|
||||
app: App,
|
||||
pattern: str,
|
||||
type: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
matched: Optional[str],
|
||||
group: Optional[Tuple[str, ...]],
|
||||
dict: Optional[Dict[str, str]],
|
||||
):
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.rule import RegexRule, regex
|
||||
from nonebot.consts import REGEX_DICT, REGEX_GROUP, REGEX_MATCHED
|
||||
|
||||
test_regex = regex(pattern)
|
||||
dependent = list(test_regex.checkers)[0]
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, RegexRule)
|
||||
assert checker.regex == pattern
|
||||
|
||||
message = text if text is None else make_fake_message()(text)
|
||||
event = make_fake_event(_type=type, _message=message)()
|
||||
state = {}
|
||||
assert await dependent(event=event, state=state) == expected
|
||||
assert state.get(REGEX_MATCHED) == matched
|
||||
assert state.get(REGEX_GROUP) == group
|
||||
assert state.get(REGEX_DICT) == dict
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -212,11 +358,32 @@ async def test_command(app: App, cmds: Tuple[Tuple[str, ...]]):
|
||||
async def test_to_me(app: App, expected: bool):
|
||||
from nonebot.rule import ToMeRule, to_me
|
||||
|
||||
test_keyword = to_me()
|
||||
dependent = list(test_keyword.checkers)[0]
|
||||
test_to_me = to_me()
|
||||
dependent = list(test_to_me.checkers)[0]
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, ToMeRule)
|
||||
|
||||
event = make_fake_event(_to_me=expected)()
|
||||
assert await dependent(event=event) == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_is_type(app: App):
|
||||
from nonebot.rule import IsTypeRule, is_type
|
||||
|
||||
Event1 = make_fake_event()
|
||||
Event2 = make_fake_event()
|
||||
Event3 = make_fake_event()
|
||||
|
||||
test_type = is_type(Event1, Event2)
|
||||
dependent = list(test_type.checkers)[0]
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, IsTypeRule)
|
||||
|
||||
event = Event1()
|
||||
assert await dependent(event=event)
|
||||
|
||||
event = Event3()
|
||||
assert not await dependent(event=event)
|
||||
|
@@ -65,7 +65,7 @@ def make_fake_event(
|
||||
_type: str = "message",
|
||||
_name: str = "test",
|
||||
_description: str = "test",
|
||||
_user_id: str = "test",
|
||||
_user_id: Optional[str] = "test",
|
||||
_session_id: Optional[str] = "test",
|
||||
_message: Optional["Message"] = None,
|
||||
_to_me: bool = True,
|
||||
@@ -86,7 +86,9 @@ def make_fake_event(
|
||||
return _description
|
||||
|
||||
def get_user_id(self) -> str:
|
||||
return _user_id
|
||||
if _user_id is not None:
|
||||
return _user_id
|
||||
raise NotImplementedError
|
||||
|
||||
def get_session_id(self) -> str:
|
||||
if _session_id is not None:
|
||||
|
@@ -8,7 +8,7 @@ slug: /
|
||||
|
||||
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架,它基于 Python 的类型注解和异步特性,能够为你的需求实现提供便捷灵活的支持。
|
||||
|
||||
需要注意的是,NoneBot2 仅支持 **Python 3.7.3 以上版本**
|
||||
需要注意的是,NoneBot2 仅支持 **Python 3.8 以上版本**
|
||||
|
||||
## 特色
|
||||
|
||||
|
@@ -13,7 +13,7 @@ import Asciinema from "@site/src/components/Asciinema";
|
||||
# 安装 NoneBot2
|
||||
|
||||
:::warning 注意
|
||||
请确保你的 Python 版本 >= 3.7.3。
|
||||
请确保你的 Python 版本 >= 3.8。
|
||||
:::
|
||||
|
||||
:::warning 注意
|
||||
|
@@ -277,7 +277,7 @@ async def _(foo: str = CommandStart()): ...
|
||||
|
||||
### ShellCommandArgs
|
||||
|
||||
获取 shell 命令解析后的参数。
|
||||
获取 shell 命令解析后的参数,支持 MessageSegment 富文本(如:图片)。
|
||||
|
||||
:::tip 提示
|
||||
如果参数解析失败,则为 [`ParserExit`](../../api/exception.md#ParserExit) 异常,并携带错误码与错误信息。
|
||||
@@ -288,21 +288,28 @@ async def _(foo: str = CommandStart()): ...
|
||||
```python {8,12}
|
||||
from nonebot import on_shell_command
|
||||
from nonebot.params import ShellCommandArgs
|
||||
from nonebot.rule import Namespace, ArgumentParser
|
||||
|
||||
parser = ArgumentParser("demo")
|
||||
# parser.add_argument ...
|
||||
matcher = on_shell_command("cmd", parser)
|
||||
|
||||
# 解析失败
|
||||
@matcher.handle()
|
||||
async def _(foo: ParserExit = ShellCommandArgs()): ...
|
||||
async def _(foo: ParserExit = ShellCommandArgs()):
|
||||
if foo.status == 0:
|
||||
foo.message # help message
|
||||
else:
|
||||
foo.message # error message
|
||||
|
||||
# 解析成功
|
||||
@matcher.handle()
|
||||
async def _(foo: Dict[str, Any] = ShellCommandArgs()): ...
|
||||
async def _(foo: Namespace = ShellCommandArgs()): ...
|
||||
```
|
||||
|
||||
### ShellCommandArgv
|
||||
|
||||
获取 shell 命令解析前的参数列表。
|
||||
获取 shell 命令解析前的参数列表,支持 MessageSegment 富文本(如:图片)。
|
||||
|
||||
```python {7}
|
||||
from nonebot import on_shell_command
|
||||
@@ -311,7 +318,7 @@ from nonebot.params import ShellCommandArgs
|
||||
matcher = on_shell_command("cmd")
|
||||
|
||||
@matcher.handle()
|
||||
async def _(foo: List[str] = ShellCommandArgv()): ...
|
||||
async def _(foo: List[Union[str, MessageSegment]] = ShellCommandArgv()): ...
|
||||
```
|
||||
|
||||
### RegexMatched
|
||||
|
@@ -5,6 +5,102 @@ toc_max_heading_level: 2
|
||||
|
||||
# 更新日志
|
||||
|
||||
## v2.0.0-rc.1
|
||||
|
||||
### 💥 破坏性变更
|
||||
|
||||
- Feature: `SUPERUSER` 权限匹配任意超管事件 [@AkiraXie](https://github.com/AkiraXie) ([#1275](https://github.com/nonebot/nonebot2/pull/1275))
|
||||
- Remove: 移除过时的 State 注入参数 [@yanyongyu](https://github.com/yanyongyu) ([#1160](https://github.com/nonebot/nonebot2/pull/1160))
|
||||
- Remove: 移除过时的 `nonebot.plugins` toml 配置 [@yanyongyu](https://github.com/yanyongyu) ([#1151](https://github.com/nonebot/nonebot2/pull/1151))
|
||||
- Remove: 移除 Python 3.7 支持 [@yanyongyu](https://github.com/yanyongyu) ([#1148](https://github.com/nonebot/nonebot2/pull/1148))
|
||||
- Remove: 删除过时的 Export 功能 [@yanyongyu](https://github.com/yanyongyu) ([#1125](https://github.com/nonebot/nonebot2/pull/1125))
|
||||
|
||||
### 🚀 新功能
|
||||
|
||||
- Feature: `SUPERUSER` 权限匹配任意超管事件 [@AkiraXie](https://github.com/AkiraXie) ([#1275](https://github.com/nonebot/nonebot2/pull/1275))
|
||||
- Feature: 改进 `CommandGroup` 与 `MatcherGroup` 的结构 [@A-kirami](https://github.com/A-kirami) ([#1240](https://github.com/nonebot/nonebot2/pull/1240))
|
||||
- Feature: 调整日志输出格式与等级 [@yanyongyu](https://github.com/yanyongyu) ([#1233](https://github.com/nonebot/nonebot2/pull/1233))
|
||||
- Feature: 优化依赖注入结构 [@yanyongyu](https://github.com/yanyongyu) ([#1227](https://github.com/nonebot/nonebot2/pull/1227))
|
||||
- Featue: `load_plugin` 支持 `pathlib.Path` [@Lancercmd](https://github.com/Lancercmd) ([#1194](https://github.com/nonebot/nonebot2/pull/1194))
|
||||
- Feature: 新增事件类型过滤 rule [@yanyongyu](https://github.com/yanyongyu) ([#1183](https://github.com/nonebot/nonebot2/pull/1183))
|
||||
- Feature: shell command 添加富文本支持 [@yanyongyu](https://github.com/yanyongyu) ([#1171](https://github.com/nonebot/nonebot2/pull/1171))
|
||||
|
||||
### 🐛 Bug 修复
|
||||
|
||||
- Fix: 内置规则和权限没有捕获错误 [@yanyongyu](https://github.com/yanyongyu) ([#1291](https://github.com/nonebot/nonebot2/pull/1291))
|
||||
- Fix: 修复 User 会话权限更新嵌套问题 [@yanyongyu](https://github.com/yanyongyu) ([#1208](https://github.com/nonebot/nonebot2/pull/1208))
|
||||
- Fix: 修复当消息与不支持的类型相加时抛出的异常类型错误 [@mnixry](https://github.com/mnixry) ([#1166](https://github.com/nonebot/nonebot2/pull/1166))
|
||||
|
||||
### 💫 杂项
|
||||
|
||||
- Fix: 修正 GenshinUID 的发布类型 [@A-kirami](https://github.com/A-kirami) ([#1243](https://github.com/nonebot/nonebot2/pull/1243))
|
||||
- Remove: 移除未使用的导入 [@A-kirami](https://github.com/A-kirami) ([#1236](https://github.com/nonebot/nonebot2/pull/1236))
|
||||
- Plugin: 更新插件米游社辅助工具 tag [@Ljzd-PRO](https://github.com/Ljzd-PRO) ([#1221](https://github.com/nonebot/nonebot2/pull/1221))
|
||||
- Plugin: 修改插件多功能简易群管信息 [@HuYihe2008](https://github.com/HuYihe2008) ([#1180](https://github.com/nonebot/nonebot2/pull/1180))
|
||||
- Plugin: 修改插件多功能简易群管信息 [@HuYihe2008](https://github.com/HuYihe2008) ([#1159](https://github.com/nonebot/nonebot2/pull/1159))
|
||||
- Plugin: 修改 QQ 续火花插件信息 [@GC-ZF](https://github.com/GC-ZF) ([#1158](https://github.com/nonebot/nonebot2/pull/1158))
|
||||
- Plugin: 修改插件多功能简易群管信息 [@HuYihe2008](https://github.com/HuYihe2008) ([#1154](https://github.com/nonebot/nonebot2/pull/1154))
|
||||
|
||||
### 🍻 插件发布
|
||||
|
||||
- Plugin: 文字识别 [@yanyongyu](https://github.com/yanyongyu) ([#1295](https://github.com/nonebot/nonebot2/pull/1295))
|
||||
- Plugin: 在线编曲 [@yanyongyu](https://github.com/yanyongyu) ([#1293](https://github.com/nonebot/nonebot2/pull/1293))
|
||||
- Plugin: 图灵机器人 [@yanyongyu](https://github.com/yanyongyu) ([#1289](https://github.com/nonebot/nonebot2/pull/1289))
|
||||
- Plugin: PicStatus [@yanyongyu](https://github.com/yanyongyu) ([#1287](https://github.com/nonebot/nonebot2/pull/1287))
|
||||
- Plugin: 阿里云盘福利码自动兑换 [@yanyongyu](https://github.com/yanyongyu) ([#1283](https://github.com/nonebot/nonebot2/pull/1283))
|
||||
- Plugin: gal 角色语音生成 [@yanyongyu](https://github.com/yanyongyu) ([#1281](https://github.com/nonebot/nonebot2/pull/1281))
|
||||
- Plugin: 漂流瓶 [@yanyongyu](https://github.com/yanyongyu) ([#1279](https://github.com/nonebot/nonebot2/pull/1279))
|
||||
- Plugin: BWIKI 助手移植版 [@yanyongyu](https://github.com/yanyongyu) ([#1274](https://github.com/nonebot/nonebot2/pull/1274))
|
||||
- Plugin: nonebot 物联网插件 [@yanyongyu](https://github.com/yanyongyu) ([#1265](https://github.com/nonebot/nonebot2/pull/1265))
|
||||
- Plugin: 狼人杀插件 [@yanyongyu](https://github.com/yanyongyu) ([#1252](https://github.com/nonebot/nonebot2/pull/1252))
|
||||
- Plugin: ayaka - 文字游戏开发辅助插件 [@yanyongyu](https://github.com/yanyongyu) ([#1254](https://github.com/nonebot/nonebot2/pull/1254))
|
||||
- Plugin: 图像超分辨率重建 [@yanyongyu](https://github.com/yanyongyu) ([#1250](https://github.com/nonebot/nonebot2/pull/1250))
|
||||
- Plugin: Minecraft Server 聊天同步 [@yanyongyu](https://github.com/yanyongyu) ([#1245](https://github.com/nonebot/nonebot2/pull/1245))
|
||||
- Plugin: 查询 ETH 合并日期 [@yanyongyu](https://github.com/yanyongyu) ([#1232](https://github.com/nonebot/nonebot2/pull/1232))
|
||||
- Plugin: 星际战甲事件查询 [@yanyongyu](https://github.com/yanyongyu) ([#1220](https://github.com/nonebot/nonebot2/pull/1220))
|
||||
- Plugin: 米游社辅助工具 [@yanyongyu](https://github.com/yanyongyu) ([#1218](https://github.com/nonebot/nonebot2/pull/1218))
|
||||
- Plugin: 原神每日材料查询 [@yanyongyu](https://github.com/yanyongyu) ([#1216](https://github.com/nonebot/nonebot2/pull/1216))
|
||||
- Plugin: MC_QQ_MCRcon [@yanyongyu](https://github.com/yanyongyu) ([#1211](https://github.com/nonebot/nonebot2/pull/1211))
|
||||
- Plugin: 原神角色展柜查询 [@yanyongyu](https://github.com/yanyongyu) ([#1209](https://github.com/nonebot/nonebot2/pull/1209))
|
||||
- Plugin: 修仙模拟器 [@yanyongyu](https://github.com/yanyongyu) ([#1202](https://github.com/nonebot/nonebot2/pull/1202))
|
||||
- Plugin: 赛博浅草寺 [@yanyongyu](https://github.com/yanyongyu) ([#1206](https://github.com/nonebot/nonebot2/pull/1206))
|
||||
- Plugin: 不背单词 [@yanyongyu](https://github.com/yanyongyu) ([#1204](https://github.com/nonebot/nonebot2/pull/1204))
|
||||
- Plugin: 自识别 todo [@yanyongyu](https://github.com/yanyongyu) ([#1193](https://github.com/nonebot/nonebot2/pull/1193))
|
||||
- Plugin: 雨课堂自动签到 [@yanyongyu](https://github.com/yanyongyu) ([#1189](https://github.com/nonebot/nonebot2/pull/1189))
|
||||
- Plugin: 反馈及通知 [@yanyongyu](https://github.com/yanyongyu) ([#1187](https://github.com/nonebot/nonebot2/pull/1187))
|
||||
- Plugin: MagiaDice 骰娘及 TRPGLOG [@yanyongyu](https://github.com/yanyongyu) ([#1185](https://github.com/nonebot/nonebot2/pull/1185))
|
||||
- Plugin: 面麻小助手 [@yanyongyu](https://github.com/yanyongyu) ([#1191](https://github.com/nonebot/nonebot2/pull/1191))
|
||||
- Plugin: 话痨排行榜 [@yanyongyu](https://github.com/yanyongyu) ([#1182](https://github.com/nonebot/nonebot2/pull/1182))
|
||||
- Plugin: 保存群聊闪照 [@yanyongyu](https://github.com/yanyongyu) ([#1179](https://github.com/nonebot/nonebot2/pull/1179))
|
||||
- Plugin: 课表查询 [@yanyongyu](https://github.com/yanyongyu) ([#1168](https://github.com/nonebot/nonebot2/pull/1168))
|
||||
- Plugin: 业余无线电助手 [@yanyongyu](https://github.com/yanyongyu) ([#1173](https://github.com/nonebot/nonebot2/pull/1173))
|
||||
- Plugin: NoneBot 树形帮助插件 [@yanyongyu](https://github.com/yanyongyu) ([#1177](https://github.com/nonebot/nonebot2/pull/1177))
|
||||
- Plugin: 工作性价比 [@yanyongyu](https://github.com/yanyongyu) ([#1175](https://github.com/nonebot/nonebot2/pull/1175))
|
||||
- Plugin: 娶群友 [@yanyongyu](https://github.com/yanyongyu) ([#1170](https://github.com/nonebot/nonebot2/pull/1170))
|
||||
- Plugin: PixivBot [@yanyongyu](https://github.com/yanyongyu) ([#1165](https://github.com/nonebot/nonebot2/pull/1165))
|
||||
- Plugin: 日韩中 VITS 模型原神拟声 [@yanyongyu](https://github.com/yanyongyu) ([#1162](https://github.com/nonebot/nonebot2/pull/1162))
|
||||
- Plugin: 每日人品 [@yanyongyu](https://github.com/yanyongyu) ([#1156](https://github.com/nonebot/nonebot2/pull/1156))
|
||||
- Plugin: nonebot-plugin-drawer [@yanyongyu](https://github.com/yanyongyu) ([#1146](https://github.com/nonebot/nonebot2/pull/1146))
|
||||
- Plugin: 小游戏合集 [@yanyongyu](https://github.com/yanyongyu) ([#1150](https://github.com/nonebot/nonebot2/pull/1150))
|
||||
- Plugin: 简易群管(带入群欢迎) [@yanyongyu](https://github.com/yanyongyu) ([#1142](https://github.com/nonebot/nonebot2/pull/1142))
|
||||
- Plugin: wiki 条目搜索、获取简介 [@yanyongyu](https://github.com/yanyongyu) ([#1133](https://github.com/nonebot/nonebot2/pull/1133))
|
||||
- Plugin: bangumi 搜索 [@yanyongyu](https://github.com/yanyongyu) ([#1137](https://github.com/nonebot/nonebot2/pull/1137))
|
||||
- Plugin: 疫情小助手-频道版 [@yanyongyu](https://github.com/yanyongyu) ([#1131](https://github.com/nonebot/nonebot2/pull/1131))
|
||||
- Plugin: MC_QQ 通信 [@yanyongyu](https://github.com/yanyongyu) ([#1127](https://github.com/nonebot/nonebot2/pull/1127))
|
||||
- Plugin: BAWiki [@yanyongyu](https://github.com/yanyongyu) ([#1129](https://github.com/nonebot/nonebot2/pull/1129))
|
||||
|
||||
### 🍻 机器人发布
|
||||
|
||||
- Bot: IdhagnBot [@yanyongyu](https://github.com/yanyongyu) ([#1267](https://github.com/nonebot/nonebot2/pull/1267))
|
||||
- Bot: LittlePaimon [@yanyongyu](https://github.com/yanyongyu) ([#1256](https://github.com/nonebot/nonebot2/pull/1256))
|
||||
- Bot: GenshinUID [@yanyongyu](https://github.com/yanyongyu) ([#1226](https://github.com/nonebot/nonebot2/pull/1226))
|
||||
- Bot: 小白机器人 [@yanyongyu](https://github.com/yanyongyu) ([#1224](https://github.com/nonebot/nonebot2/pull/1224))
|
||||
|
||||
### 🍻 适配器发布
|
||||
|
||||
- Adapter: GitHub [@yanyongyu](https://github.com/yanyongyu) ([#1297](https://github.com/nonebot/nonebot2/pull/1297))
|
||||
- Adapter: Console [@yanyongyu](https://github.com/yanyongyu) ([#1213](https://github.com/nonebot/nonebot2/pull/1213))
|
||||
|
||||
## v2.0.0-beta.5
|
||||
|
||||
### 🚀 新功能
|
||||
|
@@ -78,5 +78,25 @@
|
||||
"homepage": "https://onebot.adapters.nonebot.dev/",
|
||||
"tags": [],
|
||||
"is_official": true
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapters.console",
|
||||
"project_link": "nonebot-adapter-console",
|
||||
"name": "Console",
|
||||
"desc": "基于终端的交互式适配器",
|
||||
"author": "Melodyknit",
|
||||
"homepage": "https://github.com/nonebot/adapter-console",
|
||||
"tags": [],
|
||||
"is_official": true
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapters.github",
|
||||
"project_link": "nonebot-adapter-github",
|
||||
"name": "GitHub",
|
||||
"desc": "GitHub APP & OAuth APP integration",
|
||||
"author": "yanyongyu",
|
||||
"homepage": "https://github.com/nonebot/adapter-github",
|
||||
"tags": [],
|
||||
"is_official": true
|
||||
}
|
||||
]
|
@@ -233,5 +233,48 @@
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "小白机器人",
|
||||
"desc": "一个高度依赖数据库的群管理机器人",
|
||||
"author": "SDIJF1521",
|
||||
"homepage": "https://github.com/SDIJF1521/qqai",
|
||||
"tags": [
|
||||
{
|
||||
"label": "群管理",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "新人作品",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "LittlePaimon",
|
||||
"desc": "小派蒙,多功能原神机器人。",
|
||||
"author": "CMHopeSunshine",
|
||||
"homepage": "https://github.com/CMHopeSunshine/LittlePaimon",
|
||||
"tags": [
|
||||
{
|
||||
"label": "原神",
|
||||
"color": "#7a52ea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "IdhagnBot",
|
||||
"desc": "🐱🤖 一个以娱乐功能为主的缝合怪(划掉)QQ机器人,包含一定Furry要素但是不会卖萌(就是逊啦!)",
|
||||
"author": "su226",
|
||||
"homepage": "https://github.com/su226/IdhagnBot",
|
||||
"tags": [
|
||||
{
|
||||
"label": "a:OneBot",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
}
|
||||
]
|
@@ -1974,7 +1974,7 @@
|
||||
"module_name": "nonebot_plugin_firexN",
|
||||
"project_link": "nonebot-plugin-firexN",
|
||||
"name": "一起燚xN吧",
|
||||
"desc": "QQ续火花,可以自定义多个联系人、消息内容、发送时间",
|
||||
"desc": "QQ续火花,可以自定义多个联系人、消息内容、发送时间,支持群聊定时消息",
|
||||
"author": "GC-ZF",
|
||||
"homepage": "https://github.com/GC-ZF/nonebot_plugin_firexN",
|
||||
"tags": [],
|
||||
@@ -2125,5 +2125,757 @@
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_bawiki",
|
||||
"project_link": "nonebot-plugin-bawiki",
|
||||
"name": "BAWiki",
|
||||
"desc": "基于 NoneBot2 的碧蓝档案 Wiki 插件",
|
||||
"author": "lgc2333",
|
||||
"homepage": "https://github.com/lgc2333/nonebot-plugin-bawiki/",
|
||||
"tags": [
|
||||
{
|
||||
"label": "t:碧蓝档案",
|
||||
"color": "#00d7fb"
|
||||
},
|
||||
{
|
||||
"label": "t:蔚藍檔案",
|
||||
"color": "#00d7fb"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_mcqq",
|
||||
"project_link": "nonebot-plugin-mcqq",
|
||||
"name": "MC_QQ通信",
|
||||
"desc": "基于NoneBot的与Minecraft Server互通消息的插件",
|
||||
"author": "17TheWord",
|
||||
"homepage": "https://github.com/17TheWord/nonebot-plugin-mcqq",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Minecraft",
|
||||
"color": "#52ea6f"
|
||||
},
|
||||
{
|
||||
"label": "消息互通",
|
||||
"color": "#52eadf"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_covid_19_by_guild",
|
||||
"project_link": "nonebot-plugin-covid-19-by-guild",
|
||||
"name": "疫情小助手-频道版",
|
||||
"desc": "👉 疫情小助手 频道版👈",
|
||||
"author": "bingqiu456",
|
||||
"homepage": "https://github.com/bingqiu456/nonebot-plugin-covid-19-by-guild",
|
||||
"tags": [
|
||||
{
|
||||
"label": "a:QQ频道",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "t:疫情小助手",
|
||||
"color": "#526fea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot-plugin-bangumi-search",
|
||||
"project_link": "nonebot-plugin-bangumi-search",
|
||||
"name": "bangumi搜索",
|
||||
"desc": "搜索番剧,并且返回简介栏和收藏盒",
|
||||
"author": "Ankhyty",
|
||||
"homepage": "https://github.com/Ankhyty/nonebot-plugin-bangumi-search",
|
||||
"tags": [
|
||||
{
|
||||
"label": "a:onebot",
|
||||
"color": "#000000"
|
||||
},
|
||||
{
|
||||
"label": "t:漫研",
|
||||
"color": "#2f48e0"
|
||||
},
|
||||
{
|
||||
"label": "bangumi",
|
||||
"color": "#de2968"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_wiki",
|
||||
"project_link": "nonebot-plugin-wiki",
|
||||
"name": "wiki条目搜索、获取简介",
|
||||
"desc": "通过mediawiki api进行条目搜索、生成简介(biliwiki部分通过网页解析实现)",
|
||||
"author": "ZombieFly",
|
||||
"homepage": "https://github.com/ZombieFly/nb2-wiki",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_groupmanager",
|
||||
"project_link": "nonebot-plugin-groupmanager",
|
||||
"name": "多功能群管",
|
||||
"desc": "与简易群管属于同个产品,就是多了一个入群欢迎,感谢@幼稚园园长 开发的原版插件的帮助",
|
||||
"author": "HuYihe2008",
|
||||
"homepage": "https://github.com/HuYihe2008/nonebot_plugin_groupmanager",
|
||||
"tags": [
|
||||
{
|
||||
"label": "简易群管",
|
||||
"color": "#53e950"
|
||||
},
|
||||
{
|
||||
"label": "插件改良",
|
||||
"color": "#2b7be2"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_game_collection",
|
||||
"project_link": "nonebot_plugin_game_collection",
|
||||
"name": "小游戏合集",
|
||||
"desc": "改自 nonebot_plugin_russian 合并了nonebot_plugin_horserace还有一些自编玩法的小游戏合集。",
|
||||
"author": "KarisAya",
|
||||
"homepage": "https://github.com/KarisAya/nonebot_plugin_game_collection",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_drawer",
|
||||
"project_link": "nonebot-plugin-drawer",
|
||||
"name": "nonebot-plugin-drawer",
|
||||
"desc": "适用于 Nonebot2 的AI画画插件",
|
||||
"author": "CrazyBoyM",
|
||||
"homepage": "https://github.com/CrazyBoyM/nonebot-plugin-drawer",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_jrrp-n",
|
||||
"project_link": "nonebot-plugin-jrrp-n",
|
||||
"name": "每日人品",
|
||||
"desc": "不基于random的每日人品插件",
|
||||
"author": "SkyDynamic",
|
||||
"homepage": "https://github.com/SkyDynamic/nonebot_plugin_jrrp",
|
||||
"tags": [
|
||||
{
|
||||
"label": "每日人品",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "jrrp",
|
||||
"color": "#529fea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_moegoe",
|
||||
"project_link": "nonebot-plugin-moegoe",
|
||||
"name": "日韩中 VITS 模型原神拟声",
|
||||
"desc": "现在是琪亚娜时间~",
|
||||
"author": "Yiyuiii",
|
||||
"homepage": "https://github.com/Yiyuiii/nonebot-plugin-moegoe",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_pixivbot",
|
||||
"project_link": "nonebot-plugin-pixivbot",
|
||||
"name": "PixivBot",
|
||||
"desc": "NoneBot插件,发送随机Pixiv插画、画师更新推送、定时订阅推送……",
|
||||
"author": "ssttkkl",
|
||||
"homepage": "https://github.com/ssttkkl/nonebot-plugin-pixivbot",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_groupmate_waifu",
|
||||
"project_link": "nonebot_plugin_groupmate_waifu",
|
||||
"name": "娶群友",
|
||||
"desc": "娶群友(纯爱版,ntr版)",
|
||||
"author": "KarisAya",
|
||||
"homepage": "https://github.com/KarisAya/nonebot_plugin_groupmate_waifu",
|
||||
"tags": [
|
||||
{
|
||||
"label": "娶群友",
|
||||
"color": "#529bea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_workscore",
|
||||
"project_link": "nonebot-plugin-workscore",
|
||||
"name": "工作性价比",
|
||||
"desc": "看看你的工作惨不惨",
|
||||
"author": "yzyyz1387",
|
||||
"homepage": "https://github.com/yzyyz1387/nonebot_plugin_workscore",
|
||||
"tags": [
|
||||
{
|
||||
"label": "工作性价比计算器",
|
||||
"color": "#3898fc"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_treehelp",
|
||||
"project_link": "nonebot-plugin-treehelp",
|
||||
"name": "NoneBot 树形帮助插件",
|
||||
"desc": "利用插件元数据生成插件帮助,并支持插件嵌套。",
|
||||
"author": "he0119",
|
||||
"homepage": "https://github.com/he0119/nonebot-plugin-treehelp",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "cqsat",
|
||||
"project_link": "nonebot-plugin-cqsat",
|
||||
"name": "业余无线电助手",
|
||||
"desc": "一个业余无线电相关的插件",
|
||||
"author": "yzyyz1387",
|
||||
"homepage": "https://github.com/yzyyz1387/cqsat",
|
||||
"tags": [
|
||||
{
|
||||
"label": "业余无线电",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "HAM",
|
||||
"color": "#3898fc"
|
||||
},
|
||||
{
|
||||
"label": "卫星追踪",
|
||||
"color": "#fca638"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_course",
|
||||
"project_link": "nonebot-plugin-course",
|
||||
"name": "课表查询",
|
||||
"desc": "本周课表,完整课表,下节课在哪上,今天还有什么课🤔",
|
||||
"author": "InariInDream",
|
||||
"homepage": "https://github.com/InariInDream/nonebot_plugin_course",
|
||||
"tags": [
|
||||
{
|
||||
"label": "课表",
|
||||
"color": "#6e9af2"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_flash_silentsave",
|
||||
"project_link": "nonebot_plugin_flash_silentsave",
|
||||
"name": "保存群聊闪照",
|
||||
"desc": "静默保存群聊中的闪照",
|
||||
"author": "KarisAya",
|
||||
"homepage": "https://github.com/KarisAya/nonebot_plugin_flash_silentsave",
|
||||
"tags": [
|
||||
{
|
||||
"label": "反闪照",
|
||||
"color": "#52a5ea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_dialectlist",
|
||||
"project_link": "nonebot-plugin-dialectlist",
|
||||
"name": "话痨排行榜",
|
||||
"desc": "给话多的群友一个排行",
|
||||
"author": "X-Skirt-X",
|
||||
"homepage": "https://github.com/X-Skirt-X/nonebot_plugin_dialectlist",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_offline_mahjong_helper",
|
||||
"project_link": "nonebot-plugin-offline-mahjong-helper",
|
||||
"name": "面麻小助手",
|
||||
"desc": "可约桌、算点、精算、雀魂查询的面麻助手。",
|
||||
"author": "Nranphy",
|
||||
"homepage": "https://github.com/Nranphy/nonebot_plugin_offline_mahjong_helper",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Mahjong",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "雀魂",
|
||||
"color": "#eaa452"
|
||||
},
|
||||
{
|
||||
"label": "线下约桌",
|
||||
"color": "#52a6ea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot-plugin-magiadice",
|
||||
"project_link": "nonebot-plugin-magiadice",
|
||||
"name": "MagiaDice骰娘及TRPGLOG",
|
||||
"desc": "能够实时在网页围观的TRPG记录插件,同时具有骰娘的功能",
|
||||
"author": "sena-nana",
|
||||
"homepage": "https://github.com/sena-nana/MutsukiBot/tree/main/nonebot-plugin-magiadice",
|
||||
"tags": [
|
||||
{
|
||||
"label": "TRPG",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot-plugin-send",
|
||||
"project_link": "nonebot-plugin-send",
|
||||
"name": "反馈及通知",
|
||||
"desc": "用户通过bot向superuser发送反馈,以及bot向全群发送通知",
|
||||
"author": "sena-nana",
|
||||
"homepage": "https://github.com/sena-nana/MutsukiBot/tree/main/nonebot-plugin-send",
|
||||
"tags": [
|
||||
{
|
||||
"label": "send",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "notice",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "公告",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot-plugin-ykt",
|
||||
"project_link": "nonebot-plugin-ykt",
|
||||
"name": "雨课堂自动签到",
|
||||
"desc": "使用playwright模拟浏览器签到雨课堂,并将腾讯会议号发送给superuser",
|
||||
"author": "sena-nana",
|
||||
"homepage": "https://github.com/sena-nana/MutsukiBot/tree/main/nonebot-plugin-ykt",
|
||||
"tags": [
|
||||
{
|
||||
"label": "playwright",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_todo_nlp",
|
||||
"project_link": "nonebot-plugin-todo-nlp",
|
||||
"name": "自识别todo",
|
||||
"desc": "一款自动识别提醒内容,可生成todo图片并定时推送的nonebot2插件,v11适配器可用",
|
||||
"author": "CofinCup",
|
||||
"homepage": "https://github.com/CofinCup/nonebot-plugin-todo-nlp",
|
||||
"tags": [
|
||||
{
|
||||
"label": "t:todo",
|
||||
"color": "#499bdd"
|
||||
},
|
||||
{
|
||||
"label": "t:nlp",
|
||||
"color": "#83b279"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_wordsnorote",
|
||||
"project_link": "nonebot-plugin-wordsnorote",
|
||||
"name": "不背单词",
|
||||
"desc": "哥们哥们,背单词么?哥们!",
|
||||
"author": "GC-ZF",
|
||||
"homepage": "https://github.com/GC-ZF/nonebot_plugin_wordsnorote",
|
||||
"tags": [
|
||||
{
|
||||
"label": "四六级",
|
||||
"color": "#24a0d8"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_CyberSensoji",
|
||||
"project_link": "nonebot-plugin-CyberSensoji",
|
||||
"name": "赛博浅草寺",
|
||||
"desc": "随机抽取浅草寺的一签运势",
|
||||
"author": "Raidenneox",
|
||||
"homepage": "https://github.com/Raidenneox/nonebot_plugin_CyberSensoji",
|
||||
"tags": [
|
||||
{
|
||||
"label": "t:抽签",
|
||||
"color": "#52eadf"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_xiuxian",
|
||||
"project_link": "nonebot-plugin-xiuxian",
|
||||
"name": "修仙模拟器",
|
||||
"desc": "加入群内修仙吧!",
|
||||
"author": "s52047qwas",
|
||||
"homepage": "https://github.com/s52047qwas/nonebot_plugin_xiuxian",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_gspanel",
|
||||
"project_link": "nonebot-plugin-gspanel",
|
||||
"name": "原神角色展柜查询",
|
||||
"desc": "移植自 @yoimiya-kokomi/miao-plugin 的原神游戏内角色展柜数据查询",
|
||||
"author": "monsterxcn",
|
||||
"homepage": "https://github.com/monsterxcn/nonebot-plugin-gspanel",
|
||||
"tags": [
|
||||
{
|
||||
"label": "t:Genshin",
|
||||
"color": "#ffd49f"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_mcqq_mcrcon",
|
||||
"project_link": "nonebot-plugin-mcqq-mcrcon",
|
||||
"name": "MC_QQ_MCRcon",
|
||||
"desc": "基于NoneBot的QQ群聊与Minecraft Server消息互通插件",
|
||||
"author": "17TheWord",
|
||||
"homepage": "https://github.com/17TheWord/nonebot-plugin-mcqq/tree/mcqq_mcrcon",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Minecraft",
|
||||
"color": "#4aea7a"
|
||||
},
|
||||
{
|
||||
"label": "消息互通",
|
||||
"color": "#32ddea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_gsmaterial",
|
||||
"project_link": "nonebot-plugin-gsmaterial",
|
||||
"name": "原神每日材料查询",
|
||||
"desc": "原神每日天赋培养、武器突破材料查询,通过 Project Amber 数据库(支持部分尚未实装角色)自动更新每日数据~",
|
||||
"author": "monsterxcn",
|
||||
"homepage": "https://github.com/monsterxcn/nonebot-plugin-gsmaterial",
|
||||
"tags": [
|
||||
{
|
||||
"label": "t:Genshin",
|
||||
"color": "#ffd49f"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_mystool",
|
||||
"project_link": "nonebot-plugin-mystool",
|
||||
"name": "米游社辅助工具",
|
||||
"desc": "每日完成米游币任务、游戏签到、商品兑换、免抓包登录",
|
||||
"author": "Ljzd-PRO",
|
||||
"homepage": "https://github.com/Ljzd-PRO/nonebot-plugin-mystool",
|
||||
"tags": [
|
||||
{
|
||||
"label": "a:onebot",
|
||||
"color": "#000000"
|
||||
},
|
||||
{
|
||||
"label": "t:米游社",
|
||||
"color": "#66e0ff"
|
||||
},
|
||||
{
|
||||
"label": "t:原神",
|
||||
"color": "#faf3c4"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_warframe",
|
||||
"project_link": "nonebot-plugin-warframe",
|
||||
"name": "星际战甲事件查询",
|
||||
"desc": "基于NoneBot的星际战甲事件查询插件",
|
||||
"author": "17TheWord",
|
||||
"homepage": "https://github.com/17TheWord/nonebot-plugin-warframe",
|
||||
"tags": [
|
||||
{
|
||||
"label": "星际战甲",
|
||||
"color": "#ed3f3f"
|
||||
},
|
||||
{
|
||||
"label": "WarFrame",
|
||||
"color": "#edea3f"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "ETH_Terminal",
|
||||
"project_link": "nonebot2-plugin-eth-terminal",
|
||||
"name": "查询ETH合并日期",
|
||||
"desc": "矿难!!!!",
|
||||
"author": "Sclock",
|
||||
"homepage": "https://github.com/Sclock/nonebot2_plugin_eth_terminal",
|
||||
"tags": [
|
||||
{
|
||||
"label": "爬虫",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "ETH",
|
||||
"color": "#5285ea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_mcqq_server",
|
||||
"project_link": "nonebot-plugin-mcqq-server",
|
||||
"name": "Minecraft Server 聊天同步",
|
||||
"desc": "采用本地读取log信息的方法的Minecraft Server互通消息的插件,mcqq服主版。",
|
||||
"author": "KarisAya",
|
||||
"homepage": "https://github.com/KarisAya/nonebot_plugin_mcqq_server",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Minecraft",
|
||||
"color": "#52ea64"
|
||||
},
|
||||
{
|
||||
"label": "消息互通",
|
||||
"color": "#52e5ea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "GenshinUID",
|
||||
"project_link": "git+https://github.com/KimigaiiWuyi/GenshinUID.git@nonebot2-beta1#egg=GenshinUID",
|
||||
"name": "GenshinUID",
|
||||
"desc": "原神Uid查询/原神Wiki/米社签到/树脂提醒插件",
|
||||
"author": "KimigaiiWuyi",
|
||||
"homepage": "https://github.com/KimigaiiWuyi/GenshinUID/tree/nonebot2-beta1",
|
||||
"tags": [
|
||||
{
|
||||
"label": "原神",
|
||||
"color": "#8edffb"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_RealESRGAN",
|
||||
"project_link": "nonebot_plugin_RealESRGAN",
|
||||
"name": "图像超分辨率重建",
|
||||
"desc": "对图像进行超分辨率重建,可以理解成将图像变大变清晰,不过线上重建的效果没有本地的好,具体可以查阅仓库",
|
||||
"author": "ppxxxg22",
|
||||
"homepage": "https://github.com/ppxxxg22/nonebot_plugin_RealESRGAN",
|
||||
"tags": [
|
||||
{
|
||||
"label": "图像超分辨率重建",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "ayaka",
|
||||
"project_link": "nonebot-plugin-ayaka",
|
||||
"name": "ayaka - 文字游戏开发辅助插件",
|
||||
"desc": "提供了状态机、命令隔离、缓存、固存、帮助指令等功能,可以管理多个文字游戏避免命令冲突,帮助开发者在nonebot2框架基础上快速开发qq文字游戏",
|
||||
"author": "bridgeL",
|
||||
"homepage": "https://github.com/bridgeL/nonebot-plugin-ayaka",
|
||||
"tags": [
|
||||
{
|
||||
"label": "a:cqhttp",
|
||||
"color": "#000000"
|
||||
},
|
||||
{
|
||||
"label": "a:onebot",
|
||||
"color": "#000000"
|
||||
},
|
||||
{
|
||||
"label": "t:文字游戏",
|
||||
"color": "#e36306"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot-plugin-wolf-kill",
|
||||
"project_link": "nonebot-plugin-wolf-kill",
|
||||
"name": "狼人杀插件",
|
||||
"desc": "在群里玩狼人杀",
|
||||
"author": "AbCooly",
|
||||
"homepage": "https://github.com/AbCooly/nonebot_plugin_wolf_kill",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "iot",
|
||||
"project_link": "nonebot-plugin-iot",
|
||||
"name": "nonebot物联网插件",
|
||||
"desc": "为nonebot接入物联网提供方案,目前支持天猫精灵终端,后续会增加",
|
||||
"author": "littlebutt",
|
||||
"homepage": "https://github.com/littlebutt/nonebot-plugin-iot",
|
||||
"tags": [
|
||||
{
|
||||
"label": "t:物联网",
|
||||
"color": "#4b86d7"
|
||||
},
|
||||
{
|
||||
"label": "t:天猫精灵",
|
||||
"color": "#4b86d7"
|
||||
},
|
||||
{
|
||||
"label": "t:IOT",
|
||||
"color": "#4b86d7"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_bwiki_navigator",
|
||||
"project_link": "nonebot-plugin-bwiki-navigator",
|
||||
"name": "BWIKI助手移植版",
|
||||
"desc": "BWIKI助手机器人Nonebot2插件移植版",
|
||||
"author": "XZhouQD",
|
||||
"homepage": "https://github.com/XZhouQD/nonebot-plugin-bwiki-navigator",
|
||||
"tags": [
|
||||
{
|
||||
"label": "a:onebot",
|
||||
"color": "#000000"
|
||||
},
|
||||
{
|
||||
"label": "t:wiki",
|
||||
"color": "#29a5e3"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_bottle",
|
||||
"project_link": "nonebot-plugin-bottle",
|
||||
"name": "漂流瓶",
|
||||
"desc": "群与群互通的漂流瓶插件",
|
||||
"author": "Todysheep",
|
||||
"homepage": "https://github.com/Todysheep/nonebot_plugin_bottle",
|
||||
"tags": [
|
||||
{
|
||||
"label": "漂流瓶",
|
||||
"color": "#0893f2"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_tts_gal",
|
||||
"project_link": "nonebot-plugin-tts-gal",
|
||||
"name": "gal角色语音生成",
|
||||
"desc": "能够根据发送角色名和文本生成对应角色语音",
|
||||
"author": "dpm12345",
|
||||
"homepage": "https://github.com/dpm12345/nonebot_plugin_tts_gal",
|
||||
"tags": [
|
||||
{
|
||||
"label": "VITS",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_alicdk_get",
|
||||
"project_link": "nonebot-plugin-alicdk-get",
|
||||
"name": "阿里云盘福利码自动兑换",
|
||||
"desc": "基于nonebot2与aligo的阿里云盘兑换码自动获取和兑换插件。",
|
||||
"author": "Kaguya233qwq",
|
||||
"homepage": "https://github.com/Kaguya233qwq/nonebot_plugin_alicdk_get",
|
||||
"tags": [
|
||||
{
|
||||
"label": "兑换码",
|
||||
"color": "#595fd6"
|
||||
},
|
||||
{
|
||||
"label": "auto",
|
||||
"color": "#595fd6"
|
||||
},
|
||||
{
|
||||
"label": "t:阿里云盘",
|
||||
"color": "#595fd6"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_picstatus",
|
||||
"project_link": "nonebot-plugin-picstatus",
|
||||
"name": "PicStatus",
|
||||
"desc": "服务器运行状态图片版",
|
||||
"author": "lgc2333",
|
||||
"homepage": "https://github.com/lgc2333/nonebot-plugin-picstatus",
|
||||
"tags": [
|
||||
{
|
||||
"label": "t:server",
|
||||
"color": "#8bff00"
|
||||
},
|
||||
{
|
||||
"label": "a:OB V11",
|
||||
"color": "#ff43c3"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_tuling",
|
||||
"project_link": "nonebot-plugin-tuling",
|
||||
"name": "图灵机器人",
|
||||
"desc": "接入图灵机器人做聊天",
|
||||
"author": "koking0",
|
||||
"homepage": "https://github.com/Matrix-King-Studio/nonebot_plugin_tuling",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_makemidi",
|
||||
"project_link": "nonebot_plugin_makemidi",
|
||||
"name": "在线编曲",
|
||||
"desc": "发送简谱生成音乐",
|
||||
"author": "RandomEnch",
|
||||
"homepage": "https://github.com/RandomEnch/nonebot_plugin_makemidi",
|
||||
"tags": [
|
||||
{
|
||||
"label": "midi",
|
||||
"color": "#6515a8"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_ocr",
|
||||
"project_link": "nonebot-plugin-ocr",
|
||||
"name": "文字识别",
|
||||
"desc": "识别图片中的文字",
|
||||
"author": "NewYearPrism",
|
||||
"homepage": "https://github.com/NewYearPrism/nonebot-plugin-ocr",
|
||||
"tags": [
|
||||
{
|
||||
"label": "ocr ",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
}
|
||||
]
|
@@ -1,106 +0,0 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
description: nonebot.dependencies 模块
|
||||
---
|
||||
|
||||
# nonebot.dependencies
|
||||
|
||||
本模块模块实现了依赖注入的定义与处理。
|
||||
|
||||
## _abstract class_ `Param(default=PydanticUndefined, **kwargs)` {#Param}
|
||||
|
||||
- **说明**
|
||||
|
||||
依赖注入的基本单元 —— 参数。
|
||||
|
||||
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `default` (Any)
|
||||
|
||||
- `**kwargs` (Any)
|
||||
|
||||
## _class_ `Dependent(*, call, pre_checkers=None, params=None, parameterless=None, allow_types=None)` {#Dependent}
|
||||
|
||||
- **说明**
|
||||
|
||||
依赖注入容器
|
||||
|
||||
- **参数**
|
||||
|
||||
- `call` ((\*Any, \*\*Any) -> Any): 依赖注入的可调用对象,可以是任何 Callable 对象
|
||||
|
||||
- `pre_checkers` (list[[Param](#Param)] | None): 依赖注入解析前的参数检查
|
||||
|
||||
- `params` (list[pydantic.fields.ModelField] | None): 具名参数列表
|
||||
|
||||
- `parameterless` (list[[Param](#Param)] | None): 匿名参数列表
|
||||
|
||||
- `allow_types` (list[Type[[Param](#Param)]] | None): 允许的参数类型
|
||||
|
||||
### _method_ `append_parameterless(self, value)` {#Dependent-append_parameterless}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (Any)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _classmethod_ `parse(cls, *, call, parameterless=None, allow_types=None)` {#Dependent-parse}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `call` ((\*Any, \*\*Any) -> Any)
|
||||
|
||||
- `parameterless` (list[Any] | None)
|
||||
|
||||
- `allow_types` (list[Type[[Param](#Param)]] | None)
|
||||
|
||||
- **返回**
|
||||
|
||||
- (~ T)
|
||||
|
||||
### _method_ `parse_param(self, name, param)` {#Dependent-parse_param}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `name` (str)
|
||||
|
||||
- `param` (inspect.Parameter)
|
||||
|
||||
- **返回**
|
||||
|
||||
- [Param](#Param)
|
||||
|
||||
### _method_ `parse_parameterless(self, value)` {#Dependent-parse_parameterless}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (Any)
|
||||
|
||||
- **返回**
|
||||
|
||||
- [Param](#Param)
|
||||
|
||||
### _method_ `prepend_parameterless(self, value)` {#Dependent-prepend_parameterless}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `value` (Any)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _async method_ `solve(self, **params)` {#Dependent-solve}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `**params` (Any)
|
||||
|
||||
- **返回**
|
||||
|
||||
- dict[str, Any]
|
@@ -1,45 +0,0 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
description: nonebot.plugin.export 模块
|
||||
---
|
||||
|
||||
# nonebot.plugin.export
|
||||
|
||||
本模块定义了插件导出的内容对象。
|
||||
|
||||
在新版插件系统中,推荐优先使用直接 import 所需要的插件内容。
|
||||
|
||||
## _class_ `Export()` {#Export}
|
||||
|
||||
- **说明**
|
||||
|
||||
插件导出内容以使得其他插件可以获得。
|
||||
|
||||
- **用法**
|
||||
|
||||
```python
|
||||
nonebot.export().default = "bar"
|
||||
|
||||
@nonebot.export()
|
||||
def some_function():
|
||||
pass
|
||||
|
||||
# this doesn't work before python 3.9
|
||||
# use
|
||||
# export = nonebot.export(); @export.sub
|
||||
# instead
|
||||
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
|
||||
@nonebot.export().sub
|
||||
def something_else():
|
||||
pass
|
||||
```
|
||||
|
||||
## _def_ `export()` {#export}
|
||||
|
||||
- **说明**
|
||||
|
||||
获取当前插件的导出内容对象
|
||||
|
||||
- **返回**
|
||||
|
||||
- [Export](#Export)
|
@@ -8,7 +8,7 @@ slug: /
|
||||
|
||||
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架,它基于 Python 的类型注解和异步特性,能够为你的需求实现提供便捷灵活的支持。
|
||||
|
||||
需要注意的是,NoneBot2 仅支持 **Python 3.7.3 以上版本**
|
||||
需要注意的是,NoneBot2 仅支持 **Python 3.8 以上版本**
|
||||
|
||||
## 特色
|
||||
|
Before Width: | Height: | Size: 376 KiB After Width: | Height: | Size: 376 KiB |
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,98 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
description: nonebot.dependencies 模块
|
||||
---
|
||||
|
||||
# nonebot.dependencies
|
||||
|
||||
本模块模块实现了依赖注入的定义与处理。
|
||||
|
||||
## _abstract class_ `Param(default=PydanticUndefined, **kwargs)` {#Param}
|
||||
|
||||
- **说明**
|
||||
|
||||
依赖注入的基本单元 —— 参数。
|
||||
|
||||
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
|
||||
|
||||
- **参数**
|
||||
|
||||
- `default` (Any)
|
||||
|
||||
- `**kwargs` (Any)
|
||||
|
||||
## _class_ `Dependent(call, params=<factory>, parameterless=<factory>)` {#Dependent}
|
||||
|
||||
- **说明**
|
||||
|
||||
依赖注入容器
|
||||
|
||||
- **参数**
|
||||
|
||||
- `call` ((*Any, \*\*Any) -> (~ R) | (*Any, \*\*Any) -> Awaitable[(~ R)]): 依赖注入的可调用对象,可以是任何 Callable 对象
|
||||
|
||||
- `params` (tuple[pydantic.fields.ModelField]): 具名参数列表
|
||||
|
||||
- `parameterless` (tuple[[Param](#Param)]): 匿名参数列表
|
||||
|
||||
- `pre_checkers`: 依赖注入解析前的参数检查
|
||||
|
||||
- `allow_types`: 允许的参数类型
|
||||
|
||||
### _async method_ `check(self, **params)` {#Dependent-check}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `**params` (Any)
|
||||
|
||||
- **返回**
|
||||
|
||||
- None
|
||||
|
||||
### _classmethod_ `parse(cls, *, call, parameterless=None, allow_types)` {#Dependent-parse}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `call` ((*Any, \*\*Any) -> (~ R) | (*Any, \*\*Any) -> Awaitable[(~ R)])
|
||||
|
||||
- `parameterless` (Iterable[Any] | None)
|
||||
|
||||
- `allow_types` (Iterable[Type[[Param](#Param)]])
|
||||
|
||||
- **返回**
|
||||
|
||||
- Dependent[R]
|
||||
|
||||
### _staticmethod_ `parse_parameterless(parameterless, allow_types)` {#Dependent-parse_parameterless}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `parameterless` (tuple[Any, ...])
|
||||
|
||||
- `allow_types` (tuple[Type[[Param](#Param)], ...])
|
||||
|
||||
- **返回**
|
||||
|
||||
- tuple[[Param](#Param), ...]
|
||||
|
||||
### _staticmethod_ `parse_params(call, allow_types)` {#Dependent-parse_params}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `call` ((*Any, \*\*Any) -> (~ R) | (*Any, \*\*Any) -> Awaitable[(~ R)])
|
||||
|
||||
- `allow_types` (tuple[Type[[Param](#Param)], ...])
|
||||
|
||||
- **返回**
|
||||
|
||||
- tuple[pydantic.fields.ModelField]
|
||||
|
||||
### _async method_ `solve(self, **params)` {#Dependent-solve}
|
||||
|
||||
- **参数**
|
||||
|
||||
- `**params` (Any)
|
||||
|
||||
- **返回**
|
||||
|
||||
- dict[str, Any]
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user