mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 19:26:44 +00:00
Compare commits
139 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a9a86aba61 | ||
|
6e95d5366c | ||
|
445711e1cb | ||
|
dfd2096fe5 | ||
|
d469c6f287 | ||
|
9655b941f3 | ||
|
4254fdfd8c | ||
|
1b3cd7e2e2 | ||
|
897498b7f5 | ||
|
34770e4463 | ||
|
9d14f72249 | ||
|
87f6e81ffc | ||
|
c3373e141a | ||
|
a5f2d97b04 | ||
|
80ac6a5ae9 | ||
|
496475e0ca | ||
|
982dbbccdf | ||
|
3f9c20c60b | ||
|
cabb3c6c45 | ||
|
03bf1fdcfe | ||
|
f36f8d1bcc | ||
|
5c2c1770a2 | ||
|
6810af1e1d | ||
|
78ba6ce973 | ||
|
15bcb7e374 | ||
|
7dd7ccbff5 | ||
|
5b17c8de71 | ||
|
5cf4ff66a3 | ||
|
b6be8a178e | ||
|
b77c3b2d0c | ||
|
e4a210b47c | ||
|
6bf10aafb7 | ||
|
e15d544341 | ||
|
acdb5787db | ||
|
18f0c9b500 | ||
|
b36e721274 | ||
|
9fdc50cd0e | ||
|
41abf077bc | ||
|
27a4e5a55b | ||
|
65f6a104e9 | ||
|
415bd07c0d | ||
|
3fd26dd937 | ||
|
f5f5d93b64 | ||
|
b497bb8c83 | ||
|
b0d554eacb | ||
|
cbecc7b930 | ||
|
5e0921aca9 | ||
|
7e8015e828 | ||
|
bef5bdf0bf | ||
|
c04cd5e83e | ||
|
30d3c1bbce | ||
|
5e72461391 | ||
|
54fdf71d91 | ||
|
420d0cfdc4 | ||
|
84bfba7a82 | ||
|
9fd89a6822 | ||
|
4a02dde83f | ||
|
e93ee1ffec | ||
|
e2b6fb12c7 | ||
|
7836073c7e | ||
|
3119626d89 | ||
|
19bebdd923 | ||
|
0b0dd8b552 | ||
|
47ce7a633f | ||
|
ca32f68787 | ||
|
0b972ad302 | ||
|
9b4b1526b1 | ||
|
7a232c7a4a | ||
|
983351f0b7 | ||
|
16fb5ac121 | ||
|
bb1fbca4a7 | ||
|
b7c0b6b8e0 | ||
|
485aa62755 | ||
|
53e2a86dd9 | ||
|
312095d1df | ||
|
b498be1092 | ||
|
211ea8427f | ||
|
407eb69568 | ||
|
8a44b4d6ee | ||
|
bc58fbb741 | ||
|
0c977f5fd7 | ||
|
7eeccbcb14 | ||
|
020d2a5687 | ||
|
83d61fcffd | ||
|
c0b222a5fa | ||
|
236e4ea9aa | ||
|
0622e16d18 | ||
|
159ca84e46 | ||
|
142a61ce5c | ||
|
7f226af541 | ||
|
8bf912499a | ||
|
a55b10cfa3 | ||
|
1a4f889b40 | ||
|
f9bc2de4e4 | ||
|
5a1c635083 | ||
|
76e8567f1e | ||
|
9bd349d933 | ||
|
5e8a67b605 | ||
|
e16799d500 | ||
|
a189846194 | ||
|
fcd536aada | ||
|
c89bafc2c9 | ||
|
f5855a9f9a | ||
|
a49b4bccc6 | ||
|
b434da29b1 | ||
|
514b3a5afe | ||
|
0d30f81ddb | ||
|
9a86c00f62 | ||
|
7648138902 | ||
|
2055f092f2 | ||
|
9ff7f4baba | ||
|
92ba99c34c | ||
|
876cff4daf | ||
|
1ac6a612b0 | ||
|
15ecad9f87 | ||
|
587d3f7c7e | ||
|
10e4ea6743 | ||
|
d1601bf2fe | ||
|
2994945c64 | ||
|
c9e3cad738 | ||
|
7c36964812 | ||
|
0e02d13c67 | ||
|
f7aeea2f3d | ||
|
b2da7d4cae | ||
|
239f9769c2 | ||
|
f5947518b1 | ||
|
1a4afa406b | ||
|
412b879f39 | ||
|
a830346545 | ||
|
fbb8320a25 | ||
|
14f4a0f701 | ||
|
e82e2817d5 | ||
|
ffbd1f9aeb | ||
|
5ab418a3cf | ||
|
a58e00b206 | ||
|
a74682bbf6 | ||
|
11142253fb | ||
|
ef7782167f | ||
|
f4a2682e6c |
1
.github/actions/setup-python/action.yml
vendored
1
.github/actions/setup-python/action.yml
vendored
@@ -25,7 +25,6 @@ runs:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
architecture: "x64"
|
||||
cache: "poetry"
|
||||
cache-dependency-path: |
|
||||
./poetry.lock
|
||||
|
4
.github/workflows/codecov.yml
vendored
4
.github/workflows/codecov.yml
vendored
@@ -23,11 +23,11 @@ jobs:
|
||||
group: test-coverage-${{ github.ref }}-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.env }}
|
||||
cancel-in-progress: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
env: [pydantic-v1, pydantic-v2]
|
||||
fail-fast: false
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
|
45
.github/workflows/noneflow.yml
vendored
45
.github/workflows/noneflow.yml
vendored
@@ -15,9 +15,10 @@ concurrency:
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
plugin_test:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
name: nonebot2 plugin test
|
||||
name: check
|
||||
# do not run on forked PRs, do not run on not related issues, do not run on pr comments
|
||||
if: |
|
||||
!(
|
||||
(
|
||||
@@ -35,6 +36,46 @@ jobs:
|
||||
github.event_name == 'issue_comment' && github.event.issue.pull_request
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- run: echo "Check passed"
|
||||
reaction:
|
||||
runs-on: ubuntu-latest
|
||||
name: reaction
|
||||
needs: check
|
||||
if: |
|
||||
(
|
||||
github.event_name == 'issue_comment' &&
|
||||
github.event.action == 'created'
|
||||
) ||
|
||||
(
|
||||
github.event_name == 'issues' &&
|
||||
github.event.action == 'opened'
|
||||
)
|
||||
steps:
|
||||
- name: Generate token
|
||||
id: generate-token
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.APP_ID }}
|
||||
private_key: ${{ secrets.APP_KEY }}
|
||||
|
||||
- name: Reaction on issue
|
||||
if: github.event_name == 'issues'
|
||||
run: |
|
||||
gh api --method POST /repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/reactions -f "content=rocket"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- name: Reaction on issue comment
|
||||
if: github.event_name == 'issue_comment'
|
||||
run: |
|
||||
gh api --method POST /repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions -f "content=rocket"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
plugin_test:
|
||||
runs-on: ubuntu-latest
|
||||
name: nonebot2 plugin test
|
||||
needs: check
|
||||
permissions:
|
||||
issues: read
|
||||
outputs:
|
||||
|
2
.github/workflows/website-deploy.yml
vendored
2
.github/workflows/website-deploy.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV
|
||||
|
||||
- name: Deploy to Netlify
|
||||
uses: nwtgck/actions-netlify@v2
|
||||
uses: nwtgck/actions-netlify@v3
|
||||
with:
|
||||
publish-dir: "./website/build"
|
||||
production-deploy: true
|
||||
|
2
.github/workflows/website-preview.yml
vendored
2
.github/workflows/website-preview.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
echo "DEPLOY_NAME=deploy-preview-${{ github.event.number }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Deploy to Netlify
|
||||
uses: nwtgck/actions-netlify@v2
|
||||
uses: nwtgck/actions-netlify@v3
|
||||
with:
|
||||
publish-dir: "./website/build"
|
||||
production-deploy: false
|
||||
|
@@ -7,7 +7,7 @@ ci:
|
||||
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.2.0
|
||||
rev: v0.4.2
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
@@ -20,7 +20,7 @@ repos:
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 24.1.1
|
||||
rev: 24.4.2
|
||||
hooks:
|
||||
- id: black
|
||||
stages: [commit]
|
||||
|
10
README.md
10
README.md
@@ -21,7 +21,7 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
||||
<a href="https://pypi.python.org/pypi/nonebot2">
|
||||
<img src="https://img.shields.io/pypi/v/nonebot2?logo=python&logoColor=edb641" alt="pypi">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/python-3.8+-blue?logo=python&logoColor=edb641" alt="python">
|
||||
<img src="https://img.shields.io/badge/python-3.9+-blue?logo=python&logoColor=edb641" alt="python">
|
||||
<a href="https://github.com/psf/black">
|
||||
<img src="https://img.shields.io/badge/code%20style-black-000000.svg?logo=python&logoColor=edb641" alt="black">
|
||||
</a>
|
||||
@@ -111,25 +111,27 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
||||
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
|
||||
|
||||
| 协议名称 | 状态 | 注释 |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------: | :--: | :-----------------------------------------------------------------------: |
|
||||
| :-------------------------------------------------------------------------------------------------------------------: | :--: | :-----------------------------------------------------------------------: |
|
||||
| OneBot([仓库](https://github.com/nonebot/adapter-onebot),[协议](https://onebot.dev/)) | ✅ | 支持 QQ、TG、微信公众号、KOOK 等[平台](https://onebot.dev/ecosystem.html) |
|
||||
| Telegram([仓库](https://github.com/nonebot/adapter-telegram),[协议](https://core.telegram.org/bots/api)) | ✅ | |
|
||||
| 飞书([仓库](https://github.com/nonebot/adapter-feishu),[协议](https://open.feishu.cn/document/home/index)) | ✅ | |
|
||||
| GitHub([仓库](https://github.com/nonebot/adapter-github),[协议](https://docs.github.com/en/apps)) | ✅ | GitHub APP & OAuth APP |
|
||||
| QQ([仓库](https://github.com/nonebot/adapter-qq),[协议](https://bot.q.qq.com/wiki/)) | ✅ | QQ 官方接口调整较多 |
|
||||
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
||||
| Console([仓库](https://github.com/nonebot/adapter-console)) | ✅ | 控制台交互 |
|
||||
| Red([仓库](https://github.com/nonebot/adapter-red),[协议](https://chrononeko.github.io/QQNTRedProtocol/)) | ✅ | QQ 协议 |
|
||||
| Satori([仓库](https://github.com/nonebot/adapter-satori),[协议](https://satori.js.org/zh-CN)) | ✅ | 支持 Onebot、TG、飞书、微信公众号、Koishi 等 |
|
||||
| Discord([仓库](https://github.com/nonebot/adapter-discord),[协议](https://discord.com/developers/docs/intro)) | ✅ | Discord Bot 协议 |
|
||||
| DoDo([仓库](https://github.com/nonebot/adapter-dodo),[协议](https://open.imdodo.com/)) | ✅ | DoDo Bot 协议 |
|
||||
| Kritor([仓库](https://github.com/nonebot/adapter-kritor),[协议](https://github.com/KarinJS/kritor)) | ✅ | Kritor (OnebotX) 协议,QQ 机器人接口标准 |
|
||||
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
||||
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila),[协议](https://developer.kookapp.cn/)) | ↗️ | 由社区贡献 |
|
||||
| Mirai([仓库](https://github.com/ieew/nonebot_adapter_mirai2),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ↗️ | QQ 协议,由社区贡献 |
|
||||
| Ntchat([仓库](https://github.com/JustUndertaker/adapter-ntchat)) | ↗️ | 微信协议,由社区贡献 |
|
||||
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
||||
| BiliBili Live([仓库](https://github.com/wwweww/adapter-bilibili)) | ↗️ | 由社区贡献 |
|
||||
| Walle-Q([仓库](https://github.com/onebot-walle/nonebot_adapter_walleq)) | ↗️ | QQ 协议,由社区贡献 |
|
||||
| Villa([仓库](https://github.com/CMHopeSunshine/nonebot-adapter-villa),[协议](https://webstatic.mihoyo.com/vila/bot/doc/)) | ↗️ | 米游社大别野 Bot 协议,由社区贡献 |
|
||||
| Villa([仓库](https://github.com/CMHopeSunshine/nonebot-adapter-villa)) | ❌ | 米游社大别野 Bot 协议,官方已下线 |
|
||||
| Rocket.Chat([仓库](https://github.com/IUnlimit/nonebot-adapter-rocketchat),[协议](https://developer.rocket.chat/)) | ↗️ | Rocket.Chat Bot 协议,由社区贡献 |
|
||||
|
||||
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
||||
|
||||
|
@@ -213,5 +213,30 @@
|
||||
"homepage": "https://github.com/nonebot/adapter-dodo",
|
||||
"tags": [],
|
||||
"is_official": true
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapters.rocketchat",
|
||||
"project_link": "nonebot-adapter-rocketchat",
|
||||
"name": "RocketChat",
|
||||
"desc": "RocketChat adapter for nonebot2",
|
||||
"author": "IllTamer",
|
||||
"homepage": "https://github.com/IUnlimit/nonebot-adapter-rocketchat",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapters.kritor",
|
||||
"project_link": "nonebot-adapter-kritor",
|
||||
"name": "Kritor",
|
||||
"desc": "Kritor 协议适配",
|
||||
"author": "RF-Tar-Railt",
|
||||
"homepage": "https://github.com/nonebot/adapter-kritor",
|
||||
"tags": [
|
||||
{
|
||||
"label": "QQNT",
|
||||
"color": "#35a7c9"
|
||||
}
|
||||
],
|
||||
"is_official": true
|
||||
}
|
||||
]
|
||||
|
@@ -4203,13 +4203,6 @@
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_eitherchoice",
|
||||
"project_link": "nonebot-plugin-eitherchoice",
|
||||
"author": "lgc2333",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_poke",
|
||||
"project_link": "nonebot-plugin-poke",
|
||||
@@ -5288,8 +5281,8 @@
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_phigros_qq",
|
||||
"project_link": "nonebot-plugin-phigros-qq",
|
||||
"module_name": "nonebot_plugin_phigros",
|
||||
"project_link": "nonebot-plugin-phigros",
|
||||
"author": "XTxiaoting14332",
|
||||
"tags": [
|
||||
{
|
||||
@@ -5500,5 +5493,415 @@
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_chikari_yinpa",
|
||||
"project_link": "nonebot-plugin-chikari-yinpa",
|
||||
"author": "mrqx0195",
|
||||
"tags": [
|
||||
{
|
||||
"label": "yinpa",
|
||||
"color": "#ffff00"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_splatoon3_nso",
|
||||
"project_link": "nonebot-plugin-splatoon3-nso",
|
||||
"author": "Cypas",
|
||||
"tags": [
|
||||
{
|
||||
"label": "splatoon3",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_bf1marneserverlist",
|
||||
"project_link": "nonebot-plugin-bf1marneserverlist",
|
||||
"author": "SAFEluren",
|
||||
"tags": [
|
||||
{
|
||||
"label": "server",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_kawaii_status",
|
||||
"project_link": "nonebot-plugin-kawaii-status",
|
||||
"author": "KomoriDev",
|
||||
"tags": [
|
||||
{
|
||||
"label": "简约",
|
||||
"color": "#54adff"
|
||||
},
|
||||
{
|
||||
"label": "可爱",
|
||||
"color": "#ffb3cc"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_vits_tts",
|
||||
"project_link": "nonebot-plugin-vits-tts",
|
||||
"author": "Redmomn",
|
||||
"tags": [
|
||||
{
|
||||
"label": "VITS",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "TTS",
|
||||
"color": "#52dbea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_chatglm_plus",
|
||||
"project_link": "nonebot-plugin-chatglm-plus",
|
||||
"author": "XTxiaoting14332",
|
||||
"tags": [
|
||||
{
|
||||
"label": "ChatGLM",
|
||||
"color": "#73cccc"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_fishing",
|
||||
"project_link": "nonebot-plugin-fishing",
|
||||
"author": "C14H22O",
|
||||
"tags": [
|
||||
{
|
||||
"label": "钓鱼",
|
||||
"color": "#87cefa"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_a2s_query",
|
||||
"project_link": "nonebot-plugin-a2s-query",
|
||||
"author": "NanakaNeko",
|
||||
"tags": [
|
||||
{
|
||||
"label": "游戏服务器",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "value",
|
||||
"color": "#99ea52"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_dice_narrator",
|
||||
"project_link": "nonebot-plugin-dice-narrator",
|
||||
"author": "KroMiose",
|
||||
"tags": [
|
||||
{
|
||||
"label": "GPT",
|
||||
"color": "#29b752"
|
||||
},
|
||||
{
|
||||
"label": "掷骰姬",
|
||||
"color": "#c84b9d"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_steam_info",
|
||||
"project_link": "nonebot-plugin-steam-info",
|
||||
"author": "zhaomaoniu",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Steam",
|
||||
"color": "#14305e"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_orangejuice",
|
||||
"project_link": "nonebot-plugin-orangejuice",
|
||||
"author": "GLDYM",
|
||||
"tags": [
|
||||
{
|
||||
"label": "百橙",
|
||||
"color": "#ed6f00"
|
||||
},
|
||||
{
|
||||
"label": "100OJ",
|
||||
"color": "#ed6f00"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_md",
|
||||
"project_link": "nonebot_plugin_md",
|
||||
"author": "Agnes4m",
|
||||
"tags": [
|
||||
{
|
||||
"label": "muse dash",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_duel",
|
||||
"project_link": "nonebot-plugin-duel",
|
||||
"author": "Redmomn",
|
||||
"tags": [
|
||||
{
|
||||
"label": "决斗",
|
||||
"color": "#5be7d1"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_pallas_repeater",
|
||||
"project_link": "nonebot-plugin-pallas-repeater",
|
||||
"author": "Redmomn",
|
||||
"tags": [
|
||||
{
|
||||
"label": "复读鸡",
|
||||
"color": "#52eae7"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_humanaticstore",
|
||||
"project_link": "nonebot-plugin-humanaticstore",
|
||||
"author": "QuanhuZeYu",
|
||||
"tags": [
|
||||
{
|
||||
"label": "config",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "配置工具",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_ghtiles",
|
||||
"project_link": "nonebot-plugin-ghtiles",
|
||||
"author": "Lipraty",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Github",
|
||||
"color": "#2a2a2a"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_diffsinger",
|
||||
"project_link": "nonebot-plugin-diffsinger",
|
||||
"author": "zhzhongshi",
|
||||
"tags": [
|
||||
{
|
||||
"label": "diffsinger",
|
||||
"color": "#c24444"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_chikari_economy",
|
||||
"project_link": "nonebot-plugin-chikari-economy",
|
||||
"author": "mrqx0195",
|
||||
"tags": [
|
||||
{
|
||||
"label": "经济",
|
||||
"color": "#adad73"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_auto_bot_selector",
|
||||
"project_link": "nonebot-plugin-auto-bot-selector",
|
||||
"author": "Well2333",
|
||||
"tags": [
|
||||
{
|
||||
"label": "多适配器",
|
||||
"color": "#5280ea"
|
||||
},
|
||||
{
|
||||
"label": "跨平台",
|
||||
"color": "#5452ea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_nai3",
|
||||
"project_link": "nonebot-plugin-nai3",
|
||||
"author": "zhulinyv",
|
||||
"tags": [
|
||||
{
|
||||
"label": "NovelAI",
|
||||
"color": "#35f139"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_clovers",
|
||||
"project_link": "nonebot-plugin-clovers",
|
||||
"author": "KarisAya",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_hx_yinying",
|
||||
"project_link": "nonebot-plugin-hx-yinying",
|
||||
"author": "huanxin996",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Chat",
|
||||
"color": "#3b53ff"
|
||||
},
|
||||
{
|
||||
"label": "幻歆!",
|
||||
"color": "#0d7ccd"
|
||||
},
|
||||
{
|
||||
"label": "银影!",
|
||||
"color": "#2b4dae"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_fhl",
|
||||
"project_link": "nonebot-plugin-fhl",
|
||||
"author": "baiqwerdvd",
|
||||
"tags": [
|
||||
{
|
||||
"label": "飞花令",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_yinyu",
|
||||
"project_link": "nonebot-plugin-yinyu",
|
||||
"author": "shi-yingyingjiang",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_yinying_chat",
|
||||
"project_link": "nonebot-plugin-yinying-chat",
|
||||
"author": "YuxiCN",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Furry",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "银影",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_dcqg_relay",
|
||||
"project_link": "nonebot-plugin-dcqg-relay",
|
||||
"author": "Autuamn",
|
||||
"tags": [
|
||||
{
|
||||
"label": "消息互通",
|
||||
"color": "#52beea"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_zsmeme",
|
||||
"project_link": "nonebot-plugin-zsmeme",
|
||||
"author": "shi-yingyingjiang",
|
||||
"tags": [
|
||||
{
|
||||
"label": "帕弥什",
|
||||
"color": "#ea5252"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_sanyao",
|
||||
"project_link": "nonebot-plugin-sanyao",
|
||||
"author": "afterow",
|
||||
"tags": [
|
||||
{
|
||||
"label": "占卜",
|
||||
"color": "#415656"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_cyberfurry",
|
||||
"project_link": "nonebot-plugin-cyberfurry",
|
||||
"author": "cubstaryow",
|
||||
"tags": [
|
||||
{
|
||||
"label": "幼龙云端",
|
||||
"color": "#ccc719"
|
||||
},
|
||||
{
|
||||
"label": "银影",
|
||||
"color": "#af2af3"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_helpwithpic",
|
||||
"project_link": "nonebot-plugin-HelpWithPic",
|
||||
"author": "cubstaryow",
|
||||
"tags": [
|
||||
{
|
||||
"label": "帮助",
|
||||
"color": "#c61b1b"
|
||||
},
|
||||
{
|
||||
"label": "图片生成",
|
||||
"color": "#c61b1b"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_plugin_sticker_saver",
|
||||
"project_link": "nonebot-plugin-sticker-saver",
|
||||
"author": "colasama",
|
||||
"tags": [
|
||||
{
|
||||
"label": "表情包",
|
||||
"color": "#f1ce15"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
}
|
||||
]
|
||||
|
572
envs/pydantic-v1/poetry.lock
generated
572
envs/pydantic-v1/poetry.lock
generated
@@ -1,14 +1,14 @@
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiodns"
|
||||
version = "3.1.1"
|
||||
version = "3.2.0"
|
||||
description = "Simple DNS resolver for asyncio"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "aiodns-3.1.1-py3-none-any.whl", hash = "sha256:a387b63da4ced6aad35b1dda2d09620ad608a1c7c0fb71efa07ebb4cd511928d"},
|
||||
{file = "aiodns-3.1.1.tar.gz", hash = "sha256:1073eac48185f7a4150cad7f96a5192d6911f12b4fb894de80a088508c9b3a99"},
|
||||
{file = "aiodns-3.2.0-py3-none-any.whl", hash = "sha256:e443c0c27b07da3174a109fd9e736d69058d808f144d3c9d56dbd1776964c5f5"},
|
||||
{file = "aiodns-3.2.0.tar.gz", hash = "sha256:62869b23409349c21b072883ec8998316b234c9a9e36675756e8e317e8768f72"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -27,87 +27,87 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
version = "3.9.3"
|
||||
version = "3.9.5"
|
||||
description = "Async http client/server framework (asyncio)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"},
|
||||
{file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"},
|
||||
{file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"},
|
||||
{file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"},
|
||||
{file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"},
|
||||
{file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"},
|
||||
{file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"},
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"},
|
||||
{file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"},
|
||||
{file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"},
|
||||
{file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"},
|
||||
{file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"},
|
||||
{file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -140,13 +140,13 @@ frozenlist = ">=1.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.2.0"
|
||||
version = "4.3.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"},
|
||||
{file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"},
|
||||
{file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
|
||||
{file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -162,13 +162,13 @@ trio = ["trio (>=0.23)"]
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.7.2"
|
||||
version = "3.8.1"
|
||||
description = "ASGI specs, helper code, and adapters"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"},
|
||||
{file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"},
|
||||
{file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
|
||||
{file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -223,13 +223,13 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p
|
||||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.7.0"
|
||||
version = "1.8.1"
|
||||
description = "Fast, simple object-to-object and broadcast signaling"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"},
|
||||
{file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"},
|
||||
{file = "blinker-1.8.1-py3-none-any.whl", hash = "sha256:5f1cdeff423b77c31b89de0565cd03e5275a03028f44b2b15f912632a58cced6"},
|
||||
{file = "blinker-1.8.1.tar.gz", hash = "sha256:da44ec748222dcd0105ef975eed946da197d5bdf8bafb6aa92f5bc89da63fa25"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -564,63 +564,63 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.4.1"
|
||||
version = "7.5.0"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"},
|
||||
{file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"},
|
||||
{file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:432949a32c3e3f820af808db1833d6d1631664d53dd3ce487aa25d574e18ad1c"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2bd7065249703cbeb6d4ce679c734bef0ee69baa7bff9724361ada04a15b7e3b"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbfe6389c5522b99768a93d89aca52ef92310a96b99782973b9d11e80511f932"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39793731182c4be939b4be0cdecde074b833f6171313cf53481f869937129ed3"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a5dbe1ba1bf38d6c63b6d2c42132d45cbee6d9f0c51b52c59aa4afba057517"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:357754dcdfd811462a725e7501a9b4556388e8ecf66e79df6f4b988fa3d0b39a"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a81eb64feded34f40c8986869a2f764f0fe2db58c0530d3a4afbcde50f314880"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:51431d0abbed3a868e967f8257c5faf283d41ec882f58413cf295a389bb22e58"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-win32.whl", hash = "sha256:f609ebcb0242d84b7adeee2b06c11a2ddaec5464d21888b2c8255f5fd6a98ae4"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:6782cd6216fab5a83216cc39f13ebe30adfac2fa72688c5a4d8d180cd52e8f6a"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e768d870801f68c74c2b669fc909839660180c366501d4cc4b87efd6b0eee375"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:84921b10aeb2dd453247fd10de22907984eaf80901b578a5cf0bb1e279a587cb"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710c62b6e35a9a766b99b15cdc56d5aeda0914edae8bb467e9c355f75d14ee95"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c379cdd3efc0658e652a14112d51a7668f6bfca7445c5a10dee7eabecabba19d"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea9d3ca80bcf17edb2c08a4704259dadac196fe5e9274067e7a20511fad1743"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:41327143c5b1d715f5f98a397608f90ab9ebba606ae4e6f3389c2145410c52b1"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:565b2e82d0968c977e0b0f7cbf25fd06d78d4856289abc79694c8edcce6eb2de"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf3539007202ebfe03923128fedfdd245db5860a36810136ad95a564a2fdffff"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-win32.whl", hash = "sha256:bf0b4b8d9caa8d64df838e0f8dcf68fb570c5733b726d1494b87f3da85db3a2d"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c6384cc90e37cfb60435bbbe0488444e54b98700f727f16f64d8bfda0b84656"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fed7a72d54bd52f4aeb6c6e951f363903bd7d70bc1cad64dd1f087980d309ab9"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cbe6581fcff7c8e262eb574244f81f5faaea539e712a058e6707a9d272fe5b64"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad97ec0da94b378e593ef532b980c15e377df9b9608c7c6da3506953182398af"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd4bacd62aa2f1a1627352fe68885d6ee694bdaebb16038b6e680f2924a9b2cc"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf032b6c105881f9d77fa17d9eebe0ad1f9bfb2ad25777811f97c5362aa07f2"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ba01d9ba112b55bfa4b24808ec431197bb34f09f66f7cb4fd0258ff9d3711b1"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f0bfe42523893c188e9616d853c47685e1c575fe25f737adf473d0405dcfa7eb"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a9a7ef30a1b02547c1b23fa9a5564f03c9982fc71eb2ecb7f98c96d7a0db5cf2"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-win32.whl", hash = "sha256:3c2b77f295edb9fcdb6a250f83e6481c679335ca7e6e4a955e4290350f2d22a4"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:427e1e627b0963ac02d7c8730ca6d935df10280d230508c0ba059505e9233475"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dd88fce54abbdbf4c42fb1fea0e498973d07816f24c0e27a1ecaf91883ce69e"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a898c11dca8f8c97b467138004a30133974aacd572818c383596f8d5b2eb04a9"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07dfdd492d645eea1bd70fb1d6febdcf47db178b0d99161d8e4eed18e7f62fe7"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3d117890b6eee85887b1eed41eefe2e598ad6e40523d9f94c4c4b213258e4a4"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6afd2e84e7da40fe23ca588379f815fb6dbbb1b757c883935ed11647205111cb"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a9960dd1891b2ddf13a7fe45339cd59ecee3abb6b8326d8b932d0c5da208104f"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ced268e82af993d7801a9db2dbc1d2322e786c5dc76295d8e89473d46c6b84d4"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7c211f25777746d468d76f11719e64acb40eed410d81c26cefac641975beb88"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-win32.whl", hash = "sha256:262fffc1f6c1a26125d5d573e1ec379285a3723363f3bd9c83923c9593a2ac25"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:eed462b4541c540d63ab57b3fc69e7d8c84d5957668854ee4e408b50e92ce26a"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0194d654e360b3e6cc9b774e83235bae6b9b2cac3be09040880bb0e8a88f4a1"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33c020d3322662e74bc507fb11488773a96894aa82a622c35a5a28673c0c26f5"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbdf2cae14a06827bec50bd58e49249452d211d9caddd8bd80e35b53cb04631"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3235d7c781232e525b0761730e052388a01548bd7f67d0067a253887c6e8df46"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2de4e546f0ec4b2787d625e0b16b78e99c3e21bc1722b4977c0dddf11ca84e"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0e206259b73af35c4ec1319fd04003776e11e859936658cb6ceffdeba0f5be"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2055c4fb9a6ff624253d432aa471a37202cd8f458c033d6d989be4499aed037b"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075299460948cd12722a970c7eae43d25d37989da682997687b34ae6b87c0ef0"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-win32.whl", hash = "sha256:280132aada3bc2f0fac939a5771db4fbb84f245cb35b94fae4994d4c1f80dae7"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:c58536f6892559e030e6924896a44098bc1290663ea12532c78cef71d0df8493"},
|
||||
{file = "coverage-7.5.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:2b57780b51084d5223eee7b59f0d4911c31c16ee5aa12737c7a02455829ff067"},
|
||||
{file = "coverage-7.5.0.tar.gz", hash = "sha256:cf62d17310f34084c59c01e027259076479128d11e4661bb6c9acb38c5e19bb8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -647,13 +647,13 @@ packaging = ">=20.4"
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
|
||||
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
|
||||
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
|
||||
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -661,13 +661,13 @@ test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "execnet"
|
||||
version = "2.0.2"
|
||||
version = "2.1.1"
|
||||
description = "execnet: rapid multi-Python deployment"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"},
|
||||
{file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"},
|
||||
{file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
|
||||
{file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -675,32 +675,32 @@ testing = ["hatch", "pre-commit", "pytest", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.109.2"
|
||||
version = "0.110.3"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "fastapi-0.109.2-py3-none-any.whl", hash = "sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d"},
|
||||
{file = "fastapi-0.109.2.tar.gz", hash = "sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73"},
|
||||
{file = "fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32"},
|
||||
{file = "fastapi-0.110.3.tar.gz", hash = "sha256:555700b0159379e94fdbfc6bb66a0f1c43f4cf7060f25239af3d84b63a656626"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
||||
starlette = ">=0.36.3,<0.37.0"
|
||||
starlette = ">=0.37.2,<0.38.0"
|
||||
typing-extensions = ">=4.8.0"
|
||||
|
||||
[package.extras]
|
||||
all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||
all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "flask"
|
||||
version = "3.0.2"
|
||||
version = "3.0.3"
|
||||
description = "A simple framework for building complex web applications."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "flask-3.0.2-py3-none-any.whl", hash = "sha256:3232e0e9c850d781933cf0207523d1ece087eb8d87b23777ae38456e2fbe7c6e"},
|
||||
{file = "flask-3.0.2.tar.gz", hash = "sha256:822c03f4b799204250a7ee84b1eddc40665395333973dfb9deebfe425fefcb7d"},
|
||||
{file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"},
|
||||
{file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -840,13 +840,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.2"
|
||||
version = "1.0.5"
|
||||
description = "A minimal low-level HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"},
|
||||
{file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"},
|
||||
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
|
||||
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -857,7 +857,7 @@ h11 = ">=0.13,<0.15"
|
||||
asyncio = ["anyio (>=4.0,<5.0)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
trio = ["trio (>=0.22.0,<0.23.0)"]
|
||||
trio = ["trio (>=0.22.0,<0.26.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
@@ -909,13 +909,13 @@ test = ["Cython (>=0.29.24,<0.30.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.26.0"
|
||||
version = "0.27.0"
|
||||
description = "The next generation HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"},
|
||||
{file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"},
|
||||
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
|
||||
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -970,33 +970,33 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.6"
|
||||
version = "3.7"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
|
||||
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
|
||||
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
|
||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "7.0.1"
|
||||
version = "7.1.0"
|
||||
description = "Read metadata from Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"},
|
||||
{file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"},
|
||||
{file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"},
|
||||
{file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
perf = ["ipython"]
|
||||
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
|
||||
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
@@ -1011,13 +1011,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.1.2"
|
||||
version = "2.2.0"
|
||||
description = "Safely pass data to untrusted environments and back."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
|
||||
{file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
|
||||
{file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
|
||||
{file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1228,15 +1228,15 @@ name = "nonebot-test"
|
||||
version = "0.1.0"
|
||||
description = "Private test env for nonebot"
|
||||
optional = false
|
||||
python-versions = "^3.8"
|
||||
python-versions = "^3.9"
|
||||
files = []
|
||||
develop = false
|
||||
|
||||
[package.dependencies]
|
||||
coverage-conditional-plugin = "^0.9.0"
|
||||
nonebug = "^0.3.0"
|
||||
nonebug = "^0.3.7"
|
||||
pytest-asyncio = "^0.23.2"
|
||||
pytest-cov = "^4.0.0"
|
||||
pytest-cov = "^5.0.0"
|
||||
pytest-xdist = "^3.0.2"
|
||||
werkzeug = ">=2.3.6,<4.0.0"
|
||||
wsproto = "^1.2.0"
|
||||
@@ -1247,10 +1247,10 @@ url = "../test"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot2"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
description = "An asynchronous python bot framework."
|
||||
optional = false
|
||||
python-versions = "^3.8"
|
||||
python-versions = "^3.9"
|
||||
files = []
|
||||
develop = true
|
||||
|
||||
@@ -1283,42 +1283,42 @@ url = "../.."
|
||||
|
||||
[[package]]
|
||||
name = "nonebug"
|
||||
version = "0.3.5"
|
||||
version = "0.3.7"
|
||||
description = "nonebot2 test framework"
|
||||
optional = false
|
||||
python-versions = ">=3.8,<4.0"
|
||||
python-versions = "<4.0,>=3.8"
|
||||
files = [
|
||||
{file = "nonebug-0.3.5-py3-none-any.whl", hash = "sha256:588831b08b3ea42d058874214bedae646e2ab8c1ec4ae1540ff789873107a8fa"},
|
||||
{file = "nonebug-0.3.5.tar.gz", hash = "sha256:4d4bf9448cd1cbfaaabaab73dbe4ac8757e86dd92a41ef79cdece8dd61e724e2"},
|
||||
{file = "nonebug-0.3.7-py3-none-any.whl", hash = "sha256:c39f462aafe20660602a8b789a575db6c9346ab5b6f1985eb9d98b861528299a"},
|
||||
{file = "nonebug-0.3.7.tar.gz", hash = "sha256:8a75183400681f34eafc7caa2bb6dd511c3b5660c59264f1c379a088c7ac2247"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
asgiref = ">=3.4.0,<4.0.0"
|
||||
async-asgi-testclient = ">=1.4.8,<2.0.0"
|
||||
nonebot2 = ">=2.0.0-rc.2,<3.0.0"
|
||||
pytest = ">=7.0.0,<8.0.0"
|
||||
nonebot2 = ">=2.2.0,<3.0.0"
|
||||
pytest = ">=7.0.0,<9.0.0"
|
||||
typing-extensions = ">=4.0.0,<5.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "23.2"
|
||||
version = "24.0"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
|
||||
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
||||
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
|
||||
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
|
||||
{file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
|
||||
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
||||
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -1404,58 +1404,58 @@ idna = ["idna (>=2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.21"
|
||||
version = "2.22"
|
||||
description = "C parser in Python"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
|
||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
||||
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.10.14"
|
||||
version = "1.10.15"
|
||||
description = "Data validation and settings management using python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"},
|
||||
{file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"},
|
||||
{file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"},
|
||||
{file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"},
|
||||
{file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"},
|
||||
{file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"},
|
||||
{file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"},
|
||||
{file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"},
|
||||
{file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"},
|
||||
{file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"},
|
||||
{file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"},
|
||||
{file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"},
|
||||
{file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"},
|
||||
{file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"},
|
||||
{file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"},
|
||||
{file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"},
|
||||
{file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"},
|
||||
{file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"},
|
||||
{file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"},
|
||||
{file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"},
|
||||
{file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"},
|
||||
{file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"},
|
||||
{file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"},
|
||||
{file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"},
|
||||
{file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"},
|
||||
{file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"},
|
||||
{file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"},
|
||||
{file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"},
|
||||
{file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"},
|
||||
{file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"},
|
||||
{file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"},
|
||||
{file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"},
|
||||
{file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"},
|
||||
{file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"},
|
||||
{file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"},
|
||||
{file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"},
|
||||
{file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"},
|
||||
{file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"},
|
||||
{file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1478,13 +1478,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.4.4"
|
||||
version = "8.2.0"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
|
||||
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
|
||||
{file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"},
|
||||
{file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1492,25 +1492,25 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||
pluggy = ">=1.5,<2.0"
|
||||
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-asyncio"
|
||||
version = "0.23.4"
|
||||
version = "0.23.6"
|
||||
description = "Pytest support for asyncio"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-asyncio-0.23.4.tar.gz", hash = "sha256:2143d9d9375bf372a73260e4114541485e84fca350b0b6b92674ca56ff5f7ea2"},
|
||||
{file = "pytest_asyncio-0.23.4-py3-none-any.whl", hash = "sha256:b0079dfac14b60cd1ce4691fbfb1748fe939db7d0234b5aba97197d10fbe0fef"},
|
||||
{file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"},
|
||||
{file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=7.0.0,<8"
|
||||
pytest = ">=7.0.0,<9"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
|
||||
@@ -1518,13 +1518,13 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "4.1.0"
|
||||
version = "5.0.0"
|
||||
description = "Pytest plugin for measuring coverage."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
|
||||
{file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
|
||||
{file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"},
|
||||
{file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1532,22 +1532,22 @@ coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||
pytest = ">=4.6"
|
||||
|
||||
[package.extras]
|
||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-xdist"
|
||||
version = "3.5.0"
|
||||
version = "3.6.1"
|
||||
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"},
|
||||
{file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"},
|
||||
{file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
|
||||
{file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
execnet = ">=1.1"
|
||||
pytest = ">=6.2.0"
|
||||
execnet = ">=2.1"
|
||||
pytest = ">=7.0.0"
|
||||
|
||||
[package.extras]
|
||||
psutil = ["psutil (>=3.0)"]
|
||||
@@ -1630,13 +1630,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "quart"
|
||||
version = "0.19.4"
|
||||
version = "0.19.5"
|
||||
description = "A Python ASGI web microframework with the same API as Flask"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "quart-0.19.4-py3-none-any.whl", hash = "sha256:959da9371b44b6f48d952661863f8f64e68a893481ef3f2ef45b177629dc0928"},
|
||||
{file = "quart-0.19.4.tar.gz", hash = "sha256:22ff186cf164955a7bf7483ff42a739a9fad3b119041846b15dc9597ec74c85c"},
|
||||
{file = "quart-0.19.5-py3-none-any.whl", hash = "sha256:581d959bda40d3c45500c50007a6451a157fd381c70d3556811bdd334adb9657"},
|
||||
{file = "quart-0.19.5.tar.gz", hash = "sha256:fbe3cff25cd18b5c0e8d82bbeeaa43d78f35e5221ca5c50bb0b7c20255c87ab8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1679,24 +1679,24 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
|
||||
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
|
||||
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
|
||||
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.36.3"
|
||||
version = "0.37.2"
|
||||
description = "The little ASGI library that shines."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "starlette-0.36.3-py3-none-any.whl", hash = "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044"},
|
||||
{file = "starlette-0.36.3.tar.gz", hash = "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080"},
|
||||
{file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"},
|
||||
{file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1733,24 +1733,24 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.9.0"
|
||||
version = "4.11.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
|
||||
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
|
||||
{file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
|
||||
{file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.0"
|
||||
version = "2.2.1"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"},
|
||||
{file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"},
|
||||
{file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"},
|
||||
{file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -1761,13 +1761,13 @@ zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.27.0.post1"
|
||||
version = "0.29.0"
|
||||
description = "The lightning-fast ASGI server."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "uvicorn-0.27.0.post1-py3-none-any.whl", hash = "sha256:4b85ba02b8a20429b9b205d015cbeb788a12da527f731811b643fd739ef90d5f"},
|
||||
{file = "uvicorn-0.27.0.post1.tar.gz", hash = "sha256:54898fcd80c13ff1cd28bf77b04ec9dbd8ff60c5259b499b4b12bb0917f22907"},
|
||||
{file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"},
|
||||
{file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1999,13 +1999,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.0.1"
|
||||
version = "3.0.2"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
|
||||
{file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
|
||||
{file = "werkzeug-3.0.2-py3-none-any.whl", hash = "sha256:3aac3f5da756f93030740bc235d3e09449efcf65f2f55e3602e1d851b8f48795"},
|
||||
{file = "werkzeug-3.0.2.tar.gz", hash = "sha256:e39b645a6ac92822588e7b39a692e7828724ceae0b0d702ef96701f90e70128d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2147,20 +2147,20 @@ multidict = ">=4.0"
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.17.0"
|
||||
version = "3.18.1"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
|
||||
{file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
|
||||
{file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"},
|
||||
{file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "16419a2a03690fd222b2f8702b2a9a94bf1755bba24ba2336bb95cd41c6d625f"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "f094ed8c9ed4aec70ad2498bb5133928c28e45fab952e9acbe5553a433fcaa3b"
|
||||
|
@@ -6,7 +6,7 @@ authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||
license = "MIT"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
python = "^3.9"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pydantic = "^1.0.0"
|
||||
|
671
envs/pydantic-v2/poetry.lock
generated
671
envs/pydantic-v2/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||
license = "MIT"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
python = "^3.9"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pydantic = "^2.0.0"
|
||||
|
409
envs/test/poetry.lock
generated
409
envs/test/poetry.lock
generated
@@ -1,14 +1,25 @@
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.6.0"
|
||||
description = "Reusable constraint types to use with typing.Annotated"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
|
||||
{file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.7.2"
|
||||
version = "3.8.1"
|
||||
description = "ASGI specs, helper code, and adapters"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"},
|
||||
{file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"},
|
||||
{file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
|
||||
{file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -154,63 +165,63 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.4.1"
|
||||
version = "7.5.0"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"},
|
||||
{file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"},
|
||||
{file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:432949a32c3e3f820af808db1833d6d1631664d53dd3ce487aa25d574e18ad1c"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2bd7065249703cbeb6d4ce679c734bef0ee69baa7bff9724361ada04a15b7e3b"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbfe6389c5522b99768a93d89aca52ef92310a96b99782973b9d11e80511f932"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39793731182c4be939b4be0cdecde074b833f6171313cf53481f869937129ed3"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a5dbe1ba1bf38d6c63b6d2c42132d45cbee6d9f0c51b52c59aa4afba057517"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:357754dcdfd811462a725e7501a9b4556388e8ecf66e79df6f4b988fa3d0b39a"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a81eb64feded34f40c8986869a2f764f0fe2db58c0530d3a4afbcde50f314880"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:51431d0abbed3a868e967f8257c5faf283d41ec882f58413cf295a389bb22e58"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-win32.whl", hash = "sha256:f609ebcb0242d84b7adeee2b06c11a2ddaec5464d21888b2c8255f5fd6a98ae4"},
|
||||
{file = "coverage-7.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:6782cd6216fab5a83216cc39f13ebe30adfac2fa72688c5a4d8d180cd52e8f6a"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e768d870801f68c74c2b669fc909839660180c366501d4cc4b87efd6b0eee375"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:84921b10aeb2dd453247fd10de22907984eaf80901b578a5cf0bb1e279a587cb"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710c62b6e35a9a766b99b15cdc56d5aeda0914edae8bb467e9c355f75d14ee95"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c379cdd3efc0658e652a14112d51a7668f6bfca7445c5a10dee7eabecabba19d"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea9d3ca80bcf17edb2c08a4704259dadac196fe5e9274067e7a20511fad1743"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:41327143c5b1d715f5f98a397608f90ab9ebba606ae4e6f3389c2145410c52b1"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:565b2e82d0968c977e0b0f7cbf25fd06d78d4856289abc79694c8edcce6eb2de"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf3539007202ebfe03923128fedfdd245db5860a36810136ad95a564a2fdffff"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-win32.whl", hash = "sha256:bf0b4b8d9caa8d64df838e0f8dcf68fb570c5733b726d1494b87f3da85db3a2d"},
|
||||
{file = "coverage-7.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c6384cc90e37cfb60435bbbe0488444e54b98700f727f16f64d8bfda0b84656"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fed7a72d54bd52f4aeb6c6e951f363903bd7d70bc1cad64dd1f087980d309ab9"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cbe6581fcff7c8e262eb574244f81f5faaea539e712a058e6707a9d272fe5b64"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad97ec0da94b378e593ef532b980c15e377df9b9608c7c6da3506953182398af"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd4bacd62aa2f1a1627352fe68885d6ee694bdaebb16038b6e680f2924a9b2cc"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf032b6c105881f9d77fa17d9eebe0ad1f9bfb2ad25777811f97c5362aa07f2"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ba01d9ba112b55bfa4b24808ec431197bb34f09f66f7cb4fd0258ff9d3711b1"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f0bfe42523893c188e9616d853c47685e1c575fe25f737adf473d0405dcfa7eb"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a9a7ef30a1b02547c1b23fa9a5564f03c9982fc71eb2ecb7f98c96d7a0db5cf2"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-win32.whl", hash = "sha256:3c2b77f295edb9fcdb6a250f83e6481c679335ca7e6e4a955e4290350f2d22a4"},
|
||||
{file = "coverage-7.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:427e1e627b0963ac02d7c8730ca6d935df10280d230508c0ba059505e9233475"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dd88fce54abbdbf4c42fb1fea0e498973d07816f24c0e27a1ecaf91883ce69e"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a898c11dca8f8c97b467138004a30133974aacd572818c383596f8d5b2eb04a9"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07dfdd492d645eea1bd70fb1d6febdcf47db178b0d99161d8e4eed18e7f62fe7"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3d117890b6eee85887b1eed41eefe2e598ad6e40523d9f94c4c4b213258e4a4"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6afd2e84e7da40fe23ca588379f815fb6dbbb1b757c883935ed11647205111cb"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a9960dd1891b2ddf13a7fe45339cd59ecee3abb6b8326d8b932d0c5da208104f"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ced268e82af993d7801a9db2dbc1d2322e786c5dc76295d8e89473d46c6b84d4"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7c211f25777746d468d76f11719e64acb40eed410d81c26cefac641975beb88"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-win32.whl", hash = "sha256:262fffc1f6c1a26125d5d573e1ec379285a3723363f3bd9c83923c9593a2ac25"},
|
||||
{file = "coverage-7.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:eed462b4541c540d63ab57b3fc69e7d8c84d5957668854ee4e408b50e92ce26a"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0194d654e360b3e6cc9b774e83235bae6b9b2cac3be09040880bb0e8a88f4a1"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33c020d3322662e74bc507fb11488773a96894aa82a622c35a5a28673c0c26f5"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbdf2cae14a06827bec50bd58e49249452d211d9caddd8bd80e35b53cb04631"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3235d7c781232e525b0761730e052388a01548bd7f67d0067a253887c6e8df46"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2de4e546f0ec4b2787d625e0b16b78e99c3e21bc1722b4977c0dddf11ca84e"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0e206259b73af35c4ec1319fd04003776e11e859936658cb6ceffdeba0f5be"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2055c4fb9a6ff624253d432aa471a37202cd8f458c033d6d989be4499aed037b"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075299460948cd12722a970c7eae43d25d37989da682997687b34ae6b87c0ef0"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-win32.whl", hash = "sha256:280132aada3bc2f0fac939a5771db4fbb84f245cb35b94fae4994d4c1f80dae7"},
|
||||
{file = "coverage-7.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:c58536f6892559e030e6924896a44098bc1290663ea12532c78cef71d0df8493"},
|
||||
{file = "coverage-7.5.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:2b57780b51084d5223eee7b59f0d4911c31c16ee5aa12737c7a02455829ff067"},
|
||||
{file = "coverage-7.5.0.tar.gz", hash = "sha256:cf62d17310f34084c59c01e027259076479128d11e4661bb6c9acb38c5e19bb8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -237,13 +248,13 @@ packaging = ">=20.4"
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
|
||||
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
|
||||
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
|
||||
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -251,13 +262,13 @@ test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "execnet"
|
||||
version = "2.0.2"
|
||||
version = "2.1.1"
|
||||
description = "execnet: rapid multi-Python deployment"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"},
|
||||
{file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"},
|
||||
{file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
|
||||
{file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -276,33 +287,33 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.6"
|
||||
version = "3.7"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
|
||||
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
|
||||
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
|
||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "7.0.1"
|
||||
version = "7.1.0"
|
||||
description = "Read metadata from Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"},
|
||||
{file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"},
|
||||
{file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"},
|
||||
{file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
perf = ["ipython"]
|
||||
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
|
||||
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
@@ -503,19 +514,20 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "nonebot2"
|
||||
version = "2.1.3"
|
||||
version = "2.2.1"
|
||||
description = "An asynchronous python bot framework."
|
||||
optional = false
|
||||
python-versions = ">=3.8,<4.0"
|
||||
files = [
|
||||
{file = "nonebot2-2.1.3-py3-none-any.whl", hash = "sha256:c36c1a60ce4355d9777fee431c08619f22ffd60f7060993fbbbd1fe67b6368f7"},
|
||||
{file = "nonebot2-2.1.3.tar.gz", hash = "sha256:e750e615f1ad2503721ce055fbe55ec3b061277135d995be112fecd27f7232e5"},
|
||||
{file = "nonebot2-2.2.1-py3-none-any.whl", hash = "sha256:88f2bb456bf90922925bbe489a9effe3b09300f3aa50bfa75ee50d8a83d7330f"},
|
||||
{file = "nonebot2-2.2.1.tar.gz", hash = "sha256:fe57692300571b00724999238545d8d894523460e6835a11b326a2e1cdf98fc4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
loguru = ">=0.6.0,<1.0.0"
|
||||
pydantic = {version = ">=1.10.0,<2.0.0", extras = ["dotenv"]}
|
||||
pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<3.0.0"
|
||||
pygtrie = ">=2.4.1,<3.0.0"
|
||||
python-dotenv = ">=0.21.0,<2.0.0"
|
||||
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = ">=4.4.0,<5.0.0"
|
||||
yarl = ">=1.7.2,<2.0.0"
|
||||
@@ -530,42 +542,42 @@ websockets = ["websockets (>=10.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "nonebug"
|
||||
version = "0.3.5"
|
||||
version = "0.3.7"
|
||||
description = "nonebot2 test framework"
|
||||
optional = false
|
||||
python-versions = ">=3.8,<4.0"
|
||||
python-versions = "<4.0,>=3.8"
|
||||
files = [
|
||||
{file = "nonebug-0.3.5-py3-none-any.whl", hash = "sha256:588831b08b3ea42d058874214bedae646e2ab8c1ec4ae1540ff789873107a8fa"},
|
||||
{file = "nonebug-0.3.5.tar.gz", hash = "sha256:4d4bf9448cd1cbfaaabaab73dbe4ac8757e86dd92a41ef79cdece8dd61e724e2"},
|
||||
{file = "nonebug-0.3.7-py3-none-any.whl", hash = "sha256:c39f462aafe20660602a8b789a575db6c9346ab5b6f1985eb9d98b861528299a"},
|
||||
{file = "nonebug-0.3.7.tar.gz", hash = "sha256:8a75183400681f34eafc7caa2bb6dd511c3b5660c59264f1c379a088c7ac2247"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
asgiref = ">=3.4.0,<4.0.0"
|
||||
async-asgi-testclient = ">=1.4.8,<2.0.0"
|
||||
nonebot2 = ">=2.0.0-rc.2,<3.0.0"
|
||||
pytest = ">=7.0.0,<8.0.0"
|
||||
nonebot2 = ">=2.2.0,<3.0.0"
|
||||
pytest = ">=7.0.0,<9.0.0"
|
||||
typing-extensions = ">=4.0.0,<5.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "23.2"
|
||||
version = "24.0"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
|
||||
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
||||
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
|
||||
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
|
||||
{file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
|
||||
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
||||
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -574,56 +586,113 @@ testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.10.14"
|
||||
description = "Data validation and settings management using python type hints"
|
||||
version = "2.7.1"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"},
|
||||
{file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"},
|
||||
{file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"},
|
||||
{file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"},
|
||||
{file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
python-dotenv = {version = ">=0.10.4", optional = true, markers = "extra == \"dotenv\""}
|
||||
typing-extensions = ">=4.2.0"
|
||||
annotated-types = ">=0.4.0"
|
||||
pydantic-core = "2.18.2"
|
||||
typing-extensions = ">=4.6.1"
|
||||
|
||||
[package.extras]
|
||||
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||
email = ["email-validator (>=1.0.3)"]
|
||||
email = ["email-validator (>=2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.18.2"
|
||||
description = "Core functionality for Pydantic validation and serialization"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"},
|
||||
{file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"},
|
||||
{file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"},
|
||||
{file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"},
|
||||
{file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"},
|
||||
{file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"},
|
||||
{file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"},
|
||||
{file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"},
|
||||
{file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"},
|
||||
{file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"},
|
||||
{file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"},
|
||||
{file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"},
|
||||
{file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"},
|
||||
{file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "pygtrie"
|
||||
@@ -638,13 +707,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.4.4"
|
||||
version = "8.2.0"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
|
||||
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
|
||||
{file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"},
|
||||
{file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -652,25 +721,25 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||
pluggy = ">=1.5,<2.0"
|
||||
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-asyncio"
|
||||
version = "0.23.4"
|
||||
version = "0.23.6"
|
||||
description = "Pytest support for asyncio"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-asyncio-0.23.4.tar.gz", hash = "sha256:2143d9d9375bf372a73260e4114541485e84fca350b0b6b92674ca56ff5f7ea2"},
|
||||
{file = "pytest_asyncio-0.23.4-py3-none-any.whl", hash = "sha256:b0079dfac14b60cd1ce4691fbfb1748fe939db7d0234b5aba97197d10fbe0fef"},
|
||||
{file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"},
|
||||
{file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=7.0.0,<8"
|
||||
pytest = ">=7.0.0,<9"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
|
||||
@@ -678,13 +747,13 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "4.1.0"
|
||||
version = "5.0.0"
|
||||
description = "Pytest plugin for measuring coverage."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
|
||||
{file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
|
||||
{file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"},
|
||||
{file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -692,22 +761,22 @@ coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||
pytest = ">=4.6"
|
||||
|
||||
[package.extras]
|
||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-xdist"
|
||||
version = "3.5.0"
|
||||
version = "3.6.1"
|
||||
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"},
|
||||
{file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"},
|
||||
{file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
|
||||
{file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
execnet = ">=1.1"
|
||||
pytest = ">=6.2.0"
|
||||
execnet = ">=2.1"
|
||||
pytest = ">=7.0.0"
|
||||
|
||||
[package.extras]
|
||||
psutil = ["psutil (>=3.0)"]
|
||||
@@ -762,24 +831,24 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.9.0"
|
||||
version = "4.11.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
|
||||
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
|
||||
{file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
|
||||
{file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.0"
|
||||
version = "2.2.1"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"},
|
||||
{file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"},
|
||||
{file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"},
|
||||
{file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -790,13 +859,13 @@ zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.0.1"
|
||||
version = "3.0.2"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
|
||||
{file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
|
||||
{file = "werkzeug-3.0.2-py3-none-any.whl", hash = "sha256:3aac3f5da756f93030740bc235d3e09449efcf65f2f55e3602e1d851b8f48795"},
|
||||
{file = "werkzeug-3.0.2.tar.gz", hash = "sha256:e39b645a6ac92822588e7b39a692e7828724ceae0b0d702ef96701f90e70128d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -938,20 +1007,20 @@ multidict = ">=4.0"
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.17.0"
|
||||
version = "3.18.1"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
|
||||
{file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
|
||||
{file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"},
|
||||
{file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "ab5729309587cb130ac7848e8862368995372cf2fc91d0966598b3c6b49028e5"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "ee7bfccee3250456dbe0b9f11914fbbc290b83abdc1a82ae6f4c3525c2818416"
|
||||
|
@@ -7,10 +7,10 @@ license = "MIT"
|
||||
packages = [{ include = "nonebot-test.py" }]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
nonebug = "^0.3.0"
|
||||
python = "^3.9"
|
||||
nonebug = "^0.3.7"
|
||||
wsproto = "^1.2.0"
|
||||
pytest-cov = "^4.0.0"
|
||||
pytest-cov = "^5.0.0"
|
||||
pytest-xdist = "^3.0.2"
|
||||
pytest-asyncio = "^0.23.2"
|
||||
werkzeug = ">=2.3.6,<4.0.0"
|
||||
|
@@ -45,10 +45,11 @@ FrontMatter:
|
||||
|
||||
import os
|
||||
from importlib.metadata import version
|
||||
from typing import Any, Dict, Type, Union, TypeVar, Optional, overload
|
||||
from typing import Any, Union, TypeVar, Optional, overload
|
||||
|
||||
import loguru
|
||||
|
||||
from nonebot.compat import model_dump
|
||||
from nonebot.log import logger as logger
|
||||
from nonebot.adapters import Bot, Adapter
|
||||
from nonebot.config import DOTENV_TYPE, Env, Config
|
||||
@@ -99,7 +100,7 @@ def get_adapter(name: str) -> Adapter:
|
||||
|
||||
|
||||
@overload
|
||||
def get_adapter(name: Type[A]) -> A:
|
||||
def get_adapter(name: type[A]) -> A:
|
||||
"""
|
||||
参数:
|
||||
name: 适配器类型
|
||||
@@ -109,7 +110,7 @@ def get_adapter(name: Type[A]) -> A:
|
||||
"""
|
||||
|
||||
|
||||
def get_adapter(name: Union[str, Type[Adapter]]) -> Adapter:
|
||||
def get_adapter(name: Union[str, type[Adapter]]) -> Adapter:
|
||||
"""获取已注册的 {ref}`nonebot.adapters.Adapter` 实例。
|
||||
|
||||
异常:
|
||||
@@ -130,7 +131,7 @@ def get_adapter(name: Union[str, Type[Adapter]]) -> Adapter:
|
||||
return adapters[target]
|
||||
|
||||
|
||||
def get_adapters() -> Dict[str, Adapter]:
|
||||
def get_adapters() -> dict[str, Adapter]:
|
||||
"""获取所有已注册的 {ref}`nonebot.adapters.Adapter` 实例。
|
||||
|
||||
返回:
|
||||
@@ -229,7 +230,7 @@ def get_bot(self_id: Optional[str] = None) -> Bot:
|
||||
raise ValueError("There are no bots to get.")
|
||||
|
||||
|
||||
def get_bots() -> Dict[str, Bot]:
|
||||
def get_bots() -> dict[str, Bot]:
|
||||
"""获取所有连接到 NoneBot 的 {ref}`nonebot.adapters.Bot` 对象。
|
||||
|
||||
返回:
|
||||
@@ -248,7 +249,7 @@ def get_bots() -> Dict[str, Bot]:
|
||||
return get_driver().bots
|
||||
|
||||
|
||||
def _resolve_combine_expr(obj_str: str) -> Type[Driver]:
|
||||
def _resolve_combine_expr(obj_str: str) -> type[Driver]:
|
||||
drivers = obj_str.split("+")
|
||||
DriverClass = resolve_dot_notation(
|
||||
drivers[0], "Driver", default_prefix="nonebot.drivers."
|
||||
@@ -265,11 +266,12 @@ def _resolve_combine_expr(obj_str: str) -> Type[Driver]:
|
||||
|
||||
|
||||
def _log_patcher(record: "loguru.Record"):
|
||||
"""使用插件标识优化日志展示"""
|
||||
record["name"] = (
|
||||
plugin.name
|
||||
plugin.id_
|
||||
if (module_name := record["name"])
|
||||
and (plugin := get_plugin_by_module_name(module_name))
|
||||
else (module_name and module_name.split(".")[0])
|
||||
else (module_name and module_name.split(".", maxsplit=1)[0])
|
||||
)
|
||||
|
||||
|
||||
@@ -310,7 +312,7 @@ def init(*, _env_file: Optional[DOTENV_TYPE] = None, **kwargs: Any) -> None:
|
||||
f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>"
|
||||
)
|
||||
logger.opt(colors=True).debug(
|
||||
f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}"
|
||||
f"Loaded <y><b>Config</b></y>: {escape_tag(str(model_dump(config)))}"
|
||||
)
|
||||
|
||||
DriverClass = _resolve_combine_expr(config.driver)
|
||||
|
@@ -7,20 +7,18 @@ FrontMatter:
|
||||
description: nonebot.compat 模块
|
||||
"""
|
||||
|
||||
from collections.abc import Generator
|
||||
from dataclasses import dataclass, is_dataclass
|
||||
from typing_extensions import Self, Annotated, get_args, get_origin, is_typeddict
|
||||
from typing_extensions import Self, get_args, get_origin, is_typeddict
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Set,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Union,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Optional,
|
||||
Protocol,
|
||||
Generator,
|
||||
Annotated,
|
||||
)
|
||||
|
||||
from pydantic import VERSION, BaseModel
|
||||
@@ -54,6 +52,7 @@ __all__ = (
|
||||
"model_config",
|
||||
"model_dump",
|
||||
"type_validate_python",
|
||||
"type_validate_json",
|
||||
"custom_validation",
|
||||
)
|
||||
|
||||
@@ -92,7 +91,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
super().__init__(default=default, **kwargs)
|
||||
|
||||
@property
|
||||
def extra(self) -> Dict[str, Any]:
|
||||
def extra(self) -> dict[str, Any]:
|
||||
"""Extra data that is not part of the standard pydantic fields.
|
||||
|
||||
For compatibility with pydantic v1.
|
||||
@@ -158,7 +157,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
# to allow store them in a set.
|
||||
return id(self)
|
||||
|
||||
def extract_field_info(field_info: BaseFieldInfo) -> Dict[str, Any]:
|
||||
def extract_field_info(field_info: BaseFieldInfo) -> dict[str, Any]:
|
||||
"""Get FieldInfo init kwargs from a FieldInfo instance."""
|
||||
|
||||
kwargs = field_info._attributes_set.copy()
|
||||
@@ -174,7 +173,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
type, config=None if model_field._annotation_has_config() else config
|
||||
).validate_python(value)
|
||||
|
||||
def model_fields(model: Type[BaseModel]) -> List[ModelField]:
|
||||
def model_fields(model: type[BaseModel]) -> list[ModelField]:
|
||||
"""Get field list of a model."""
|
||||
|
||||
return [
|
||||
@@ -186,23 +185,38 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
for name, field_info in model.model_fields.items()
|
||||
]
|
||||
|
||||
def model_config(model: Type[BaseModel]) -> Any:
|
||||
def model_config(model: type[BaseModel]) -> Any:
|
||||
"""Get config of a model."""
|
||||
return model.model_config
|
||||
|
||||
def model_dump(
|
||||
model: BaseModel,
|
||||
include: Optional[Set[str]] = None,
|
||||
exclude: Optional[Set[str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
return model.model_dump(include=include, exclude=exclude)
|
||||
include: Optional[set[str]] = None,
|
||||
exclude: Optional[set[str]] = None,
|
||||
by_alias: bool = False,
|
||||
exclude_unset: bool = False,
|
||||
exclude_defaults: bool = False,
|
||||
exclude_none: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return model.model_dump(
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
by_alias=by_alias,
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_defaults=exclude_defaults,
|
||||
exclude_none=exclude_none,
|
||||
)
|
||||
|
||||
def type_validate_python(type_: Type[T], data: Any) -> T:
|
||||
def type_validate_python(type_: type[T], data: Any) -> T:
|
||||
"""Validate data with given type."""
|
||||
return TypeAdapter(type_).validate_python(data)
|
||||
|
||||
def type_validate_json(type_: type[T], data: Union[str, bytes]) -> T:
|
||||
"""Validate JSON with given type."""
|
||||
return TypeAdapter(type_).validate_json(data)
|
||||
|
||||
def __get_pydantic_core_schema__(
|
||||
cls: Type["_CustomValidationClass"],
|
||||
cls: type["_CustomValidationClass"],
|
||||
source_type: Any,
|
||||
handler: GetCoreSchemaHandler,
|
||||
) -> CoreSchema:
|
||||
@@ -213,7 +227,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
[core_schema.no_info_plain_validator_function(func) for func in validators]
|
||||
)
|
||||
|
||||
def custom_validation(class_: Type["CVC"]) -> Type["CVC"]:
|
||||
def custom_validation(class_: type["CVC"]) -> type["CVC"]:
|
||||
"""Use pydantic v1 like validator generator in pydantic v2"""
|
||||
|
||||
setattr(
|
||||
@@ -225,7 +239,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
|
||||
else: # pragma: pydantic-v1
|
||||
from pydantic import Extra
|
||||
from pydantic import parse_obj_as
|
||||
from pydantic import parse_obj_as, parse_raw_as
|
||||
from pydantic import BaseConfig as PydanticConfig
|
||||
from pydantic.fields import FieldInfo as BaseFieldInfo
|
||||
from pydantic.fields import ModelField as BaseModelField
|
||||
@@ -291,7 +305,7 @@ else: # pragma: pydantic-v1
|
||||
)
|
||||
return cls._construct(name, annotation, field_info or FieldInfo())
|
||||
|
||||
def extract_field_info(field_info: BaseFieldInfo) -> Dict[str, Any]:
|
||||
def extract_field_info(field_info: BaseFieldInfo) -> dict[str, Any]:
|
||||
"""Get FieldInfo init kwargs from a FieldInfo instance."""
|
||||
|
||||
kwargs = {
|
||||
@@ -301,7 +315,7 @@ else: # pragma: pydantic-v1
|
||||
return kwargs
|
||||
|
||||
def model_field_validate(
|
||||
model_field: ModelField, value: Any, config: Optional[Type[ConfigDict]] = None
|
||||
model_field: ModelField, value: Any, config: Optional[type[ConfigDict]] = None
|
||||
) -> Any:
|
||||
"""Validate the value pass to the field.
|
||||
|
||||
@@ -316,7 +330,7 @@ else: # pragma: pydantic-v1
|
||||
raise ValueError(value, model_field)
|
||||
return v
|
||||
|
||||
def model_fields(model: Type[BaseModel]) -> List[ModelField]:
|
||||
def model_fields(model: type[BaseModel]) -> list[ModelField]:
|
||||
"""Get field list of a model."""
|
||||
|
||||
# construct the model field without preprocess to avoid error
|
||||
@@ -331,21 +345,36 @@ else: # pragma: pydantic-v1
|
||||
for model_field in model.__fields__.values()
|
||||
]
|
||||
|
||||
def model_config(model: Type[BaseModel]) -> Any:
|
||||
def model_config(model: type[BaseModel]) -> Any:
|
||||
"""Get config of a model."""
|
||||
return model.__config__
|
||||
|
||||
def model_dump(
|
||||
model: BaseModel,
|
||||
include: Optional[Set[str]] = None,
|
||||
exclude: Optional[Set[str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
return model.dict(include=include, exclude=exclude)
|
||||
include: Optional[set[str]] = None,
|
||||
exclude: Optional[set[str]] = None,
|
||||
by_alias: bool = False,
|
||||
exclude_unset: bool = False,
|
||||
exclude_defaults: bool = False,
|
||||
exclude_none: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return model.dict(
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
by_alias=by_alias,
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_defaults=exclude_defaults,
|
||||
exclude_none=exclude_none,
|
||||
)
|
||||
|
||||
def type_validate_python(type_: Type[T], data: Any) -> T:
|
||||
def type_validate_python(type_: type[T], data: Any) -> T:
|
||||
"""Validate data with given type."""
|
||||
return parse_obj_as(type_, data)
|
||||
|
||||
def custom_validation(class_: Type["CVC"]) -> Type["CVC"]:
|
||||
def type_validate_json(type_: type[T], data: Union[str, bytes]) -> T:
|
||||
"""Validate JSON with given type."""
|
||||
return parse_raw_as(type_, data)
|
||||
|
||||
def custom_validation(class_: type["CVC"]) -> type["CVC"]:
|
||||
"""Do nothing in pydantic v1"""
|
||||
return class_
|
||||
|
@@ -17,19 +17,9 @@ import json
|
||||
from pathlib import Path
|
||||
from datetime import timedelta
|
||||
from ipaddress import IPv4Address
|
||||
from collections.abc import Mapping
|
||||
from typing import TYPE_CHECKING, Any, Union, Optional
|
||||
from typing_extensions import TypeAlias, get_args, get_origin
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Set,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Mapping,
|
||||
Optional,
|
||||
)
|
||||
|
||||
from dotenv import dotenv_values
|
||||
from pydantic import Field, BaseModel
|
||||
@@ -49,7 +39,7 @@ from nonebot.compat import (
|
||||
)
|
||||
|
||||
DOTENV_TYPE: TypeAlias = Union[
|
||||
Path, str, List[Union[Path, str]], Tuple[Union[Path, str], ...]
|
||||
Path, str, list[Union[Path, str]], tuple[Union[Path, str], ...]
|
||||
]
|
||||
|
||||
ENV_FILE_SENTINEL = Path("")
|
||||
@@ -59,7 +49,7 @@ class SettingsError(ValueError): ...
|
||||
|
||||
|
||||
class BaseSettingsSource(abc.ABC):
|
||||
def __init__(self, settings_cls: Type["BaseSettings"]) -> None:
|
||||
def __init__(self, settings_cls: type["BaseSettings"]) -> None:
|
||||
self.settings_cls = settings_cls
|
||||
|
||||
@property
|
||||
@@ -67,7 +57,7 @@ class BaseSettingsSource(abc.ABC):
|
||||
return model_config(self.settings_cls)
|
||||
|
||||
@abc.abstractmethod
|
||||
def __call__(self) -> Dict[str, Any]:
|
||||
def __call__(self) -> dict[str, Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -75,12 +65,12 @@ class InitSettingsSource(BaseSettingsSource):
|
||||
__slots__ = ("init_kwargs",)
|
||||
|
||||
def __init__(
|
||||
self, settings_cls: Type["BaseSettings"], init_kwargs: Dict[str, Any]
|
||||
self, settings_cls: type["BaseSettings"], init_kwargs: dict[str, Any]
|
||||
) -> None:
|
||||
self.init_kwargs = init_kwargs
|
||||
super().__init__(settings_cls)
|
||||
|
||||
def __call__(self) -> Dict[str, Any]:
|
||||
def __call__(self) -> dict[str, Any]:
|
||||
return self.init_kwargs
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -90,7 +80,7 @@ class InitSettingsSource(BaseSettingsSource):
|
||||
class DotEnvSettingsSource(BaseSettingsSource):
|
||||
def __init__(
|
||||
self,
|
||||
settings_cls: Type["BaseSettings"],
|
||||
settings_cls: type["BaseSettings"],
|
||||
env_file: Optional[DOTENV_TYPE] = ENV_FILE_SENTINEL,
|
||||
env_file_encoding: Optional[str] = None,
|
||||
case_sensitive: Optional[bool] = None,
|
||||
@@ -121,7 +111,7 @@ class DotEnvSettingsSource(BaseSettingsSource):
|
||||
def _apply_case_sensitive(self, var_name: str) -> str:
|
||||
return var_name if self.case_sensitive else var_name.lower()
|
||||
|
||||
def _field_is_complex(self, field: ModelField) -> Tuple[bool, bool]:
|
||||
def _field_is_complex(self, field: ModelField) -> tuple[bool, bool]:
|
||||
if type_is_complex(field.annotation):
|
||||
return True, False
|
||||
elif origin_is_union(get_origin(field.annotation)) and any(
|
||||
@@ -132,16 +122,16 @@ class DotEnvSettingsSource(BaseSettingsSource):
|
||||
|
||||
def _parse_env_vars(
|
||||
self, env_vars: Mapping[str, Optional[str]]
|
||||
) -> Dict[str, Optional[str]]:
|
||||
) -> dict[str, Optional[str]]:
|
||||
return {
|
||||
self._apply_case_sensitive(key): value for key, value in env_vars.items()
|
||||
}
|
||||
|
||||
def _read_env_file(self, file_path: Path) -> Dict[str, Optional[str]]:
|
||||
def _read_env_file(self, file_path: Path) -> dict[str, Optional[str]]:
|
||||
file_vars = dotenv_values(file_path, encoding=self.env_file_encoding)
|
||||
return self._parse_env_vars(file_vars)
|
||||
|
||||
def _read_env_files(self) -> Dict[str, Optional[str]]:
|
||||
def _read_env_files(self) -> dict[str, Optional[str]]:
|
||||
env_files = self.env_file
|
||||
if env_files is None:
|
||||
return {}
|
||||
@@ -149,7 +139,7 @@ class DotEnvSettingsSource(BaseSettingsSource):
|
||||
if isinstance(env_files, (str, os.PathLike)):
|
||||
env_files = [env_files]
|
||||
|
||||
dotenv_vars: Dict[str, Optional[str]] = {}
|
||||
dotenv_vars: dict[str, Optional[str]] = {}
|
||||
for env_file in env_files:
|
||||
env_path = Path(env_file).expanduser()
|
||||
if env_path.is_file():
|
||||
@@ -170,14 +160,14 @@ class DotEnvSettingsSource(BaseSettingsSource):
|
||||
def _explode_env_vars(
|
||||
self,
|
||||
field: ModelField,
|
||||
env_vars: Dict[str, Optional[str]],
|
||||
env_file_vars: Dict[str, Optional[str]],
|
||||
) -> Dict[str, Any]:
|
||||
env_vars: dict[str, Optional[str]],
|
||||
env_file_vars: dict[str, Optional[str]],
|
||||
) -> dict[str, Any]:
|
||||
if self.env_nested_delimiter is None:
|
||||
return {}
|
||||
|
||||
prefix = f"{field.name}{self.env_nested_delimiter}"
|
||||
result: Dict[str, Any] = {}
|
||||
result: dict[str, Any] = {}
|
||||
for env_name, env_val in env_vars.items():
|
||||
if not env_name.startswith(prefix):
|
||||
continue
|
||||
@@ -209,10 +199,10 @@ class DotEnvSettingsSource(BaseSettingsSource):
|
||||
|
||||
return result
|
||||
|
||||
def __call__(self) -> Dict[str, Any]:
|
||||
def __call__(self) -> dict[str, Any]:
|
||||
"""从环境变量和 dotenv 配置文件中读取配置项。"""
|
||||
|
||||
d: Dict[str, Any] = {}
|
||||
d: dict[str, Any] = {}
|
||||
|
||||
env_vars = self._parse_env_vars(os.environ)
|
||||
env_file_vars = self._read_env_files()
|
||||
@@ -317,7 +307,7 @@ class BaseSettings(BaseModel):
|
||||
return self.__dict__.get(name)
|
||||
|
||||
if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
model_config: SettingsConfig = SettingsConfig(
|
||||
model_config = SettingsConfig(
|
||||
extra="allow",
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
@@ -351,11 +341,11 @@ class BaseSettings(BaseModel):
|
||||
|
||||
def _settings_build_values(
|
||||
self,
|
||||
init_kwargs: Dict[str, Any],
|
||||
init_kwargs: dict[str, Any],
|
||||
env_file: Optional[DOTENV_TYPE] = None,
|
||||
env_file_encoding: Optional[str] = None,
|
||||
env_nested_delimiter: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
init_settings = InitSettingsSource(self.__class__, init_kwargs=init_kwargs)
|
||||
env_settings = DotEnvSettingsSource(
|
||||
self.__class__,
|
||||
@@ -426,7 +416,7 @@ class Config(BaseSettings):
|
||||
"""API 请求超时时间,单位: 秒。"""
|
||||
|
||||
# bot runtime configs
|
||||
superusers: Set[str] = set()
|
||||
superusers: set[str] = set()
|
||||
"""机器人超级用户。
|
||||
|
||||
用法:
|
||||
@@ -434,9 +424,9 @@ class Config(BaseSettings):
|
||||
SUPERUSERS=["12345789"]
|
||||
```
|
||||
"""
|
||||
nickname: Set[str] = set()
|
||||
nickname: set[str] = set()
|
||||
"""机器人昵称。"""
|
||||
command_start: Set[str] = {"/"}
|
||||
command_start: set[str] = {"/"}
|
||||
"""命令的起始标记,用于判断一条消息是不是命令。
|
||||
|
||||
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
|
||||
@@ -446,7 +436,7 @@ class Config(BaseSettings):
|
||||
COMMAND_START=["/", ""]
|
||||
```
|
||||
"""
|
||||
command_sep: Set[str] = {"."}
|
||||
command_sep: set[str] = {"."}
|
||||
"""命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
|
||||
|
||||
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
|
||||
@@ -461,9 +451,8 @@ class Config(BaseSettings):
|
||||
|
||||
用法:
|
||||
```conf
|
||||
SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒
|
||||
SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff]
|
||||
SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601
|
||||
SESSION_EXPIRE_TIMEOUT=[-][DD]D[,][HH:MM:]SS[.ffffff]
|
||||
SESSION_EXPIRE_TIMEOUT=[±]P[DD]DT[HH]H[MM]M[SS]S # ISO 8601
|
||||
```
|
||||
"""
|
||||
|
||||
@@ -478,7 +467,9 @@ class Config(BaseSettings):
|
||||
model_config = SettingsConfig(env_file=(".env", ".env.prod"))
|
||||
else: # pragma: pydantic-v1
|
||||
|
||||
class Config(SettingsConfig):
|
||||
class Config( # pyright: ignore[reportIncompatibleVariableOverride]
|
||||
SettingsConfig
|
||||
):
|
||||
env_file = ".env", ".env.prod"
|
||||
|
||||
|
||||
|
@@ -9,20 +9,8 @@ import abc
|
||||
import asyncio
|
||||
import inspect
|
||||
from dataclasses import field, dataclass
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Generic,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Iterable,
|
||||
Optional,
|
||||
Awaitable,
|
||||
cast,
|
||||
)
|
||||
from collections.abc import Iterable, Awaitable
|
||||
from typing import Any, Generic, TypeVar, Callable, Optional, cast
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.typing import _DependentCallable
|
||||
@@ -48,13 +36,13 @@ class Param(abc.ABC, FieldInfo):
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type["Param"], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type["Param"], ...]
|
||||
) -> Optional["Param"]:
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def _check_parameterless(
|
||||
cls, value: Any, allow_types: Tuple[Type["Param"], ...]
|
||||
cls, value: Any, allow_types: tuple[type["Param"], ...]
|
||||
) -> Optional["Param"]:
|
||||
return
|
||||
|
||||
@@ -79,8 +67,8 @@ class Dependent(Generic[R]):
|
||||
"""
|
||||
|
||||
call: _DependentCallable[R]
|
||||
params: Tuple[ModelField, ...] = field(default_factory=tuple)
|
||||
parameterless: Tuple[Param, ...] = field(default_factory=tuple)
|
||||
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):
|
||||
@@ -112,9 +100,9 @@ class Dependent(Generic[R]):
|
||||
|
||||
@staticmethod
|
||||
def parse_params(
|
||||
call: _DependentCallable[R], allow_types: Tuple[Type[Param], ...]
|
||||
) -> Tuple[ModelField, ...]:
|
||||
fields: List[ModelField] = []
|
||||
call: _DependentCallable[R], allow_types: tuple[type[Param], ...]
|
||||
) -> tuple[ModelField, ...]:
|
||||
fields: list[ModelField] = []
|
||||
params = get_typed_signature(call).parameters.values()
|
||||
|
||||
for param in params:
|
||||
@@ -144,9 +132,9 @@ class Dependent(Generic[R]):
|
||||
|
||||
@staticmethod
|
||||
def parse_parameterless(
|
||||
parameterless: Tuple[Any, ...], allow_types: Tuple[Type[Param], ...]
|
||||
) -> Tuple[Param, ...]:
|
||||
parameterless_params: List[Param] = []
|
||||
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):
|
||||
@@ -162,7 +150,7 @@ class Dependent(Generic[R]):
|
||||
*,
|
||||
call: _DependentCallable[R],
|
||||
parameterless: Optional[Iterable[Any]] = None,
|
||||
allow_types: Iterable[Type[Param]],
|
||||
allow_types: Iterable[type[Param]],
|
||||
) -> "Dependent[R]":
|
||||
allow_types = tuple(allow_types)
|
||||
|
||||
@@ -181,7 +169,7 @@ class Dependent(Generic[R]):
|
||||
*(cast(Param, param.field_info)._check(**params) for param in self.params)
|
||||
)
|
||||
|
||||
async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
|
||||
async def _solve_field(self, field: ModelField, params: dict[str, Any]) -> Any:
|
||||
param = cast(Param, field.field_info)
|
||||
value = await param._solve(**params)
|
||||
if value is PydanticUndefined:
|
||||
@@ -189,7 +177,7 @@ class Dependent(Generic[R]):
|
||||
v = check_field_type(field, value)
|
||||
return v if param.validate else value
|
||||
|
||||
async def solve(self, **params: Any) -> Dict[str, Any]:
|
||||
async def solve(self, **params: Any) -> dict[str, Any]:
|
||||
# solve parameterless
|
||||
for param in self.parameterless:
|
||||
await param._solve(**params)
|
||||
|
@@ -5,7 +5,7 @@ FrontMatter:
|
||||
"""
|
||||
|
||||
import inspect
|
||||
from typing import Any, Dict, Callable, ForwardRef
|
||||
from typing import Any, Callable, ForwardRef
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -31,7 +31,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
||||
return inspect.Signature(typed_params)
|
||||
|
||||
|
||||
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:
|
||||
def get_typed_annotation(param: inspect.Parameter, globalns: dict[str, Any]) -> Any:
|
||||
"""获取参数的类型注解"""
|
||||
|
||||
annotation = param.annotation
|
||||
|
@@ -23,6 +23,7 @@ from nonebot.internal.driver import ReverseDriver as ReverseDriver
|
||||
from nonebot.internal.driver import combine_driver as combine_driver
|
||||
from nonebot.internal.driver import HTTPClientMixin as HTTPClientMixin
|
||||
from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup
|
||||
from nonebot.internal.driver import HTTPClientSession as HTTPClientSession
|
||||
from nonebot.internal.driver import WebSocketClientMixin as WebSocketClientMixin
|
||||
from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
|
||||
|
||||
|
@@ -16,16 +16,21 @@ FrontMatter:
|
||||
"""
|
||||
|
||||
from typing_extensions import override
|
||||
from collections.abc import AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import TYPE_CHECKING, AsyncGenerator
|
||||
from typing import TYPE_CHECKING, Union, Optional
|
||||
|
||||
from multidict import CIMultiDict
|
||||
|
||||
from nonebot.drivers import Request, Response
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.drivers import URL, Request, Response
|
||||
from nonebot.drivers.none import Driver as NoneDriver
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.internal.driver import Cookies, QueryTypes, CookieTypes, HeaderTypes
|
||||
from nonebot.drivers import (
|
||||
HTTPVersion,
|
||||
HTTPClientMixin,
|
||||
HTTPClientSession,
|
||||
WebSocketClientMixin,
|
||||
combine_driver,
|
||||
)
|
||||
@@ -39,6 +44,107 @@ except ModuleNotFoundError as e: # pragma: no cover
|
||||
) from e
|
||||
|
||||
|
||||
class Session(HTTPClientSession):
|
||||
@override
|
||||
def __init__(
|
||||
self,
|
||||
params: QueryTypes = None,
|
||||
headers: HeaderTypes = None,
|
||||
cookies: CookieTypes = None,
|
||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||
timeout: Optional[float] = None,
|
||||
proxy: Optional[str] = None,
|
||||
):
|
||||
self._client: Optional[aiohttp.ClientSession] = None
|
||||
|
||||
self._params = URL.build(query=params).query if params is not None else None
|
||||
|
||||
self._headers = CIMultiDict(headers) if headers is not None else None
|
||||
self._cookies = tuple(
|
||||
(cookie.name, cookie.value)
|
||||
for cookie in Cookies(cookies)
|
||||
if cookie.value is not None
|
||||
)
|
||||
|
||||
version = HTTPVersion(version)
|
||||
if version == HTTPVersion.H10:
|
||||
self._version = aiohttp.HttpVersion10
|
||||
elif version == HTTPVersion.H11:
|
||||
self._version = aiohttp.HttpVersion11
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported HTTP version: {version}")
|
||||
|
||||
self._timeout = timeout
|
||||
self._proxy = proxy
|
||||
|
||||
@property
|
||||
def client(self) -> aiohttp.ClientSession:
|
||||
if self._client is None:
|
||||
raise RuntimeError("Session is not initialized")
|
||||
return self._client
|
||||
|
||||
@override
|
||||
async def request(self, setup: Request) -> Response:
|
||||
if self._params:
|
||||
params = self._params.copy()
|
||||
params.update(setup.url.query)
|
||||
url = setup.url.with_query(params)
|
||||
else:
|
||||
url = setup.url
|
||||
|
||||
data = setup.data
|
||||
if setup.files:
|
||||
data = aiohttp.FormData(data or {}, quote_fields=False)
|
||||
for name, file in setup.files:
|
||||
data.add_field(name, file[1], content_type=file[2], filename=file[0])
|
||||
|
||||
cookies = (
|
||||
(cookie.name, cookie.value)
|
||||
for cookie in setup.cookies
|
||||
if cookie.value is not None
|
||||
)
|
||||
|
||||
timeout = aiohttp.ClientTimeout(setup.timeout)
|
||||
|
||||
async with await self.client.request(
|
||||
setup.method,
|
||||
url,
|
||||
data=setup.content or data,
|
||||
json=setup.json,
|
||||
cookies=cookies,
|
||||
headers=setup.headers,
|
||||
proxy=setup.proxy or self._proxy,
|
||||
timeout=timeout,
|
||||
) as response:
|
||||
return Response(
|
||||
response.status,
|
||||
headers=response.headers.copy(),
|
||||
content=await response.read(),
|
||||
request=setup,
|
||||
)
|
||||
|
||||
@override
|
||||
async def setup(self) -> None:
|
||||
if self._client is not None:
|
||||
raise RuntimeError("Session has already been initialized")
|
||||
self._client = aiohttp.ClientSession(
|
||||
cookies=self._cookies,
|
||||
headers=self._headers,
|
||||
version=self._version,
|
||||
timeout=self._timeout,
|
||||
trust_env=True,
|
||||
)
|
||||
await self._client.__aenter__()
|
||||
|
||||
@override
|
||||
async def close(self) -> None:
|
||||
try:
|
||||
if self._client is not None:
|
||||
await self._client.close()
|
||||
finally:
|
||||
self._client = None
|
||||
|
||||
|
||||
class Mixin(HTTPClientMixin, WebSocketClientMixin):
|
||||
"""AIOHTTP Mixin"""
|
||||
|
||||
@@ -49,42 +155,8 @@ class Mixin(HTTPClientMixin, WebSocketClientMixin):
|
||||
|
||||
@override
|
||||
async def request(self, setup: Request) -> Response:
|
||||
if setup.version == HTTPVersion.H10:
|
||||
version = aiohttp.HttpVersion10
|
||||
elif setup.version == HTTPVersion.H11:
|
||||
version = aiohttp.HttpVersion11
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported HTTP version: {setup.version}")
|
||||
|
||||
timeout = aiohttp.ClientTimeout(setup.timeout)
|
||||
|
||||
data = setup.data
|
||||
if setup.files:
|
||||
data = aiohttp.FormData(data or {})
|
||||
for name, file in setup.files:
|
||||
data.add_field(name, file[1], content_type=file[2], filename=file[0])
|
||||
|
||||
cookies = {
|
||||
cookie.name: cookie.value for cookie in setup.cookies if cookie.value
|
||||
}
|
||||
async with aiohttp.ClientSession(
|
||||
cookies=cookies, version=version, trust_env=True
|
||||
) as session:
|
||||
async with session.request(
|
||||
setup.method,
|
||||
setup.url,
|
||||
data=setup.content or data,
|
||||
json=setup.json,
|
||||
headers=setup.headers,
|
||||
timeout=timeout,
|
||||
proxy=setup.proxy,
|
||||
) as response:
|
||||
return Response(
|
||||
response.status,
|
||||
headers=response.headers.copy(),
|
||||
content=await response.read(),
|
||||
request=setup,
|
||||
)
|
||||
async with self.get_session() as session:
|
||||
return await session.request(setup)
|
||||
|
||||
@override
|
||||
@asynccontextmanager
|
||||
@@ -106,6 +178,25 @@ class Mixin(HTTPClientMixin, WebSocketClientMixin):
|
||||
) as ws:
|
||||
yield WebSocket(request=setup, session=session, websocket=ws)
|
||||
|
||||
@override
|
||||
def get_session(
|
||||
self,
|
||||
params: QueryTypes = None,
|
||||
headers: HeaderTypes = None,
|
||||
cookies: CookieTypes = None,
|
||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||
timeout: Optional[float] = None,
|
||||
proxy: Optional[str] = None,
|
||||
) -> Session:
|
||||
return Session(
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
version=version,
|
||||
timeout=timeout,
|
||||
proxy=proxy,
|
||||
)
|
||||
|
||||
|
||||
class WebSocket(BaseWebSocket):
|
||||
"""AIOHTTP Websocket Wrapper"""
|
||||
@@ -131,8 +222,8 @@ class WebSocket(BaseWebSocket):
|
||||
raise NotImplementedError
|
||||
|
||||
@override
|
||||
async def close(self, code: int = 1000):
|
||||
await self.websocket.close(code=code)
|
||||
async def close(self, code: int = 1000, reason: str = ""):
|
||||
await self.websocket.close(code=code, message=reason.encode("utf-8"))
|
||||
await self.session.close()
|
||||
|
||||
async def _receive(self) -> aiohttp.WSMessage:
|
||||
|
@@ -19,7 +19,7 @@ import logging
|
||||
import contextlib
|
||||
from functools import wraps
|
||||
from typing_extensions import override
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||
from typing import Any, Union, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -31,6 +31,7 @@ from nonebot.drivers import Driver as BaseDriver
|
||||
from nonebot.config import Config as NoneBotConfig
|
||||
from nonebot.drivers import Request as BaseRequest
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.compat import model_dump, type_validate_python
|
||||
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
||||
|
||||
try:
|
||||
@@ -71,15 +72,15 @@ class Config(BaseModel):
|
||||
"""是否包含适配器路由的 schema,默认为 `True`"""
|
||||
fastapi_reload: bool = False
|
||||
"""开启/关闭冷重载"""
|
||||
fastapi_reload_dirs: Optional[List[str]] = None
|
||||
fastapi_reload_dirs: Optional[list[str]] = None
|
||||
"""重载监控文件夹列表,默认为 uvicorn 默认值"""
|
||||
fastapi_reload_delay: float = 0.25
|
||||
"""重载延迟,默认为 uvicorn 默认值"""
|
||||
fastapi_reload_includes: Optional[List[str]] = None
|
||||
fastapi_reload_includes: Optional[list[str]] = None
|
||||
"""要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
fastapi_reload_excludes: Optional[List[str]] = None
|
||||
fastapi_reload_excludes: Optional[list[str]] = None
|
||||
"""不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
fastapi_extra: Dict[str, Any] = {}
|
||||
fastapi_extra: dict[str, Any] = {}
|
||||
"""传递给 `FastAPI` 的其他参数。"""
|
||||
|
||||
|
||||
@@ -89,7 +90,7 @@ class Driver(BaseDriver, ASGIMixin):
|
||||
def __init__(self, env: Env, config: NoneBotConfig):
|
||||
super().__init__(env, config)
|
||||
|
||||
self.fastapi_config: Config = Config(**config.dict())
|
||||
self.fastapi_config: Config = type_validate_python(Config, model_dump(config))
|
||||
|
||||
self._server_app = FastAPI(
|
||||
lifespan=self._lifespan_manager,
|
||||
@@ -160,7 +161,7 @@ class Driver(BaseDriver, ASGIMixin):
|
||||
self,
|
||||
host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
*,
|
||||
*args,
|
||||
app: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
@@ -205,7 +206,7 @@ class Driver(BaseDriver, ASGIMixin):
|
||||
json = await request.json()
|
||||
|
||||
data: Optional[dict] = None
|
||||
files: Optional[List[Tuple[str, FileTypes]]] = None
|
||||
files: Optional[list[tuple[str, FileTypes]]] = None
|
||||
with contextlib.suppress(Exception):
|
||||
form = await request.form()
|
||||
data = {}
|
||||
|
@@ -15,15 +15,20 @@ FrontMatter:
|
||||
description: nonebot.drivers.httpx 模块
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing_extensions import override
|
||||
from typing import TYPE_CHECKING, Union, Optional
|
||||
|
||||
from multidict import CIMultiDict
|
||||
|
||||
from nonebot.drivers.none import Driver as NoneDriver
|
||||
from nonebot.internal.driver import Cookies, QueryTypes, CookieTypes, HeaderTypes
|
||||
from nonebot.drivers import (
|
||||
URL,
|
||||
Request,
|
||||
Response,
|
||||
HTTPVersion,
|
||||
HTTPClientMixin,
|
||||
HTTPClientSession,
|
||||
combine_driver,
|
||||
)
|
||||
|
||||
@@ -36,6 +41,79 @@ except ModuleNotFoundError as e: # pragma: no cover
|
||||
) from e
|
||||
|
||||
|
||||
class Session(HTTPClientSession):
|
||||
@override
|
||||
def __init__(
|
||||
self,
|
||||
params: QueryTypes = None,
|
||||
headers: HeaderTypes = None,
|
||||
cookies: CookieTypes = None,
|
||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||
timeout: Optional[float] = None,
|
||||
proxy: Optional[str] = None,
|
||||
):
|
||||
self._client: Optional[httpx.AsyncClient] = None
|
||||
|
||||
self._params = (
|
||||
tuple(URL.build(query=params).query.items()) if params is not None else None
|
||||
)
|
||||
self._headers = (
|
||||
tuple(CIMultiDict(headers).items()) if headers is not None else None
|
||||
)
|
||||
self._cookies = Cookies(cookies)
|
||||
self._version = HTTPVersion(version)
|
||||
self._timeout = timeout
|
||||
self._proxy = proxy
|
||||
|
||||
@property
|
||||
def client(self) -> httpx.AsyncClient:
|
||||
if self._client is None:
|
||||
raise RuntimeError("Session is not initialized")
|
||||
return self._client
|
||||
|
||||
@override
|
||||
async def request(self, setup: Request) -> Response:
|
||||
response = await self.client.request(
|
||||
setup.method,
|
||||
str(setup.url),
|
||||
content=setup.content,
|
||||
data=setup.data,
|
||||
files=setup.files,
|
||||
json=setup.json,
|
||||
headers=tuple(setup.headers.items()),
|
||||
cookies=setup.cookies.jar,
|
||||
timeout=setup.timeout,
|
||||
)
|
||||
return Response(
|
||||
response.status_code,
|
||||
headers=response.headers.multi_items(),
|
||||
content=response.content,
|
||||
request=setup,
|
||||
)
|
||||
|
||||
@override
|
||||
async def setup(self) -> None:
|
||||
if self._client is not None:
|
||||
raise RuntimeError("Session has already been initialized")
|
||||
self._client = httpx.AsyncClient(
|
||||
params=self._params,
|
||||
headers=self._headers,
|
||||
cookies=self._cookies.jar,
|
||||
http2=self._version == HTTPVersion.H2,
|
||||
proxies=self._proxy,
|
||||
follow_redirects=True,
|
||||
)
|
||||
await self._client.__aenter__()
|
||||
|
||||
@override
|
||||
async def close(self) -> None:
|
||||
try:
|
||||
if self._client is not None:
|
||||
await self._client.aclose()
|
||||
finally:
|
||||
self._client = None
|
||||
|
||||
|
||||
class Mixin(HTTPClientMixin):
|
||||
"""HTTPX Mixin"""
|
||||
|
||||
@@ -46,27 +124,28 @@ class Mixin(HTTPClientMixin):
|
||||
|
||||
@override
|
||||
async def request(self, setup: Request) -> Response:
|
||||
async with httpx.AsyncClient(
|
||||
cookies=setup.cookies.jar,
|
||||
http2=setup.version == HTTPVersion.H2,
|
||||
proxies=setup.proxy,
|
||||
follow_redirects=True,
|
||||
) as client:
|
||||
response = await client.request(
|
||||
setup.method,
|
||||
str(setup.url),
|
||||
content=setup.content,
|
||||
data=setup.data,
|
||||
json=setup.json,
|
||||
files=setup.files,
|
||||
headers=tuple(setup.headers.items()),
|
||||
timeout=setup.timeout,
|
||||
)
|
||||
return Response(
|
||||
response.status_code,
|
||||
headers=response.headers.multi_items(),
|
||||
content=response.content,
|
||||
request=setup,
|
||||
async with self.get_session(
|
||||
version=setup.version, proxy=setup.proxy
|
||||
) as session:
|
||||
return await session.request(setup)
|
||||
|
||||
@override
|
||||
def get_session(
|
||||
self,
|
||||
params: QueryTypes = None,
|
||||
headers: HeaderTypes = None,
|
||||
cookies: CookieTypes = None,
|
||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||
timeout: Optional[float] = None,
|
||||
proxy: Optional[str] = None,
|
||||
) -> Session:
|
||||
return Session(
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
version=version,
|
||||
timeout=timeout,
|
||||
proxy=proxy,
|
||||
)
|
||||
|
||||
|
||||
|
@@ -68,9 +68,11 @@ class Driver(BaseDriver):
|
||||
await self._lifespan.startup()
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running startup function. "
|
||||
"Ignored!</bg #f8bbd0></r>"
|
||||
"<r><bg #f8bbd0>Application startup failed. "
|
||||
"Exiting.</bg #f8bbd0></r>"
|
||||
)
|
||||
self.should_exit.set()
|
||||
return
|
||||
|
||||
logger.info("Application startup completed.")
|
||||
|
||||
|
@@ -18,7 +18,7 @@ FrontMatter:
|
||||
import asyncio
|
||||
from functools import wraps
|
||||
from typing_extensions import override
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional, cast
|
||||
from typing import Any, Union, Optional, cast
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -30,6 +30,7 @@ from nonebot.drivers import Driver as BaseDriver
|
||||
from nonebot.config import Config as NoneBotConfig
|
||||
from nonebot.drivers import Request as BaseRequest
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.compat import model_dump, type_validate_python
|
||||
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
||||
|
||||
try:
|
||||
@@ -63,15 +64,15 @@ class Config(BaseModel):
|
||||
|
||||
quart_reload: bool = False
|
||||
"""开启/关闭冷重载"""
|
||||
quart_reload_dirs: Optional[List[str]] = None
|
||||
quart_reload_dirs: Optional[list[str]] = None
|
||||
"""重载监控文件夹列表,默认为 uvicorn 默认值"""
|
||||
quart_reload_delay: float = 0.25
|
||||
"""重载延迟,默认为 uvicorn 默认值"""
|
||||
quart_reload_includes: Optional[List[str]] = None
|
||||
quart_reload_includes: Optional[list[str]] = None
|
||||
"""要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
quart_reload_excludes: Optional[List[str]] = None
|
||||
quart_reload_excludes: Optional[list[str]] = None
|
||||
"""不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
quart_extra: Dict[str, Any] = {}
|
||||
quart_extra: dict[str, Any] = {}
|
||||
"""传递给 `Quart` 的其他参数。"""
|
||||
|
||||
|
||||
@@ -81,7 +82,7 @@ class Driver(BaseDriver, ASGIMixin):
|
||||
def __init__(self, env: Env, config: NoneBotConfig):
|
||||
super().__init__(env, config)
|
||||
|
||||
self.quart_config = Config(**config.dict())
|
||||
self.quart_config = type_validate_python(Config, model_dump(config))
|
||||
|
||||
self._server_app = Quart(
|
||||
self.__class__.__qualname__, **self.quart_config.quart_extra
|
||||
@@ -141,7 +142,7 @@ class Driver(BaseDriver, ASGIMixin):
|
||||
self,
|
||||
host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
*,
|
||||
*args,
|
||||
app: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
@@ -183,7 +184,7 @@ class Driver(BaseDriver, ASGIMixin):
|
||||
|
||||
data = await request.form
|
||||
files_dict = await request.files
|
||||
files: List[Tuple[str, FileTypes]] = []
|
||||
files: list[tuple[str, FileTypes]] = []
|
||||
key: str
|
||||
value: FileStorage
|
||||
for key, value in files_dict.items():
|
||||
|
@@ -19,7 +19,8 @@ import logging
|
||||
from functools import wraps
|
||||
from contextlib import asynccontextmanager
|
||||
from typing_extensions import ParamSpec, override
|
||||
from typing import TYPE_CHECKING, Union, TypeVar, Callable, Awaitable, AsyncGenerator
|
||||
from collections.abc import Coroutine, AsyncGenerator
|
||||
from typing import TYPE_CHECKING, Any, Union, TypeVar, Callable
|
||||
|
||||
from nonebot.drivers import Request
|
||||
from nonebot.log import LoguruHandler
|
||||
@@ -44,7 +45,9 @@ logger = logging.Logger("websockets.client", "INFO")
|
||||
logger.addHandler(LoguruHandler())
|
||||
|
||||
|
||||
def catch_closed(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
|
||||
def catch_closed(
|
||||
func: Callable[P, Coroutine[Any, Any, T]]
|
||||
) -> Callable[P, Coroutine[Any, Any, T]]:
|
||||
@wraps(func)
|
||||
async def decorator(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||
try:
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import abc
|
||||
from typing import Any
|
||||
from collections.abc import AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any, Dict, AsyncGenerator
|
||||
|
||||
from nonebot.config import Config
|
||||
from nonebot.internal.driver._lifespan import LIFESPAN_FUNC
|
||||
@@ -32,7 +33,7 @@ class Adapter(abc.ABC):
|
||||
def __init__(self, driver: Driver, **kwargs: Any):
|
||||
self.driver: Driver = driver
|
||||
"""{ref}`nonebot.drivers.Driver` 实例"""
|
||||
self.bots: Dict[str, Bot] = {}
|
||||
self.bots: dict[str, Bot] = {}
|
||||
"""本协议适配器已建立连接的 {ref}`nonebot.adapters.Bot` 实例"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import abc
|
||||
import asyncio
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Any, Set, Union, Optional, Protocol
|
||||
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional, Protocol
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.config import Config
|
||||
@@ -27,9 +27,9 @@ class Bot(abc.ABC):
|
||||
self_id: 机器人 ID
|
||||
"""
|
||||
|
||||
_calling_api_hook: Set[T_CallingAPIHook] = set()
|
||||
_calling_api_hook: ClassVar[set[T_CallingAPIHook]] = set()
|
||||
"""call_api 时执行的函数"""
|
||||
_called_api_hook: Set[T_CalledAPIHook] = set()
|
||||
_called_api_hook: ClassVar[set[T_CalledAPIHook]] = set()
|
||||
"""call_api 后执行的函数"""
|
||||
|
||||
def __init__(self, adapter: "Adapter", self_id: str):
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import abc
|
||||
from typing import Any, Type, TypeVar
|
||||
from typing import Any, TypeVar
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -20,12 +20,12 @@ class Event(abc.ABC, BaseModel):
|
||||
|
||||
class Config(ConfigDict):
|
||||
extra = "allow" # type: ignore
|
||||
json_encoders = {Message: DataclassEncoder}
|
||||
json_encoders = {Message: DataclassEncoder} # noqa: RUF012
|
||||
|
||||
if not PYDANTIC_V2: # pragma: pydantic-v1
|
||||
|
||||
@classmethod
|
||||
def validate(cls: Type["E"], value: Any) -> "E":
|
||||
def validate(cls: type["E"], value: Any) -> "E":
|
||||
if isinstance(value, Event) and not isinstance(value, cls):
|
||||
raise TypeError(f"{value} is incompatible with Event type {cls}")
|
||||
return super().validate(value)
|
||||
|
@@ -1,17 +1,14 @@
|
||||
import abc
|
||||
from copy import deepcopy
|
||||
from typing_extensions import Self
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import field, asdict, dataclass
|
||||
from typing import (
|
||||
from typing import ( # noqa: UP035
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Generic,
|
||||
TypeVar,
|
||||
Iterable,
|
||||
Optional,
|
||||
SupportsIndex,
|
||||
overload,
|
||||
@@ -32,12 +29,12 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
||||
|
||||
type: str
|
||||
"""消息段类型"""
|
||||
data: Dict[str, Any] = field(default_factory=dict)
|
||||
data: dict[str, Any] = field(default_factory=dict)
|
||||
"""消息段数据"""
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_message_class(cls) -> Type[TM]:
|
||||
def get_message_class(cls) -> Type[TM]: # noqa: UP006
|
||||
"""获取消息数组类型"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -49,7 +46,9 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
||||
def __len__(self) -> int:
|
||||
return len(str(self))
|
||||
|
||||
def __ne__(self, other: Self) -> bool:
|
||||
def __ne__( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, other: Self
|
||||
) -> bool:
|
||||
return not self == other
|
||||
|
||||
def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
||||
@@ -101,7 +100,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
||||
|
||||
|
||||
@custom_validation
|
||||
class Message(List[TMS], abc.ABC):
|
||||
class Message(list[TMS], abc.ABC):
|
||||
"""消息序列
|
||||
|
||||
参数:
|
||||
@@ -142,7 +141,7 @@ class Message(List[TMS], abc.ABC):
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_segment_class(cls) -> Type[TMS]:
|
||||
def get_segment_class(cls) -> type[TMS]:
|
||||
"""获取消息段类型"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -177,7 +176,9 @@ class Message(List[TMS], abc.ABC):
|
||||
"""构造消息数组"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __add__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
|
||||
def __add__( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, other: Union[str, TMS, Iterable[TMS]]
|
||||
) -> Self:
|
||||
result = self.copy()
|
||||
result += other
|
||||
return result
|
||||
@@ -209,7 +210,7 @@ class Message(List[TMS], abc.ABC):
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __getitem__(self, args: Tuple[str, int]) -> TMS:
|
||||
def __getitem__(self, args: tuple[str, int]) -> TMS:
|
||||
"""索引指定类型的消息段
|
||||
|
||||
参数:
|
||||
@@ -220,7 +221,7 @@ class Message(List[TMS], abc.ABC):
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __getitem__(self, args: Tuple[str, slice]) -> Self:
|
||||
def __getitem__(self, args: tuple[str, slice]) -> Self:
|
||||
"""切片指定类型的消息段
|
||||
|
||||
参数:
|
||||
@@ -252,12 +253,12 @@ class Message(List[TMS], abc.ABC):
|
||||
消息切片 `args`
|
||||
"""
|
||||
|
||||
def __getitem__(
|
||||
def __getitem__( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self,
|
||||
args: Union[
|
||||
str,
|
||||
Tuple[str, int],
|
||||
Tuple[str, slice],
|
||||
tuple[str, int],
|
||||
tuple[str, slice],
|
||||
int,
|
||||
slice,
|
||||
],
|
||||
@@ -276,7 +277,9 @@ class Message(List[TMS], abc.ABC):
|
||||
else:
|
||||
raise ValueError("Incorrect arguments to slice") # pragma: no cover
|
||||
|
||||
def __contains__(self, value: Union[TMS, str]) -> bool:
|
||||
def __contains__( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, value: Union[TMS, str]
|
||||
) -> bool:
|
||||
"""检查消息段是否存在
|
||||
|
||||
参数:
|
||||
@@ -285,7 +288,7 @@ class Message(List[TMS], abc.ABC):
|
||||
消息内是否存在给定消息段或给定类型的消息段
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
return bool(next((seg for seg in self if seg.type == value), None))
|
||||
return next((seg for seg in self if seg.type == value), None) is not None
|
||||
return super().__contains__(value)
|
||||
|
||||
def has(self, value: Union[TMS, str]) -> bool:
|
||||
@@ -359,7 +362,9 @@ class Message(List[TMS], abc.ABC):
|
||||
return all(seg.type == value for seg in self)
|
||||
return all(seg == value for seg in self)
|
||||
|
||||
def append(self, obj: Union[str, TMS]) -> Self:
|
||||
def append( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, obj: Union[str, TMS]
|
||||
) -> Self:
|
||||
"""添加一个消息段到消息数组末尾。
|
||||
|
||||
参数:
|
||||
@@ -373,7 +378,9 @@ class Message(List[TMS], abc.ABC):
|
||||
raise ValueError(f"Unexpected type: {type(obj)} {obj}") # pragma: no cover
|
||||
return self
|
||||
|
||||
def extend(self, obj: Union[Self, Iterable[TMS]]) -> Self:
|
||||
def extend( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, obj: Union[Self, Iterable[TMS]]
|
||||
) -> Self:
|
||||
"""拼接一个消息数组或多个消息段到消息数组末尾。
|
||||
|
||||
参数:
|
||||
|
@@ -1,21 +1,15 @@
|
||||
import functools
|
||||
from string import Formatter
|
||||
from typing_extensions import TypeAlias
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Set,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Generic,
|
||||
Mapping,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Optional,
|
||||
Sequence,
|
||||
cast,
|
||||
overload,
|
||||
)
|
||||
@@ -25,9 +19,9 @@ from _string import formatter_field_name_split # type: ignore
|
||||
if TYPE_CHECKING:
|
||||
from .message import Message, MessageSegment
|
||||
|
||||
def formatter_field_name_split( # noqa: F811
|
||||
def formatter_field_name_split(
|
||||
field_name: str,
|
||||
) -> Tuple[str, List[Tuple[bool, str]]]: ...
|
||||
) -> tuple[str, list[tuple[bool, str]]]: ...
|
||||
|
||||
|
||||
TM = TypeVar("TM", bound="Message")
|
||||
@@ -50,7 +44,7 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
def __init__(
|
||||
self: "MessageTemplate[str]",
|
||||
template: str,
|
||||
factory: Type[str] = str,
|
||||
factory: type[str] = str,
|
||||
private_getattr: bool = False,
|
||||
) -> None: ...
|
||||
|
||||
@@ -58,19 +52,19 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
def __init__(
|
||||
self: "MessageTemplate[TM]",
|
||||
template: Union[str, TM],
|
||||
factory: Type[TM],
|
||||
factory: type[TM],
|
||||
private_getattr: bool = False,
|
||||
) -> None: ...
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
template: Union[str, TM],
|
||||
factory: Union[Type[str], Type[TM]] = str,
|
||||
factory: Union[type[str], type[TM]] = str,
|
||||
private_getattr: bool = False,
|
||||
) -> None:
|
||||
self.template: TF = template # type: ignore
|
||||
self.factory: Type[TF] = factory # type: ignore
|
||||
self.format_specs: Dict[str, FormatSpecFunc] = {}
|
||||
self.factory: type[TF] = factory # type: ignore
|
||||
self.format_specs: dict[str, FormatSpecFunc] = {}
|
||||
self.private_getattr = private_getattr
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -85,7 +79,9 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
self.format_specs[name] = spec
|
||||
return spec
|
||||
|
||||
def format(self, *args, **kwargs):
|
||||
def format( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, *args, **kwargs
|
||||
) -> TF:
|
||||
"""根据传入参数和模板生成消息对象"""
|
||||
return self._format(args, kwargs)
|
||||
|
||||
@@ -118,7 +114,7 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
self.check_unused_args(used_args, args, kwargs)
|
||||
return cast(TF, full_message)
|
||||
|
||||
def vformat(
|
||||
def vformat( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self,
|
||||
format_string: str,
|
||||
args: Sequence[Any],
|
||||
@@ -126,15 +122,15 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
) -> TF:
|
||||
raise NotImplementedError("`vformat` has merged into `_format`")
|
||||
|
||||
def _vformat(
|
||||
def _vformat( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self,
|
||||
format_string: str,
|
||||
args: Sequence[Any],
|
||||
kwargs: Mapping[str, Any],
|
||||
used_args: Set[Union[int, str]],
|
||||
used_args: set[Union[int, str]],
|
||||
auto_arg_index: int = 0,
|
||||
) -> Tuple[TF, int]:
|
||||
results: List[Any] = [self.factory()]
|
||||
) -> tuple[TF, int]:
|
||||
results: list[Any] = [self.factory()]
|
||||
|
||||
for literal_text, field_name, format_spec, conversion in self.parse(
|
||||
format_string
|
||||
@@ -185,7 +181,7 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
|
||||
def get_field(
|
||||
self, field_name: str, args: Sequence[Any], kwargs: Mapping[str, Any]
|
||||
) -> Tuple[Any, Union[int, str]]:
|
||||
) -> tuple[Any, Union[int, str]]:
|
||||
first, rest = formatter_field_name_split(field_name)
|
||||
obj = self.get_value(first, args, kwargs)
|
||||
|
||||
@@ -199,7 +195,7 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
def format_field(self, value: Any, format_spec: str) -> Any:
|
||||
formatter: Optional[FormatSpecFunc] = self.format_specs.get(format_spec)
|
||||
if formatter is None and not issubclass(self.factory, str):
|
||||
segment_class: Type["MessageSegment"] = self.factory.get_segment_class()
|
||||
segment_class: type["MessageSegment"] = self.factory.get_segment_class()
|
||||
method = getattr(segment_class, format_spec, None)
|
||||
if callable(method) and not cast(str, method.__name__).startswith("_"):
|
||||
formatter = getattr(segment_class, format_spec)
|
||||
|
@@ -26,5 +26,6 @@ from .abstract import ReverseDriver as ReverseDriver
|
||||
from .combine import combine_driver as combine_driver
|
||||
from .model import HTTPServerSetup as HTTPServerSetup
|
||||
from .abstract import HTTPClientMixin as HTTPClientMixin
|
||||
from .abstract import HTTPClientSession as HTTPClientSession
|
||||
from .model import WebSocketServerSetup as WebSocketServerSetup
|
||||
from .abstract import WebSocketClientMixin as WebSocketClientMixin
|
||||
|
@@ -1,5 +1,6 @@
|
||||
from collections.abc import Awaitable
|
||||
from typing_extensions import TypeAlias
|
||||
from typing import Any, List, Union, Callable, Awaitable, cast
|
||||
from typing import Any, Union, Callable, cast
|
||||
|
||||
from nonebot.utils import run_sync, is_coroutine_callable
|
||||
|
||||
@@ -10,9 +11,9 @@ LIFESPAN_FUNC: TypeAlias = Union[SYNC_LIFESPAN_FUNC, ASYNC_LIFESPAN_FUNC]
|
||||
|
||||
class Lifespan:
|
||||
def __init__(self) -> None:
|
||||
self._startup_funcs: List[LIFESPAN_FUNC] = []
|
||||
self._ready_funcs: List[LIFESPAN_FUNC] = []
|
||||
self._shutdown_funcs: List[LIFESPAN_FUNC] = []
|
||||
self._startup_funcs: list[LIFESPAN_FUNC] = []
|
||||
self._ready_funcs: list[LIFESPAN_FUNC] = []
|
||||
self._shutdown_funcs: list[LIFESPAN_FUNC] = []
|
||||
|
||||
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||
self._startup_funcs.append(func)
|
||||
@@ -28,7 +29,7 @@ class Lifespan:
|
||||
|
||||
@staticmethod
|
||||
async def _run_lifespan_func(
|
||||
funcs: List[LIFESPAN_FUNC],
|
||||
funcs: list[LIFESPAN_FUNC],
|
||||
) -> None:
|
||||
for func in funcs:
|
||||
if is_coroutine_callable(func):
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import abc
|
||||
import asyncio
|
||||
from typing_extensions import TypeAlias
|
||||
from types import TracebackType
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing_extensions import Self, TypeAlias
|
||||
from contextlib import AsyncExitStack, asynccontextmanager
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, AsyncGenerator
|
||||
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.config import Env, Config
|
||||
@@ -17,7 +19,17 @@ from nonebot.typing import (
|
||||
)
|
||||
|
||||
from ._lifespan import LIFESPAN_FUNC, Lifespan
|
||||
from .model import Request, Response, WebSocket, HTTPServerSetup, WebSocketServerSetup
|
||||
from .model import (
|
||||
Request,
|
||||
Response,
|
||||
WebSocket,
|
||||
QueryTypes,
|
||||
CookieTypes,
|
||||
HeaderTypes,
|
||||
HTTPVersion,
|
||||
HTTPServerSetup,
|
||||
WebSocketServerSetup,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.internal.adapter import Bot, Adapter
|
||||
@@ -36,11 +48,11 @@ class Driver(abc.ABC):
|
||||
config: 包含配置信息的 Config 对象
|
||||
"""
|
||||
|
||||
_adapters: Dict[str, "Adapter"] = {}
|
||||
_adapters: ClassVar[dict[str, "Adapter"]] = {}
|
||||
"""已注册的适配器列表"""
|
||||
_bot_connection_hook: Set[Dependent[Any]] = set()
|
||||
_bot_connection_hook: ClassVar[set[Dependent[Any]]] = set()
|
||||
"""Bot 连接建立时执行的函数"""
|
||||
_bot_disconnection_hook: Set[Dependent[Any]] = set()
|
||||
_bot_disconnection_hook: ClassVar[set[Dependent[Any]]] = set()
|
||||
"""Bot 连接断开时执行的函数"""
|
||||
|
||||
def __init__(self, env: Env, config: Config):
|
||||
@@ -48,8 +60,8 @@ class Driver(abc.ABC):
|
||||
"""环境名称"""
|
||||
self.config: Config = config
|
||||
"""全局配置对象"""
|
||||
self._bots: Dict[str, "Bot"] = {}
|
||||
self._bot_tasks: Set[asyncio.Task] = set()
|
||||
self._bots: dict[str, "Bot"] = {}
|
||||
self._bot_tasks: set[asyncio.Task] = set()
|
||||
self._lifespan = Lifespan()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -59,11 +71,11 @@ class Driver(abc.ABC):
|
||||
)
|
||||
|
||||
@property
|
||||
def bots(self) -> Dict[str, "Bot"]:
|
||||
def bots(self) -> dict[str, "Bot"]:
|
||||
"""获取当前所有已连接的 Bot"""
|
||||
return self._bots
|
||||
|
||||
def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None:
|
||||
def register_adapter(self, adapter: type["Adapter"], **kwargs) -> None:
|
||||
"""注册一个协议适配器
|
||||
|
||||
参数:
|
||||
@@ -222,6 +234,49 @@ class ReverseMixin(Mixin):
|
||||
"""服务端混入基类。"""
|
||||
|
||||
|
||||
class HTTPClientSession(abc.ABC):
|
||||
"""HTTP 客户端会话基类。"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __init__(
|
||||
self,
|
||||
params: QueryTypes = None,
|
||||
headers: HeaderTypes = None,
|
||||
cookies: CookieTypes = None,
|
||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||
timeout: Optional[float] = None,
|
||||
proxy: Optional[str] = None,
|
||||
):
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
async def request(self, setup: Request) -> Response:
|
||||
"""发送一个 HTTP 请求"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
async def setup(self) -> None:
|
||||
"""初始化会话"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
async def close(self) -> None:
|
||||
"""关闭会话"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def __aenter__(self) -> Self:
|
||||
await self.setup()
|
||||
return self
|
||||
|
||||
async def __aexit__(
|
||||
self,
|
||||
exc_type: Optional[type[BaseException]],
|
||||
exc: Optional[BaseException],
|
||||
tb: Optional[TracebackType],
|
||||
) -> None:
|
||||
await self.close()
|
||||
|
||||
|
||||
class HTTPClientMixin(ForwardMixin):
|
||||
"""HTTP 客户端混入基类。"""
|
||||
|
||||
@@ -230,6 +285,19 @@ class HTTPClientMixin(ForwardMixin):
|
||||
"""发送一个 HTTP 请求"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_session(
|
||||
self,
|
||||
params: QueryTypes = None,
|
||||
headers: HeaderTypes = None,
|
||||
cookies: CookieTypes = None,
|
||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||
timeout: Optional[float] = None,
|
||||
proxy: Optional[str] = None,
|
||||
) -> HTTPClientSession:
|
||||
"""获取一个 HTTP 会话"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class WebSocketClientMixin(ForwardMixin):
|
||||
"""WebSocket 客户端混入基类。"""
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Type, Union, TypeVar, overload
|
||||
from typing import TYPE_CHECKING, Union, TypeVar, overload
|
||||
|
||||
from .abstract import Mixin, Driver
|
||||
|
||||
@@ -10,16 +10,18 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
@overload
|
||||
def combine_driver(driver: Type[D]) -> Type[D]: ...
|
||||
def combine_driver(driver: type[D]) -> type[D]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def combine_driver(driver: Type[D], *mixins: Type[Mixin]) -> Type["CombinedDriver"]: ...
|
||||
def combine_driver(
|
||||
driver: type[D], _m: type[Mixin], *mixins: type[Mixin]
|
||||
) -> type["CombinedDriver"]: ...
|
||||
|
||||
|
||||
def combine_driver(
|
||||
driver: Type[D], *mixins: Type[Mixin]
|
||||
) -> Union[Type[D], Type["CombinedDriver"]]:
|
||||
driver: type[D], *mixins: type[Mixin]
|
||||
) -> Union[type[D], type["CombinedDriver"]]:
|
||||
"""将一个驱动器和多个混入类合并。"""
|
||||
# check first
|
||||
if not issubclass(driver, Driver):
|
||||
|
@@ -4,56 +4,44 @@ from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing_extensions import TypeAlias
|
||||
from http.cookiejar import Cookie, CookieJar
|
||||
from typing import (
|
||||
IO,
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Tuple,
|
||||
Union,
|
||||
Mapping,
|
||||
Callable,
|
||||
Iterator,
|
||||
Optional,
|
||||
Awaitable,
|
||||
MutableMapping,
|
||||
)
|
||||
from typing import IO, Any, Union, Callable, Optional
|
||||
from collections.abc import Mapping, Iterator, Awaitable, MutableMapping
|
||||
|
||||
from yarl import URL as URL
|
||||
from multidict import CIMultiDict
|
||||
|
||||
RawURL: TypeAlias = Tuple[bytes, bytes, Optional[int], bytes]
|
||||
RawURL: TypeAlias = tuple[bytes, bytes, Optional[int], bytes]
|
||||
|
||||
SimpleQuery: TypeAlias = Union[str, int, float]
|
||||
QueryVariable: TypeAlias = Union[SimpleQuery, List[SimpleQuery]]
|
||||
QueryVariable: TypeAlias = Union[SimpleQuery, list[SimpleQuery]]
|
||||
QueryTypes: TypeAlias = Union[
|
||||
None, str, Mapping[str, QueryVariable], List[Tuple[str, QueryVariable]]
|
||||
None, str, Mapping[str, QueryVariable], list[tuple[str, SimpleQuery]]
|
||||
]
|
||||
|
||||
HeaderTypes: TypeAlias = Union[
|
||||
None,
|
||||
CIMultiDict[str],
|
||||
Dict[str, str],
|
||||
List[Tuple[str, str]],
|
||||
dict[str, str],
|
||||
list[tuple[str, str]],
|
||||
]
|
||||
|
||||
CookieTypes: TypeAlias = Union[
|
||||
None, "Cookies", CookieJar, Dict[str, str], List[Tuple[str, str]]
|
||||
None, "Cookies", CookieJar, dict[str, str], list[tuple[str, str]]
|
||||
]
|
||||
|
||||
ContentTypes: TypeAlias = Union[str, bytes, None]
|
||||
DataTypes: TypeAlias = Union[dict, None]
|
||||
FileContent: TypeAlias = Union[IO[bytes], bytes]
|
||||
FileType: TypeAlias = Tuple[Optional[str], FileContent, Optional[str]]
|
||||
FileType: TypeAlias = tuple[Optional[str], FileContent, Optional[str]]
|
||||
FileTypes: TypeAlias = Union[
|
||||
# file (or bytes)
|
||||
FileContent,
|
||||
# (filename, file (or bytes))
|
||||
Tuple[Optional[str], FileContent],
|
||||
tuple[Optional[str], FileContent],
|
||||
# (filename, file (or bytes), content_type)
|
||||
FileType,
|
||||
]
|
||||
FilesTypes: TypeAlias = Union[Dict[str, FileTypes], List[Tuple[str, FileTypes]], None]
|
||||
FilesTypes: TypeAlias = Union[dict[str, FileTypes], list[tuple[str, FileTypes]], None]
|
||||
|
||||
|
||||
class HTTPVersion(Enum):
|
||||
@@ -119,7 +107,7 @@ class Request:
|
||||
self.content: ContentTypes = content
|
||||
self.data: DataTypes = data
|
||||
self.json: Any = json
|
||||
self.files: Optional[List[Tuple[str, FileType]]] = None
|
||||
self.files: Optional[list[tuple[str, FileType]]] = None
|
||||
if files:
|
||||
self.files = []
|
||||
files_ = files.items() if isinstance(files, dict) else files
|
||||
@@ -257,7 +245,7 @@ class Cookies(MutableMapping):
|
||||
)
|
||||
self.jar.set_cookie(cookie)
|
||||
|
||||
def get(
|
||||
def get( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self,
|
||||
name: str,
|
||||
default: Optional[str] = None,
|
||||
@@ -298,12 +286,14 @@ class Cookies(MutableMapping):
|
||||
def clear(self, domain: Optional[str] = None, path: Optional[str] = None) -> None:
|
||||
self.jar.clear(domain, path)
|
||||
|
||||
def update(self, cookies: CookieTypes = None) -> None:
|
||||
def update( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, cookies: CookieTypes = None
|
||||
) -> None:
|
||||
cookies = Cookies(cookies)
|
||||
for cookie in cookies.jar:
|
||||
self.jar.set_cookie(cookie)
|
||||
|
||||
def as_header(self, request: Request) -> Dict[str, str]:
|
||||
def as_header(self, request: Request) -> dict[str, str]:
|
||||
urllib_request = self._CookieCompatRequest(request)
|
||||
self.jar.add_cookie_header(urllib_request)
|
||||
return urllib_request.added_headers
|
||||
@@ -341,9 +331,11 @@ class Cookies(MutableMapping):
|
||||
method=request.method,
|
||||
)
|
||||
self.request = request
|
||||
self.added_headers: Dict[str, str] = {}
|
||||
self.added_headers: dict[str, str] = {}
|
||||
|
||||
def add_unredirected_header(self, key: str, value: str) -> None:
|
||||
def add_unredirected_header( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, key: str, value: str
|
||||
) -> None:
|
||||
super().add_unredirected_header(key, value)
|
||||
self.added_headers[key] = value
|
||||
|
||||
|
@@ -1,18 +1,5 @@
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
TypeVar,
|
||||
Iterator,
|
||||
KeysView,
|
||||
Optional,
|
||||
ItemsView,
|
||||
ValuesView,
|
||||
MutableMapping,
|
||||
overload,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Union, TypeVar, Optional, overload
|
||||
from collections.abc import Iterator, KeysView, ItemsView, ValuesView, MutableMapping
|
||||
|
||||
from .provider import DEFAULT_PROVIDER_CLASS, MatcherProvider
|
||||
|
||||
@@ -22,7 +9,7 @@ if TYPE_CHECKING:
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class MatcherManager(MutableMapping[int, List[Type["Matcher"]]]):
|
||||
class MatcherManager(MutableMapping[int, list[type["Matcher"]]]):
|
||||
"""事件响应器管理器
|
||||
|
||||
实现了常用字典操作,用于管理事件响应器。
|
||||
@@ -43,10 +30,10 @@ class MatcherManager(MutableMapping[int, List[Type["Matcher"]]]):
|
||||
def __len__(self) -> int:
|
||||
return len(self.provider)
|
||||
|
||||
def __getitem__(self, key: int) -> List[Type["Matcher"]]:
|
||||
def __getitem__(self, key: int) -> list[type["Matcher"]]:
|
||||
return self.provider[key]
|
||||
|
||||
def __setitem__(self, key: int, value: List[Type["Matcher"]]) -> None:
|
||||
def __setitem__(self, key: int, value: list[type["Matcher"]]) -> None:
|
||||
self.provider[key] = value
|
||||
|
||||
def __delitem__(self, key: int) -> None:
|
||||
@@ -58,41 +45,45 @@ class MatcherManager(MutableMapping[int, List[Type["Matcher"]]]):
|
||||
def keys(self) -> KeysView[int]:
|
||||
return self.provider.keys()
|
||||
|
||||
def values(self) -> ValuesView[List[Type["Matcher"]]]:
|
||||
def values(self) -> ValuesView[list[type["Matcher"]]]:
|
||||
return self.provider.values()
|
||||
|
||||
def items(self) -> ItemsView[int, List[Type["Matcher"]]]:
|
||||
def items(self) -> ItemsView[int, list[type["Matcher"]]]:
|
||||
return self.provider.items()
|
||||
|
||||
@overload
|
||||
def get(self, key: int) -> Optional[List[Type["Matcher"]]]: ...
|
||||
def get(self, key: int) -> Optional[list[type["Matcher"]]]: ...
|
||||
|
||||
@overload
|
||||
def get(self, key: int, default: T) -> Union[List[Type["Matcher"]], T]: ...
|
||||
def get(self, key: int, default: T) -> Union[list[type["Matcher"]], T]: ...
|
||||
|
||||
def get(
|
||||
self, key: int, default: Optional[T] = None
|
||||
) -> Optional[Union[List[Type["Matcher"]], T]]:
|
||||
) -> Optional[Union[list[type["Matcher"]], T]]:
|
||||
return self.provider.get(key, default)
|
||||
|
||||
def pop(self, key: int) -> List[Type["Matcher"]]:
|
||||
def pop( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, key: int
|
||||
) -> list[type["Matcher"]]:
|
||||
return self.provider.pop(key)
|
||||
|
||||
def popitem(self) -> Tuple[int, List[Type["Matcher"]]]:
|
||||
def popitem(self) -> tuple[int, list[type["Matcher"]]]:
|
||||
return self.provider.popitem()
|
||||
|
||||
def clear(self) -> None:
|
||||
self.provider.clear()
|
||||
|
||||
def update(self, __m: MutableMapping[int, List[Type["Matcher"]]]) -> None:
|
||||
def update( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, __m: MutableMapping[int, list[type["Matcher"]]]
|
||||
) -> None:
|
||||
self.provider.update(__m)
|
||||
|
||||
def setdefault(
|
||||
self, key: int, default: List[Type["Matcher"]]
|
||||
) -> List[Type["Matcher"]]:
|
||||
self, key: int, default: list[type["Matcher"]]
|
||||
) -> list[type["Matcher"]]:
|
||||
return self.provider.setdefault(key, default)
|
||||
|
||||
def set_provider(self, provider_class: Type[MatcherProvider]) -> None:
|
||||
def set_provider(self, provider_class: type[MatcherProvider]) -> None:
|
||||
"""设置事件响应器存储器
|
||||
|
||||
参数:
|
||||
|
@@ -6,19 +6,17 @@ from types import ModuleType
|
||||
from dataclasses import dataclass
|
||||
from contextvars import ContextVar
|
||||
from typing_extensions import Self
|
||||
from collections.abc import Iterable
|
||||
from datetime import datetime, timedelta
|
||||
from contextlib import AsyncExitStack, contextmanager
|
||||
from typing import (
|
||||
from typing import ( # noqa: UP035
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
TypeVar,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Iterable,
|
||||
NoReturn,
|
||||
Optional,
|
||||
overload,
|
||||
@@ -85,8 +83,8 @@ current_handler: ContextVar[Dependent] = ContextVar("current_handler")
|
||||
class MatcherSource:
|
||||
"""Matcher 源代码上下文信息"""
|
||||
|
||||
plugin_name: Optional[str] = None
|
||||
"""事件响应器所在插件名称"""
|
||||
plugin_id: Optional[str] = None
|
||||
"""事件响应器所在插件标识符"""
|
||||
module_name: Optional[str] = None
|
||||
"""事件响应器所在插件模块的路径名"""
|
||||
lineno: Optional[int] = None
|
||||
@@ -97,8 +95,13 @@ class MatcherSource:
|
||||
"""事件响应器所在插件"""
|
||||
from nonebot.plugin import get_plugin
|
||||
|
||||
if self.plugin_name is not None:
|
||||
return get_plugin(self.plugin_name)
|
||||
if self.plugin_id is not None:
|
||||
return get_plugin(self.plugin_id)
|
||||
|
||||
@property
|
||||
def plugin_name(self) -> Optional[str]:
|
||||
"""事件响应器所在插件名"""
|
||||
return self.plugin and self.plugin.name
|
||||
|
||||
@property
|
||||
def module(self) -> Optional[ModuleType]:
|
||||
@@ -141,7 +144,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
"""事件响应器匹配规则"""
|
||||
permission: ClassVar[Permission] = Permission()
|
||||
"""事件响应器触发权限"""
|
||||
handlers: List[Dependent[Any]] = []
|
||||
handlers: ClassVar[list[Dependent[Any]]] = []
|
||||
"""事件响应器拥有的事件处理函数列表"""
|
||||
priority: ClassVar[int] = 1
|
||||
"""事件响应器优先级"""
|
||||
@@ -160,7 +163,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
_default_permission_updater: ClassVar[Optional[Dependent[Permission]]] = None
|
||||
"""事件响应器权限更新函数"""
|
||||
|
||||
HANDLER_PARAM_TYPES: ClassVar[Tuple[Type[Param], ...]] = (
|
||||
HANDLER_PARAM_TYPES: ClassVar[tuple[Type[Param], ...]] = ( # noqa: UP006
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
@@ -171,7 +174,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.handlers = self.handlers.copy()
|
||||
self.remain_handlers: list[Dependent[Any]] = self.handlers.copy()
|
||||
self.state = self._default_state.copy()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -192,7 +195,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
type_: str = "",
|
||||
rule: Optional[Rule] = None,
|
||||
permission: Optional[Permission] = None,
|
||||
handlers: Optional[List[Union[T_Handler, Dependent[Any]]]] = None,
|
||||
handlers: Optional[list[Union[T_Handler, Dependent[Any]]]] = None,
|
||||
temp: bool = False,
|
||||
priority: int = 1,
|
||||
block: bool = False,
|
||||
@@ -206,7 +209,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
default_permission_updater: Optional[
|
||||
Union[T_PermissionUpdater, Dependent[Permission]]
|
||||
] = None,
|
||||
) -> Type[Self]:
|
||||
) -> Type[Self]: # noqa: UP006
|
||||
"""
|
||||
创建一个新的事件响应器,并存储至 `matchers <#matchers>`_
|
||||
|
||||
@@ -247,7 +250,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
)
|
||||
source = source or (
|
||||
MatcherSource(
|
||||
plugin_name=plugin and plugin.name,
|
||||
plugin_id=plugin and plugin.id_,
|
||||
module_name=module and module.__name__,
|
||||
)
|
||||
if plugin is not None or module is not None
|
||||
@@ -330,15 +333,20 @@ class Matcher(metaclass=MatcherMeta):
|
||||
return cls._source and cls._source.plugin
|
||||
|
||||
@classproperty
|
||||
def module(cls) -> Optional[ModuleType]:
|
||||
"""事件响应器所在插件模块"""
|
||||
return cls._source and cls._source.module
|
||||
def plugin_id(cls) -> Optional[str]:
|
||||
"""事件响应器所在插件标识符"""
|
||||
return cls._source and cls._source.plugin_id
|
||||
|
||||
@classproperty
|
||||
def plugin_name(cls) -> Optional[str]:
|
||||
"""事件响应器所在插件名"""
|
||||
return cls._source and cls._source.plugin_name
|
||||
|
||||
@classproperty
|
||||
def module(cls) -> Optional[ModuleType]:
|
||||
"""事件响应器所在插件模块"""
|
||||
return cls._source and cls._source.module
|
||||
|
||||
@classproperty
|
||||
def module_name(cls) -> Optional[str]:
|
||||
"""事件响应器所在插件模块路径"""
|
||||
@@ -457,7 +465,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
parameterless: 非参数类型依赖列表
|
||||
"""
|
||||
|
||||
async def _receive(event: Event, matcher: "Matcher") -> Union[None, NoReturn]:
|
||||
async def _receive(event: Event, matcher: "Matcher") -> None:
|
||||
matcher.set_target(RECEIVE_KEY.format(id=id))
|
||||
if matcher.get_target() == RECEIVE_KEY.format(id=id):
|
||||
matcher.set_receive(id, event)
|
||||
@@ -775,7 +783,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
|
||||
async def resolve_reject(self):
|
||||
handler = current_handler.get()
|
||||
self.handlers.insert(0, handler)
|
||||
self.remain_handlers.insert(0, handler)
|
||||
if REJECT_CACHE_TARGET in self.state:
|
||||
self.state[REJECT_TARGET] = self.state[REJECT_CACHE_TARGET]
|
||||
|
||||
@@ -809,8 +817,8 @@ class Matcher(metaclass=MatcherMeta):
|
||||
# Refresh preprocess state
|
||||
self.state.update(state)
|
||||
|
||||
while self.handlers:
|
||||
handler = self.handlers.pop(0)
|
||||
while self.remain_handlers:
|
||||
handler = self.remain_handlers.pop(0)
|
||||
current_handler.set(handler)
|
||||
logger.debug(f"Running handler {handler}")
|
||||
try:
|
||||
@@ -852,7 +860,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
type_,
|
||||
Rule(),
|
||||
permission,
|
||||
self.handlers,
|
||||
self.remain_handlers,
|
||||
temp=True,
|
||||
priority=0,
|
||||
block=True,
|
||||
@@ -872,7 +880,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
type_,
|
||||
Rule(),
|
||||
permission,
|
||||
self.handlers,
|
||||
self.remain_handlers,
|
||||
temp=True,
|
||||
priority=0,
|
||||
block=True,
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import abc
|
||||
from typing import TYPE_CHECKING
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, List, Type, Mapping, MutableMapping
|
||||
from collections.abc import Mapping, MutableMapping
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .matcher import Matcher
|
||||
|
||||
|
||||
class MatcherProvider(abc.ABC, MutableMapping[int, List[Type["Matcher"]]]):
|
||||
class MatcherProvider(abc.ABC, MutableMapping[int, list[type["Matcher"]]]):
|
||||
"""事件响应器存储器基类
|
||||
|
||||
参数:
|
||||
@@ -14,12 +15,12 @@ class MatcherProvider(abc.ABC, MutableMapping[int, List[Type["Matcher"]]]):
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __init__(self, matchers: Mapping[int, List[Type["Matcher"]]]):
|
||||
def __init__(self, matchers: Mapping[int, list[type["Matcher"]]]):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class _DictProvider(defaultdict, MatcherProvider):
|
||||
def __init__(self, matchers: Mapping[int, List[Type["Matcher"]]]):
|
||||
def __init__(self, matchers: Mapping[int, list[type["Matcher"]]]):
|
||||
super().__init__(list, matchers)
|
||||
|
||||
|
||||
|
@@ -1,16 +1,15 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
from typing_extensions import Self, get_args, override, get_origin
|
||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
||||
from typing_extensions import Self, Annotated, get_args, override, get_origin
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Literal,
|
||||
Callable,
|
||||
Optional,
|
||||
Annotated,
|
||||
cast,
|
||||
)
|
||||
|
||||
@@ -126,7 +125,7 @@ class DependParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
type_annotation, depends_inner = param.annotation, None
|
||||
# extract type annotation and dependency from Annotated
|
||||
@@ -166,7 +165,7 @@ class DependParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_parameterless(
|
||||
cls, value: Any, allow_types: Tuple[Type[Param], ...]
|
||||
cls, value: Any, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional["Param"]:
|
||||
if isinstance(value, DependsInner):
|
||||
assert value.dependency, "Dependency cannot be empty"
|
||||
@@ -249,7 +248,7 @@ class BotParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
from nonebot.adapters import Bot
|
||||
|
||||
@@ -266,11 +265,15 @@ class BotParam(Param):
|
||||
return cls()
|
||||
|
||||
@override
|
||||
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
||||
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, bot: "Bot", **kwargs: Any
|
||||
) -> Any:
|
||||
return bot
|
||||
|
||||
@override
|
||||
async def _check(self, bot: "Bot", **kwargs: Any) -> None:
|
||||
async def _check( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, bot: "Bot", **kwargs: Any
|
||||
) -> None:
|
||||
if self.checker is not None:
|
||||
check_field_type(self.checker, bot)
|
||||
|
||||
@@ -299,7 +302,7 @@ class EventParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
from nonebot.adapters import Event
|
||||
|
||||
@@ -316,11 +319,15 @@ class EventParam(Param):
|
||||
return cls()
|
||||
|
||||
@override
|
||||
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
||||
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, event: "Event", **kwargs: Any
|
||||
) -> Any:
|
||||
return event
|
||||
|
||||
@override
|
||||
async def _check(self, event: "Event", **kwargs: Any) -> Any:
|
||||
async def _check( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, event: "Event", **kwargs: Any
|
||||
) -> Any:
|
||||
if self.checker is not None:
|
||||
check_field_type(self.checker, event)
|
||||
|
||||
@@ -339,7 +346,7 @@ class StateParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
# param type is T_State
|
||||
if param.annotation is T_State:
|
||||
@@ -349,7 +356,9 @@ class StateParam(Param):
|
||||
return cls()
|
||||
|
||||
@override
|
||||
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
||||
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, state: T_State, **kwargs: Any
|
||||
) -> Any:
|
||||
return state
|
||||
|
||||
|
||||
@@ -377,7 +386,7 @@ class MatcherParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
@@ -394,11 +403,15 @@ class MatcherParam(Param):
|
||||
return cls()
|
||||
|
||||
@override
|
||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, matcher: "Matcher", **kwargs: Any
|
||||
) -> Any:
|
||||
return matcher
|
||||
|
||||
@override
|
||||
async def _check(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
async def _check( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, matcher: "Matcher", **kwargs: Any
|
||||
) -> Any:
|
||||
if self.checker is not None:
|
||||
check_field_type(self.checker, matcher)
|
||||
|
||||
@@ -455,7 +468,7 @@ class ArgParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
if isinstance(param.default, ArgInner):
|
||||
return cls(key=param.default.key or param.name, type=param.default.type)
|
||||
@@ -464,7 +477,9 @@ class ArgParam(Param):
|
||||
if isinstance(arg, ArgInner):
|
||||
return cls(key=arg.key or param.name, type=arg.type)
|
||||
|
||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, matcher: "Matcher", **kwargs: Any
|
||||
) -> Any:
|
||||
message = matcher.get_arg(self.key)
|
||||
if message is None:
|
||||
return message
|
||||
@@ -490,7 +505,7 @@ class ExceptionParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
# param type is Exception(s) or subclass(es) of Exception or None
|
||||
if generic_check_issubclass(param.annotation, Exception):
|
||||
@@ -518,7 +533,7 @@ class DefaultParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
if param.default != param.empty:
|
||||
return cls(default=param.default)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
from typing_extensions import Self
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import Set, Tuple, Union, NoReturn, Optional
|
||||
from typing import Union, ClassVar, NoReturn, Optional
|
||||
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.utils import run_coro_with_catch
|
||||
@@ -9,7 +9,7 @@ from nonebot.exception import SkippedException
|
||||
from nonebot.typing import T_DependencyCache, T_PermissionChecker
|
||||
|
||||
from .adapter import Bot, Event
|
||||
from .params import BotParam, EventParam, DependParam, DefaultParam
|
||||
from .params import Param, BotParam, EventParam, DependParam, DefaultParam
|
||||
|
||||
|
||||
class Permission:
|
||||
@@ -30,7 +30,7 @@ class Permission:
|
||||
|
||||
__slots__ = ("checkers",)
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
HANDLER_PARAM_TYPES: ClassVar[list[type[Param]]] = [
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
@@ -38,7 +38,7 @@ class Permission:
|
||||
]
|
||||
|
||||
def __init__(self, *checkers: Union[T_PermissionChecker, Dependent[bool]]) -> None:
|
||||
self.checkers: Set[Dependent[bool]] = {
|
||||
self.checkers: set[Dependent[bool]] = {
|
||||
(
|
||||
checker
|
||||
if isinstance(checker, Dependent)
|
||||
@@ -122,7 +122,7 @@ class User:
|
||||
__slots__ = ("users", "perm")
|
||||
|
||||
def __init__(
|
||||
self, users: Tuple[str, ...], perm: Optional[Permission] = None
|
||||
self, users: tuple[str, ...], perm: Optional[Permission] = None
|
||||
) -> None:
|
||||
self.users = users
|
||||
self.perm = perm
|
||||
@@ -146,7 +146,7 @@ class User:
|
||||
@classmethod
|
||||
def _clean_permission(cls, perm: Permission) -> Optional[Permission]:
|
||||
if len(perm.checkers) == 1 and isinstance(
|
||||
user_perm := tuple(perm.checkers)[0].call, cls
|
||||
user_perm := next(iter(perm.checkers)).call, cls
|
||||
):
|
||||
return user_perm.perm
|
||||
return perm
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import asyncio
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import Set, Union, NoReturn, Optional
|
||||
from typing import Union, ClassVar, NoReturn, Optional
|
||||
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.exception import SkippedException
|
||||
from nonebot.typing import T_State, T_RuleChecker, T_DependencyCache
|
||||
|
||||
from .adapter import Bot, Event
|
||||
from .params import BotParam, EventParam, StateParam, DependParam, DefaultParam
|
||||
from .params import Param, BotParam, EventParam, StateParam, DependParam, DefaultParam
|
||||
|
||||
|
||||
class Rule:
|
||||
@@ -28,7 +28,7 @@ class Rule:
|
||||
|
||||
__slots__ = ("checkers",)
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
HANDLER_PARAM_TYPES: ClassVar[list[type[Param]]] = [
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
@@ -37,7 +37,7 @@ class Rule:
|
||||
]
|
||||
|
||||
def __init__(self, *checkers: Union[T_RuleChecker, Dependent[bool]]) -> None:
|
||||
self.checkers: Set[Dependent[bool]] = {
|
||||
self.checkers: set[Dependent[bool]] = {
|
||||
(
|
||||
checker
|
||||
if isinstance(checker, Dependent)
|
||||
|
@@ -13,6 +13,7 @@ FrontMatter:
|
||||
"""
|
||||
|
||||
import sys
|
||||
import inspect
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -45,6 +46,7 @@ logger: "Logger" = loguru.logger
|
||||
# logger.addHandler(default_handler)
|
||||
|
||||
|
||||
# https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
|
||||
class LoguruHandler(logging.Handler): # pragma: no cover
|
||||
"""logging 与 loguru 之间的桥梁,将 logging 的日志转发到 loguru。"""
|
||||
|
||||
@@ -54,8 +56,8 @@ class LoguruHandler(logging.Handler): # pragma: no cover
|
||||
except ValueError:
|
||||
level = record.levelno
|
||||
|
||||
frame, depth = sys._getframe(6), 6
|
||||
while frame and frame.f_code.co_filename == logging.__file__:
|
||||
frame, depth = inspect.currentframe(), 0
|
||||
while frame and (depth == 0 or frame.f_code.co_filename == logging.__file__):
|
||||
frame = frame.f_back
|
||||
depth += 1
|
||||
|
||||
|
@@ -11,7 +11,7 @@ import asyncio
|
||||
import contextlib
|
||||
from datetime import datetime
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.rule import TrieRule
|
||||
@@ -46,10 +46,10 @@ from nonebot.internal.params import (
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.adapters import Bot, Event
|
||||
|
||||
_event_preprocessors: Set[Dependent[Any]] = set()
|
||||
_event_postprocessors: Set[Dependent[Any]] = set()
|
||||
_run_preprocessors: Set[Dependent[Any]] = set()
|
||||
_run_postprocessors: Set[Dependent[Any]] = set()
|
||||
_event_preprocessors: set[Dependent[Any]] = set()
|
||||
_event_postprocessors: set[Dependent[Any]] = set()
|
||||
_run_preprocessors: set[Dependent[Any]] = set()
|
||||
_run_postprocessors: set[Dependent[Any]] = set()
|
||||
|
||||
EVENT_PCS_PARAMS = (
|
||||
DependParam,
|
||||
@@ -330,7 +330,7 @@ async def _apply_run_postprocessors(
|
||||
|
||||
|
||||
async def _check_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
Matcher: type[Matcher],
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
@@ -381,7 +381,7 @@ async def _check_matcher(
|
||||
|
||||
|
||||
async def _run_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
Matcher: type[Matcher],
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
@@ -446,7 +446,7 @@ async def _run_matcher(
|
||||
|
||||
|
||||
async def check_and_run_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
Matcher: type[Matcher],
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
@@ -505,7 +505,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
if show_log:
|
||||
logger.opt(colors=True).success(log_msg)
|
||||
|
||||
state: Dict[Any, Any] = {}
|
||||
state: dict[Any, Any] = {}
|
||||
dependency_cache: T_DependencyCache = {}
|
||||
|
||||
# create event scope context
|
||||
|
@@ -5,18 +5,8 @@ FrontMatter:
|
||||
description: nonebot.params 模块
|
||||
"""
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Match,
|
||||
Tuple,
|
||||
Union,
|
||||
Literal,
|
||||
Callable,
|
||||
Optional,
|
||||
overload,
|
||||
)
|
||||
from re import Match
|
||||
from typing import Any, Union, Literal, Callable, Optional, overload
|
||||
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.matcher import Matcher
|
||||
@@ -90,7 +80,7 @@ def _command(state: T_State) -> Message:
|
||||
return state[PREFIX_KEY][CMD_KEY]
|
||||
|
||||
|
||||
def Command() -> Tuple[str, ...]:
|
||||
def Command() -> tuple[str, ...]:
|
||||
"""消息命令元组"""
|
||||
return Depends(_command)
|
||||
|
||||
@@ -140,7 +130,7 @@ def ShellCommandArgs() -> Any:
|
||||
return Depends(_shell_command_args, use_cache=False)
|
||||
|
||||
|
||||
def _shell_command_argv(state: T_State) -> List[Union[str, MessageSegment]]:
|
||||
def _shell_command_argv(state: T_State) -> list[Union[str, MessageSegment]]:
|
||||
return state[SHELL_ARGV]
|
||||
|
||||
|
||||
@@ -159,11 +149,11 @@ def RegexMatched() -> Match[str]:
|
||||
|
||||
|
||||
def _regex_str(
|
||||
groups: Tuple[Union[str, int], ...]
|
||||
) -> Callable[[T_State], Union[str, Tuple[Union[str, Any], ...], Any]]:
|
||||
groups: tuple[Union[str, int], ...]
|
||||
) -> Callable[[T_State], Union[str, tuple[Union[str, Any], ...], Any]]:
|
||||
def _regex_str_dependency(
|
||||
state: T_State,
|
||||
) -> Union[str, Tuple[Union[str, Any], ...], Any]:
|
||||
) -> Union[str, tuple[Union[str, Any], ...], Any]:
|
||||
return _regex_matched(state).group(*groups)
|
||||
|
||||
return _regex_str_dependency
|
||||
@@ -180,28 +170,28 @@ def RegexStr(__group: Union[str, int]) -> Union[str, Any]: ...
|
||||
@overload
|
||||
def RegexStr(
|
||||
__group1: Union[str, int], __group2: Union[str, int], *groups: Union[str, int]
|
||||
) -> Tuple[Union[str, Any], ...]: ...
|
||||
) -> tuple[Union[str, Any], ...]: ...
|
||||
|
||||
|
||||
def RegexStr(*groups: Union[str, int]) -> Union[str, Tuple[Union[str, Any], ...], Any]:
|
||||
def RegexStr(*groups: Union[str, int]) -> Union[str, tuple[Union[str, Any], ...], Any]:
|
||||
"""正则匹配结果文本"""
|
||||
return Depends(_regex_str(groups), use_cache=False)
|
||||
|
||||
|
||||
def _regex_group(state: T_State) -> Tuple[Any, ...]:
|
||||
def _regex_group(state: T_State) -> tuple[Any, ...]:
|
||||
return _regex_matched(state).groups()
|
||||
|
||||
|
||||
def RegexGroup() -> Tuple[Any, ...]:
|
||||
def RegexGroup() -> tuple[Any, ...]:
|
||||
"""正则匹配结果 group 元组"""
|
||||
return Depends(_regex_group, use_cache=False)
|
||||
|
||||
|
||||
def _regex_dict(state: T_State) -> Dict[str, Any]:
|
||||
def _regex_dict(state: T_State) -> dict[str, Any]:
|
||||
return _regex_matched(state).groupdict()
|
||||
|
||||
|
||||
def RegexDict() -> Dict[str, Any]:
|
||||
def RegexDict() -> dict[str, Any]:
|
||||
"""正则匹配结果 group 字典"""
|
||||
return Depends(_regex_dict, use_cache=False)
|
||||
|
||||
|
@@ -39,7 +39,7 @@ FrontMatter:
|
||||
from itertools import chain
|
||||
from types import ModuleType
|
||||
from contextvars import ContextVar
|
||||
from typing import Set, Dict, List, Type, Tuple, TypeVar, Optional
|
||||
from typing import TypeVar, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -48,10 +48,10 @@ from nonebot.compat import model_dump, type_validate_python
|
||||
|
||||
C = TypeVar("C", bound=BaseModel)
|
||||
|
||||
_plugins: Dict[str, "Plugin"] = {}
|
||||
_managers: List["PluginManager"] = []
|
||||
_current_plugin_chain: ContextVar[Tuple["Plugin", ...]] = ContextVar(
|
||||
"_current_plugin_chain", default=()
|
||||
_plugins: dict[str, "Plugin"] = {}
|
||||
_managers: list["PluginManager"] = []
|
||||
_current_plugin: ContextVar[Optional["Plugin"]] = ContextVar(
|
||||
"_current_plugin", default=None
|
||||
)
|
||||
|
||||
|
||||
@@ -59,34 +59,87 @@ def _module_name_to_plugin_name(module_name: str) -> str:
|
||||
return module_name.rsplit(".", 1)[-1]
|
||||
|
||||
|
||||
def _controlled_modules() -> dict[str, str]:
|
||||
return {
|
||||
plugin_id: module_name
|
||||
for manager in _managers
|
||||
for plugin_id, module_name in manager.controlled_modules.items()
|
||||
}
|
||||
|
||||
|
||||
def _find_parent_plugin_id(
|
||||
module_name: str, controlled_modules: Optional[dict[str, str]] = None
|
||||
) -> Optional[str]:
|
||||
if controlled_modules is None:
|
||||
controlled_modules = _controlled_modules()
|
||||
available = {
|
||||
module_name: plugin_id for plugin_id, module_name in controlled_modules.items()
|
||||
}
|
||||
while "." in module_name:
|
||||
module_name, _ = module_name.rsplit(".", 1)
|
||||
if module_name in available:
|
||||
return available[module_name]
|
||||
|
||||
|
||||
def _module_name_to_plugin_id(
|
||||
module_name: str, controlled_modules: Optional[dict[str, str]] = None
|
||||
) -> str:
|
||||
plugin_name = _module_name_to_plugin_name(module_name)
|
||||
if parent_plugin_id := _find_parent_plugin_id(module_name, controlled_modules):
|
||||
return f"{parent_plugin_id}:{plugin_name}"
|
||||
return plugin_name
|
||||
|
||||
|
||||
def _new_plugin(
|
||||
module_name: str, module: ModuleType, manager: "PluginManager"
|
||||
) -> "Plugin":
|
||||
plugin_name = _module_name_to_plugin_name(module_name)
|
||||
if plugin_name in _plugins:
|
||||
raise RuntimeError("Plugin already exists! Check your plugin name.")
|
||||
plugin = Plugin(plugin_name, module, module_name, manager)
|
||||
_plugins[plugin_name] = plugin
|
||||
plugin_id = _module_name_to_plugin_id(module_name)
|
||||
if plugin_id in _plugins:
|
||||
raise RuntimeError(
|
||||
f"Plugin {plugin_id} already exists! Check your plugin name."
|
||||
)
|
||||
|
||||
parent_plugin_id = _find_parent_plugin_id(module_name)
|
||||
if parent_plugin_id is not None and parent_plugin_id not in _plugins:
|
||||
raise RuntimeError(
|
||||
f"Parent plugin {parent_plugin_id} must "
|
||||
f"be loaded before loading {plugin_id}."
|
||||
)
|
||||
parent_plugin = _plugins[parent_plugin_id] if parent_plugin_id is not None else None
|
||||
|
||||
plugin = Plugin(
|
||||
name=_module_name_to_plugin_name(module_name),
|
||||
module=module,
|
||||
module_name=module_name,
|
||||
manager=manager,
|
||||
parent_plugin=parent_plugin,
|
||||
)
|
||||
if parent_plugin:
|
||||
parent_plugin.sub_plugins.add(plugin)
|
||||
|
||||
_plugins[plugin_id] = plugin
|
||||
return plugin
|
||||
|
||||
|
||||
def _revert_plugin(plugin: "Plugin") -> None:
|
||||
if plugin.name not in _plugins:
|
||||
if plugin.id_ not in _plugins:
|
||||
raise RuntimeError("Plugin not found!")
|
||||
del _plugins[plugin.name]
|
||||
del _plugins[plugin.id_]
|
||||
if parent_plugin := plugin.parent_plugin:
|
||||
parent_plugin.sub_plugins.remove(plugin)
|
||||
parent_plugin.sub_plugins.discard(plugin)
|
||||
|
||||
|
||||
def get_plugin(name: str) -> Optional["Plugin"]:
|
||||
def get_plugin(plugin_id: str) -> Optional["Plugin"]:
|
||||
"""获取已经导入的某个插件。
|
||||
|
||||
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
||||
|
||||
如果为嵌套的子插件,标识符为 `父插件标识符:子插件文件(夹)名`。
|
||||
|
||||
参数:
|
||||
name: 插件名,即 {ref}`nonebot.plugin.model.Plugin.name`。
|
||||
plugin_id: 插件标识符,即 {ref}`nonebot.plugin.model.Plugin.id_`。
|
||||
"""
|
||||
return _plugins.get(name)
|
||||
return _plugins.get(plugin_id)
|
||||
|
||||
|
||||
def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]:
|
||||
@@ -105,17 +158,17 @@ def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]:
|
||||
module_name, *has_parent = module_name.rsplit(".", 1)
|
||||
|
||||
|
||||
def get_loaded_plugins() -> Set["Plugin"]:
|
||||
def get_loaded_plugins() -> set["Plugin"]:
|
||||
"""获取当前已导入的所有插件。"""
|
||||
return set(_plugins.values())
|
||||
|
||||
|
||||
def get_available_plugin_names() -> Set[str]:
|
||||
"""获取当前所有可用的插件名(包含尚未加载的插件)。"""
|
||||
def get_available_plugin_names() -> set[str]:
|
||||
"""获取当前所有可用的插件标识符(包含尚未加载的插件)。"""
|
||||
return {*chain.from_iterable(manager.available_plugins for manager in _managers)}
|
||||
|
||||
|
||||
def get_plugin_config(config: Type[C]) -> C:
|
||||
def get_plugin_config(config: type[C]) -> C:
|
||||
"""从全局配置获取当前插件需要的配置项。"""
|
||||
return type_validate_python(config, model_dump(get_driver().config))
|
||||
|
||||
|
@@ -8,13 +8,14 @@ FrontMatter:
|
||||
import json
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Set, Union, Iterable, Optional
|
||||
from typing import Union, Optional
|
||||
from collections.abc import Iterable
|
||||
|
||||
from nonebot.utils import path_to_module_name
|
||||
|
||||
from .model import Plugin
|
||||
from .manager import PluginManager
|
||||
from . import _managers, get_plugin, _current_plugin_chain, _module_name_to_plugin_name
|
||||
from . import _managers, get_plugin, _module_name_to_plugin_id
|
||||
|
||||
try: # pragma: py-gte-311
|
||||
import tomllib # pyright: ignore[reportMissingImports]
|
||||
@@ -39,7 +40,7 @@ def load_plugin(module_path: Union[str, Path]) -> Optional[Plugin]:
|
||||
return manager.load_plugin(module_path)
|
||||
|
||||
|
||||
def load_plugins(*plugin_dir: str) -> Set[Plugin]:
|
||||
def load_plugins(*plugin_dir: str) -> set[Plugin]:
|
||||
"""导入文件夹下多个插件,以 `_` 开头的插件不会被导入!
|
||||
|
||||
参数:
|
||||
@@ -52,7 +53,7 @@ def load_plugins(*plugin_dir: str) -> Set[Plugin]:
|
||||
|
||||
def load_all_plugins(
|
||||
module_path: Iterable[str], plugin_dir: Iterable[str]
|
||||
) -> Set[Plugin]:
|
||||
) -> set[Plugin]:
|
||||
"""导入指定列表中的插件以及指定目录下多个插件,以 `_` 开头的插件不会被导入!
|
||||
|
||||
参数:
|
||||
@@ -64,7 +65,7 @@ def load_all_plugins(
|
||||
return manager.load_all_plugins()
|
||||
|
||||
|
||||
def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
def load_from_json(file_path: str, encoding: str = "utf-8") -> set[Plugin]:
|
||||
"""导入指定 json 文件中的 `plugins` 以及 `plugin_dirs` 下多个插件。
|
||||
以 `_` 开头的插件不会被导入!
|
||||
|
||||
@@ -95,7 +96,7 @@ def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
return load_all_plugins(set(plugins), set(plugin_dirs))
|
||||
|
||||
|
||||
def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
def load_from_toml(file_path: str, encoding: str = "utf-8") -> set[Plugin]:
|
||||
"""导入指定 toml 文件 `[tool.nonebot]` 中的
|
||||
`plugins` 以及 `plugin_dirs` 下多个插件。
|
||||
以 `_` 开头的插件不会被导入!
|
||||
@@ -139,7 +140,7 @@ def load_builtin_plugin(name: str) -> Optional[Plugin]:
|
||||
return load_plugin(f"nonebot.plugins.{name}")
|
||||
|
||||
|
||||
def load_builtin_plugins(*plugins: str) -> Set[Plugin]:
|
||||
def load_builtin_plugins(*plugins: str) -> set[Plugin]:
|
||||
"""导入多个 NoneBot 内置插件。
|
||||
|
||||
参数:
|
||||
@@ -150,41 +151,45 @@ def load_builtin_plugins(*plugins: str) -> Set[Plugin]:
|
||||
|
||||
def _find_manager_by_name(name: str) -> Optional[PluginManager]:
|
||||
for manager in reversed(_managers):
|
||||
if name in manager.plugins or name in manager.searched_plugins:
|
||||
if (
|
||||
name in manager.controlled_modules
|
||||
or name in manager.controlled_modules.values()
|
||||
):
|
||||
return manager
|
||||
|
||||
|
||||
def require(name: str) -> ModuleType:
|
||||
"""获取一个插件的导出内容。
|
||||
|
||||
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
||||
"""声明依赖插件。
|
||||
|
||||
参数:
|
||||
name: 插件名,即 {ref}`nonebot.plugin.model.Plugin.name`。
|
||||
name: 插件模块名或插件标识符,仅在已声明插件的情况下可使用标识符。
|
||||
|
||||
异常:
|
||||
RuntimeError: 插件无法加载
|
||||
"""
|
||||
plugin = get_plugin(_module_name_to_plugin_name(name))
|
||||
if "." in name:
|
||||
# name is a module name
|
||||
plugin = get_plugin(_module_name_to_plugin_id(name))
|
||||
else:
|
||||
# name is a plugin id or simple module name (equals to plugin id)
|
||||
plugin = get_plugin(name)
|
||||
|
||||
# if plugin not loaded
|
||||
if not plugin:
|
||||
# plugin already declared
|
||||
if plugin is None:
|
||||
# plugin already declared, module name / plugin id
|
||||
if manager := _find_manager_by_name(name):
|
||||
plugin = manager.load_plugin(name)
|
||||
|
||||
# plugin not declared, try to declare and load it
|
||||
else:
|
||||
# clear current plugin chain, ensure plugin loaded in a new context
|
||||
_t = _current_plugin_chain.set(())
|
||||
try:
|
||||
plugin = load_plugin(name)
|
||||
finally:
|
||||
_current_plugin_chain.reset(_t)
|
||||
if not plugin:
|
||||
|
||||
if plugin is None:
|
||||
raise RuntimeError(f'Cannot load plugin "{name}"!')
|
||||
return plugin.module
|
||||
|
||||
|
||||
def inherit_supported_adapters(*names: str) -> Optional[Set[str]]:
|
||||
def inherit_supported_adapters(*names: str) -> Optional[set[str]]:
|
||||
"""获取已加载插件的适配器支持状态集合。
|
||||
|
||||
如果传入了多个插件名称,返回值会自动取交集。
|
||||
@@ -196,27 +201,28 @@ def inherit_supported_adapters(*names: str) -> Optional[Set[str]]:
|
||||
RuntimeError: 插件未加载
|
||||
ValueError: 插件缺少元数据
|
||||
"""
|
||||
final_supported: Optional[Set[str]] = None
|
||||
final_supported: Optional[set[str]] = None
|
||||
|
||||
for name in names:
|
||||
plugin = get_plugin(_module_name_to_plugin_name(name))
|
||||
plugin = get_plugin(_module_name_to_plugin_id(name))
|
||||
if plugin is None:
|
||||
raise RuntimeError(f'Plugin "{name}" is not loaded!')
|
||||
raise RuntimeError(
|
||||
f'Plugin "{name}" is not loaded! You should require it first.'
|
||||
)
|
||||
meta = plugin.metadata
|
||||
if meta is None:
|
||||
raise ValueError(f'Plugin "{name}" has no metadata!')
|
||||
support = meta.supported_adapters
|
||||
if support is None:
|
||||
|
||||
if (raw := meta.supported_adapters) is None:
|
||||
continue
|
||||
|
||||
support = {
|
||||
f"nonebot.adapters.{adapter[1:]}" if adapter.startswith("~") else adapter
|
||||
for adapter in raw
|
||||
}
|
||||
|
||||
final_supported = (
|
||||
support if final_supported is None else (final_supported & support)
|
||||
)
|
||||
|
||||
return final_supported and {
|
||||
(
|
||||
f"nonebot.adapters.{adapter_name[1:]}"
|
||||
if adapter_name.startswith("~")
|
||||
else adapter_name
|
||||
)
|
||||
for adapter_name in final_supported
|
||||
}
|
||||
return final_supported
|
||||
|
@@ -12,10 +12,11 @@ import pkgutil
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from itertools import chain
|
||||
from typing import Optional
|
||||
from types import ModuleType
|
||||
from importlib.abc import MetaPathFinder
|
||||
from collections.abc import Iterable, Sequence
|
||||
from importlib.machinery import PathFinder, SourceFileLoader
|
||||
from typing import Set, Dict, List, Iterable, Optional, Sequence
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.utils import escape_tag, path_to_module_name
|
||||
@@ -25,8 +26,8 @@ from . import (
|
||||
_managers,
|
||||
_new_plugin,
|
||||
_revert_plugin,
|
||||
_current_plugin_chain,
|
||||
_module_name_to_plugin_name,
|
||||
_current_plugin,
|
||||
_module_name_to_plugin_id,
|
||||
)
|
||||
|
||||
|
||||
@@ -35,7 +36,7 @@ class PluginManager:
|
||||
|
||||
参数:
|
||||
plugins: 独立插件模块名集合。
|
||||
search_path: 插件搜索路径(文件夹)。
|
||||
search_path: 插件搜索路径(文件夹),相对于当前工作目录。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -44,60 +45,78 @@ class PluginManager:
|
||||
search_path: Optional[Iterable[str]] = None,
|
||||
):
|
||||
# simple plugin not in search path
|
||||
self.plugins: Set[str] = set(plugins or [])
|
||||
self.search_path: Set[str] = set(search_path or [])
|
||||
self.plugins: set[str] = set(plugins or [])
|
||||
self.search_path: set[str] = set(search_path or [])
|
||||
|
||||
# cache plugins
|
||||
self._third_party_plugin_names: Dict[str, str] = {}
|
||||
self._searched_plugin_names: Dict[str, Path] = {}
|
||||
self.prepare_plugins()
|
||||
self._third_party_plugin_ids: dict[str, str] = {}
|
||||
self._searched_plugin_ids: dict[str, str] = {}
|
||||
self._prepare_plugins()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"PluginManager(plugins={self.plugins}, search_path={self.search_path})"
|
||||
return f"PluginManager(available_plugins={self.controlled_modules})"
|
||||
|
||||
@property
|
||||
def third_party_plugins(self) -> Set[str]:
|
||||
"""返回所有独立插件名称。"""
|
||||
return set(self._third_party_plugin_names.keys())
|
||||
def third_party_plugins(self) -> set[str]:
|
||||
"""返回所有独立插件标识符。"""
|
||||
return set(self._third_party_plugin_ids.keys())
|
||||
|
||||
@property
|
||||
def searched_plugins(self) -> Set[str]:
|
||||
"""返回已搜索到的插件名称。"""
|
||||
return set(self._searched_plugin_names.keys())
|
||||
def searched_plugins(self) -> set[str]:
|
||||
"""返回已搜索到的插件标识符。"""
|
||||
return set(self._searched_plugin_ids.keys())
|
||||
|
||||
@property
|
||||
def available_plugins(self) -> Set[str]:
|
||||
"""返回当前插件管理器中可用的插件名称。"""
|
||||
def available_plugins(self) -> set[str]:
|
||||
"""返回当前插件管理器中可用的插件标识符。"""
|
||||
return self.third_party_plugins | self.searched_plugins
|
||||
|
||||
def _previous_plugins(self) -> Set[str]:
|
||||
_pre_managers: List[PluginManager]
|
||||
@property
|
||||
def controlled_modules(self) -> dict[str, str]:
|
||||
"""返回当前插件管理器中控制的插件标识符与模块路径映射字典。"""
|
||||
return dict(
|
||||
chain(
|
||||
self._third_party_plugin_ids.items(), self._searched_plugin_ids.items()
|
||||
)
|
||||
)
|
||||
|
||||
def _previous_controlled_modules(self) -> dict[str, str]:
|
||||
_pre_managers: list[PluginManager]
|
||||
if self in _managers:
|
||||
_pre_managers = _managers[: _managers.index(self)]
|
||||
else:
|
||||
_pre_managers = _managers[:]
|
||||
|
||||
return {
|
||||
*chain.from_iterable(manager.available_plugins for manager in _pre_managers)
|
||||
plugin_id: module_name
|
||||
for manager in _pre_managers
|
||||
for plugin_id, module_name in manager.controlled_modules.items()
|
||||
}
|
||||
|
||||
def prepare_plugins(self) -> Set[str]:
|
||||
def _prepare_plugins(self) -> set[str]:
|
||||
"""搜索插件并缓存插件名称。"""
|
||||
# get all previous ready to load plugins
|
||||
previous_plugins = self._previous_plugins()
|
||||
searched_plugins: Dict[str, Path] = {}
|
||||
third_party_plugins: Dict[str, str] = {}
|
||||
previous_plugin_ids = self._previous_controlled_modules()
|
||||
|
||||
# if self not in global managers, merge self's controlled modules
|
||||
def get_controlled_modules():
|
||||
return (
|
||||
previous_plugin_ids
|
||||
if self in _managers
|
||||
else {**previous_plugin_ids, **self.controlled_modules}
|
||||
)
|
||||
|
||||
# check third party plugins
|
||||
for plugin in self.plugins:
|
||||
name = _module_name_to_plugin_name(plugin)
|
||||
if name in third_party_plugins or name in previous_plugins:
|
||||
plugin_id = _module_name_to_plugin_id(plugin, get_controlled_modules())
|
||||
if (
|
||||
plugin_id in self._third_party_plugin_ids
|
||||
or plugin_id in previous_plugin_ids
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"Plugin already exists: {name}! Check your plugin name"
|
||||
f"Plugin already exists: {plugin_id}! Check your plugin name"
|
||||
)
|
||||
third_party_plugins[name] = plugin
|
||||
|
||||
self._third_party_plugin_names = third_party_plugins
|
||||
self._third_party_plugin_ids[plugin_id] = plugin
|
||||
|
||||
# check plugins in search path
|
||||
for module_info in pkgutil.iter_modules(self.search_path):
|
||||
@@ -105,47 +124,55 @@ class PluginManager:
|
||||
if module_info.name.startswith("_"):
|
||||
continue
|
||||
|
||||
if (
|
||||
module_info.name in searched_plugins
|
||||
or module_info.name in previous_plugins
|
||||
or module_info.name in third_party_plugins
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"Plugin already exists: {module_info.name}! Check your plugin name"
|
||||
)
|
||||
|
||||
if not (
|
||||
module_spec := module_info.module_finder.find_spec(
|
||||
module_info.name, None
|
||||
)
|
||||
):
|
||||
continue
|
||||
if not (module_path := module_spec.origin):
|
||||
continue
|
||||
searched_plugins[module_info.name] = Path(module_path).resolve()
|
||||
|
||||
self._searched_plugin_names = searched_plugins
|
||||
if not module_spec.origin:
|
||||
continue
|
||||
|
||||
# get module name from path, pkgutil does not return the actual module name
|
||||
module_path = Path(module_spec.origin).resolve()
|
||||
module_name = path_to_module_name(module_path)
|
||||
plugin_id = _module_name_to_plugin_id(module_name, get_controlled_modules())
|
||||
|
||||
if (
|
||||
plugin_id in previous_plugin_ids
|
||||
or plugin_id in self._third_party_plugin_ids
|
||||
or plugin_id in self._searched_plugin_ids
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"Plugin already exists: {plugin_id}! Check your plugin name"
|
||||
)
|
||||
|
||||
self._searched_plugin_ids[plugin_id] = module_name
|
||||
|
||||
return self.available_plugins
|
||||
|
||||
def load_plugin(self, name: str) -> Optional[Plugin]:
|
||||
"""加载指定插件。
|
||||
|
||||
对于独立插件,可以使用完整插件模块名或者插件名称。
|
||||
可以使用完整插件模块名或者插件标识符加载。
|
||||
|
||||
参数:
|
||||
name: 插件名称。
|
||||
name: 插件名称或插件标识符。
|
||||
"""
|
||||
|
||||
try:
|
||||
if name in self.plugins:
|
||||
# load using plugin id
|
||||
if name in self._third_party_plugin_ids:
|
||||
module = importlib.import_module(self._third_party_plugin_ids[name])
|
||||
elif name in self._searched_plugin_ids:
|
||||
module = importlib.import_module(self._searched_plugin_ids[name])
|
||||
# load using module name
|
||||
elif (
|
||||
name in self._third_party_plugin_ids.values()
|
||||
or name in self._searched_plugin_ids.values()
|
||||
):
|
||||
module = importlib.import_module(name)
|
||||
elif name in self._third_party_plugin_names:
|
||||
module = importlib.import_module(self._third_party_plugin_names[name])
|
||||
elif name in self._searched_plugin_names:
|
||||
module = importlib.import_module(
|
||||
path_to_module_name(self._searched_plugin_names[name])
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name")
|
||||
|
||||
@@ -154,13 +181,13 @@ class PluginManager:
|
||||
) is None or not isinstance(plugin, Plugin):
|
||||
raise RuntimeError(
|
||||
f"Module {module.__name__} is not loaded as a plugin! "
|
||||
"Make sure not to import it before loading."
|
||||
f"Make sure not to import it before loading."
|
||||
)
|
||||
logger.opt(colors=True).success(
|
||||
f'Succeeded to load plugin "<y>{escape_tag(plugin.name)}</y>"'
|
||||
f'Succeeded to load plugin "<y>{escape_tag(plugin.id_)}</y>"'
|
||||
+ (
|
||||
f' from "<m>{escape_tag(plugin.module_name)}</m>"'
|
||||
if plugin.module_name != plugin.name
|
||||
if plugin.module_name != plugin.id_
|
||||
else ""
|
||||
)
|
||||
)
|
||||
@@ -170,7 +197,7 @@ class PluginManager:
|
||||
f'<r><bg #f8bbd0>Failed to import "{escape_tag(name)}"</bg #f8bbd0></r>'
|
||||
)
|
||||
|
||||
def load_all_plugins(self) -> Set[Plugin]:
|
||||
def load_all_plugins(self) -> set[Plugin]:
|
||||
"""加载所有可用插件。"""
|
||||
|
||||
return set(
|
||||
@@ -192,21 +219,16 @@ class PluginFinder(MetaPathFinder):
|
||||
module_origin = module_spec.origin
|
||||
if not module_origin:
|
||||
return
|
||||
module_path = Path(module_origin).resolve()
|
||||
|
||||
for manager in reversed(_managers):
|
||||
# use path instead of name in case of submodule name conflict
|
||||
if (
|
||||
fullname in manager.plugins
|
||||
or module_path in manager._searched_plugin_names.values()
|
||||
):
|
||||
if fullname in manager.controlled_modules.values():
|
||||
module_spec.loader = PluginLoader(manager, fullname, module_origin)
|
||||
return module_spec
|
||||
return
|
||||
|
||||
|
||||
class PluginLoader(SourceFileLoader):
|
||||
def __init__(self, manager: PluginManager, fullname: str, path) -> None:
|
||||
def __init__(self, manager: PluginManager, fullname: str, path: str) -> None:
|
||||
self.manager = manager
|
||||
self.loaded = False
|
||||
super().__init__(fullname, path)
|
||||
@@ -226,17 +248,8 @@ class PluginLoader(SourceFileLoader):
|
||||
plugin = _new_plugin(self.name, module, self.manager)
|
||||
setattr(module, "__plugin__", plugin)
|
||||
|
||||
# detect parent plugin before entering current plugin context
|
||||
parent_plugins = _current_plugin_chain.get()
|
||||
for pre_plugin in reversed(parent_plugins):
|
||||
# ensure parent plugin is declared before current plugin
|
||||
if _managers.index(pre_plugin.manager) < _managers.index(self.manager):
|
||||
plugin.parent_plugin = pre_plugin
|
||||
pre_plugin.sub_plugins.add(plugin)
|
||||
break
|
||||
|
||||
# enter plugin context
|
||||
_plugin_token = _current_plugin_chain.set(parent_plugins + (plugin,))
|
||||
_plugin_token = _current_plugin.set(plugin)
|
||||
|
||||
try:
|
||||
super().exec_module(module)
|
||||
@@ -245,7 +258,7 @@ class PluginLoader(SourceFileLoader):
|
||||
raise
|
||||
finally:
|
||||
# leave plugin context
|
||||
_current_plugin_chain.reset(_plugin_token)
|
||||
_current_plugin.reset(_plugin_token)
|
||||
|
||||
# get plugin metadata
|
||||
metadata: Optional[PluginMetadata] = getattr(module, "__plugin_meta__", None)
|
||||
|
@@ -8,7 +8,7 @@ FrontMatter:
|
||||
import contextlib
|
||||
from types import ModuleType
|
||||
from dataclasses import field, dataclass
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
|
||||
from typing import TYPE_CHECKING, Any, Type, Optional # noqa: UP035
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -35,19 +35,19 @@ class PluginMetadata:
|
||||
"""插件类型,用于商店分类"""
|
||||
homepage: Optional[str] = None
|
||||
"""插件主页"""
|
||||
config: Optional[Type[BaseModel]] = None
|
||||
config: Optional[Type[BaseModel]] = None # noqa: UP006
|
||||
"""插件配置项"""
|
||||
supported_adapters: Optional[Set[str]] = None
|
||||
supported_adapters: Optional[set[str]] = None
|
||||
"""插件支持的适配器模块路径
|
||||
|
||||
格式为 `<module>[:<Adapter>]`,`~` 为 `nonebot.adapters.` 的缩写。
|
||||
|
||||
`None` 表示支持**所有适配器**。
|
||||
"""
|
||||
extra: Dict[Any, Any] = field(default_factory=dict)
|
||||
extra: dict[Any, Any] = field(default_factory=dict)
|
||||
"""插件额外信息,可由插件编写者自由扩展定义"""
|
||||
|
||||
def get_supported_adapters(self) -> Optional[Set[Type["Adapter"]]]:
|
||||
def get_supported_adapters(self) -> Optional[set[Type["Adapter"]]]: # noqa: UP006
|
||||
"""获取当前已安装的插件支持适配器类列表"""
|
||||
if self.supported_adapters is None:
|
||||
return None
|
||||
@@ -66,17 +66,24 @@ class Plugin:
|
||||
"""存储插件信息"""
|
||||
|
||||
name: str
|
||||
"""插件索引标识,NoneBot 使用 文件/文件夹 名称作为标识符"""
|
||||
"""插件名称,NoneBot 使用 文件/文件夹 名称作为插件名称"""
|
||||
module: ModuleType
|
||||
"""插件模块对象"""
|
||||
module_name: str
|
||||
"""点分割模块路径"""
|
||||
manager: "PluginManager"
|
||||
"""导入该插件的插件管理器"""
|
||||
matcher: Set[Type[Matcher]] = field(default_factory=set)
|
||||
matcher: set[type[Matcher]] = field(default_factory=set)
|
||||
"""插件加载时定义的 `Matcher`"""
|
||||
parent_plugin: Optional["Plugin"] = None
|
||||
"""父插件"""
|
||||
sub_plugins: Set["Plugin"] = field(default_factory=set)
|
||||
sub_plugins: set["Plugin"] = field(default_factory=set)
|
||||
"""子插件集合"""
|
||||
metadata: Optional[PluginMetadata] = None
|
||||
|
||||
@property
|
||||
def id_(self) -> str:
|
||||
"""插件索引标识"""
|
||||
return (
|
||||
f"{self.parent_plugin.id_}:{self.name}" if self.parent_plugin else self.name
|
||||
)
|
||||
|
@@ -9,8 +9,8 @@ import re
|
||||
import inspect
|
||||
import warnings
|
||||
from types import ModuleType
|
||||
from typing import Any, Union, Optional
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.permission import Permission
|
||||
@@ -31,19 +31,19 @@ from nonebot.rule import (
|
||||
)
|
||||
|
||||
from .model import Plugin
|
||||
from .manager import _current_plugin
|
||||
from . import get_plugin_by_module_name
|
||||
from .manager import _current_plugin_chain
|
||||
|
||||
|
||||
def store_matcher(matcher: Type[Matcher]) -> None:
|
||||
def store_matcher(matcher: type[Matcher]) -> None:
|
||||
"""存储一个事件响应器到插件。
|
||||
|
||||
参数:
|
||||
matcher: 事件响应器
|
||||
"""
|
||||
# only store the matcher defined when plugin loading
|
||||
if plugin_chain := _current_plugin_chain.get():
|
||||
plugin_chain[-1].matcher.add(matcher)
|
||||
if plugin := _current_plugin.get():
|
||||
plugin.matcher.add(matcher)
|
||||
|
||||
|
||||
def get_matcher_plugin(depth: int = 1) -> Optional[Plugin]: # pragma: no cover
|
||||
@@ -76,7 +76,7 @@ def get_matcher_module(depth: int = 1) -> Optional[ModuleType]: # pragma: no co
|
||||
return (source := get_matcher_source(depth + 1)) and source.module
|
||||
|
||||
|
||||
def get_matcher_source(depth: int = 1) -> Optional[MatcherSource]:
|
||||
def get_matcher_source(depth: int = 0) -> Optional[MatcherSource]:
|
||||
"""获取事件响应器定义所在源码信息。
|
||||
|
||||
参数:
|
||||
@@ -85,20 +85,25 @@ def get_matcher_source(depth: int = 1) -> Optional[MatcherSource]:
|
||||
current_frame = inspect.currentframe()
|
||||
if current_frame is None:
|
||||
return None
|
||||
frame = inspect.getouterframes(current_frame)[depth + 1].frame
|
||||
|
||||
frame = current_frame
|
||||
d = depth + 1
|
||||
while d > 0:
|
||||
frame = frame.f_back
|
||||
if frame is None:
|
||||
raise ValueError("Depth out of range")
|
||||
d -= 1
|
||||
|
||||
module_name = (module := inspect.getmodule(frame)) and module.__name__
|
||||
|
||||
plugin: Optional["Plugin"] = None
|
||||
# matcher defined when plugin loading
|
||||
if plugin_chain := _current_plugin_chain.get():
|
||||
plugin = plugin_chain[-1]
|
||||
plugin: Optional["Plugin"] = _current_plugin.get()
|
||||
# matcher defined when plugin running
|
||||
elif module_name:
|
||||
if plugin is None and module_name:
|
||||
plugin = get_plugin_by_module_name(module_name)
|
||||
|
||||
return MatcherSource(
|
||||
plugin_name=plugin and plugin.name,
|
||||
plugin_id=plugin and plugin.id_,
|
||||
module_name=module_name,
|
||||
lineno=frame.f_lineno,
|
||||
)
|
||||
@@ -109,14 +114,14 @@ def on(
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
permission: Optional[Union[Permission, T_PermissionChecker]] = None,
|
||||
*,
|
||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = None,
|
||||
handlers: Optional[list[Union[T_Handler, Dependent]]] = None,
|
||||
temp: bool = False,
|
||||
expire_time: Optional[Union[datetime, timedelta]] = None,
|
||||
priority: int = 1,
|
||||
block: bool = False,
|
||||
state: Optional[T_State] = None,
|
||||
_depth: int = 0,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个基础事件响应器,可自定义类型。
|
||||
|
||||
参数:
|
||||
@@ -146,7 +151,7 @@ def on(
|
||||
return matcher
|
||||
|
||||
|
||||
def on_metaevent(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
def on_metaevent(*args, _depth: int = 0, **kwargs) -> type[Matcher]:
|
||||
"""注册一个元事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -162,7 +167,7 @@ def on_metaevent(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
return on("meta_event", *args, **kwargs, _depth=_depth + 1)
|
||||
|
||||
|
||||
def on_message(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
def on_message(*args, _depth: int = 0, **kwargs) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -179,7 +184,7 @@ def on_message(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
return on("message", *args, **kwargs, _depth=_depth + 1)
|
||||
|
||||
|
||||
def on_notice(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
def on_notice(*args, _depth: int = 0, **kwargs) -> type[Matcher]:
|
||||
"""注册一个通知事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -195,7 +200,7 @@ def on_notice(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
return on("notice", *args, **kwargs, _depth=_depth + 1)
|
||||
|
||||
|
||||
def on_request(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
def on_request(*args, _depth: int = 0, **kwargs) -> type[Matcher]:
|
||||
"""注册一个请求事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -212,12 +217,12 @@ def on_request(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
|
||||
|
||||
def on_startswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
msg: Union[str, tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
ignorecase: bool = False,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息的**文本部分**以指定内容开头时响应。
|
||||
|
||||
参数:
|
||||
@@ -236,12 +241,12 @@ def on_startswith(
|
||||
|
||||
|
||||
def on_endswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
msg: Union[str, tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
ignorecase: bool = False,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息的**文本部分**以指定内容结尾时响应。
|
||||
|
||||
参数:
|
||||
@@ -260,12 +265,12 @@ def on_endswith(
|
||||
|
||||
|
||||
def on_fullmatch(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
msg: Union[str, tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
ignorecase: bool = False,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息的**文本部分**与指定内容完全一致时响应。
|
||||
|
||||
参数:
|
||||
@@ -284,11 +289,11 @@ def on_fullmatch(
|
||||
|
||||
|
||||
def on_keyword(
|
||||
keywords: Set[str],
|
||||
keywords: set[str],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应。
|
||||
|
||||
参数:
|
||||
@@ -306,13 +311,13 @@ def on_keyword(
|
||||
|
||||
|
||||
def on_command(
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
cmd: Union[str, tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
aliases: Optional[set[Union[str, tuple[str, ...]]]] = None,
|
||||
force_whitespace: Optional[Union[str, bool]] = None,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息以指定命令开头时响应。
|
||||
|
||||
命令匹配规则参考: `命令形式匹配 <rule.md#command-command>`_
|
||||
@@ -341,13 +346,13 @@ def on_command(
|
||||
|
||||
|
||||
def on_shell_command(
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
cmd: Union[str, tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
aliases: Optional[set[Union[str, tuple[str, ...]]]] = None,
|
||||
parser: Optional[ArgumentParser] = None,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个支持 `shell_like` 解析参数的命令消息事件响应器。
|
||||
|
||||
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
|
||||
@@ -383,7 +388,7 @@ def on_regex(
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息匹配正则表达式时响应。
|
||||
|
||||
命令匹配规则参考: `正则匹配 <rule.md#regex-regex-flags-0>`_
|
||||
@@ -404,12 +409,12 @@ def on_regex(
|
||||
|
||||
|
||||
def on_type(
|
||||
types: Union[Type[Event], Tuple[Type[Event], ...]],
|
||||
types: Union[type[Event], tuple[type[Event], ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
*,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个事件响应器,并且当事件为指定类型时响应。
|
||||
|
||||
参数:
|
||||
@@ -430,14 +435,14 @@ def on_type(
|
||||
class _Group:
|
||||
def __init__(self, **kwargs):
|
||||
"""创建一个事件响应器组合,参数为默认值,与 `on` 一致"""
|
||||
self.matchers: List[Type[Matcher]] = []
|
||||
self.matchers: list[type[Matcher]] = []
|
||||
"""组内事件响应器列表"""
|
||||
self.base_kwargs: Dict[str, Any] = kwargs
|
||||
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]:
|
||||
self, update: dict[str, Any], *, exclude: Optional[set[str]] = None
|
||||
) -> dict[str, Any]:
|
||||
"""获取最终传递给 `on` 的参数
|
||||
|
||||
参数:
|
||||
@@ -470,18 +475,18 @@ class CommandGroup(_Group):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, cmd: Union[str, Tuple[str, ...]], prefix_aliases: bool = False, **kwargs
|
||||
self, cmd: Union[str, tuple[str, ...]], prefix_aliases: bool = False, **kwargs
|
||||
):
|
||||
"""命令前缀"""
|
||||
super().__init__(**kwargs)
|
||||
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
|
||||
self.basecmd: tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
|
||||
self.base_kwargs.pop("aliases", None)
|
||||
self.prefix_aliases = prefix_aliases
|
||||
|
||||
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]:
|
||||
def command(self, cmd: Union[str, tuple[str, ...]], **kwargs) -> type[Matcher]:
|
||||
"""注册一个新的命令。新参数将会覆盖命令组默认值
|
||||
|
||||
参数:
|
||||
@@ -509,8 +514,8 @@ class CommandGroup(_Group):
|
||||
return matcher
|
||||
|
||||
def shell_command(
|
||||
self, cmd: Union[str, Tuple[str, ...]], **kwargs
|
||||
) -> Type[Matcher]:
|
||||
self, cmd: Union[str, tuple[str, ...]], **kwargs
|
||||
) -> type[Matcher]:
|
||||
"""注册一个新的 `shell_like` 命令。新参数将会覆盖命令组默认值
|
||||
|
||||
参数:
|
||||
@@ -544,7 +549,7 @@ class MatcherGroup(_Group):
|
||||
def __repr__(self) -> str:
|
||||
return f"MatcherGroup(matchers={len(self.matchers)})"
|
||||
|
||||
def on(self, **kwargs) -> Type[Matcher]:
|
||||
def on(self, **kwargs) -> type[Matcher]:
|
||||
"""注册一个基础事件响应器,可自定义类型。
|
||||
|
||||
参数:
|
||||
@@ -562,7 +567,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_metaevent(self, **kwargs) -> Type[Matcher]:
|
||||
def on_metaevent(self, **kwargs) -> type[Matcher]:
|
||||
"""注册一个元事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -580,7 +585,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_message(self, **kwargs) -> Type[Matcher]:
|
||||
def on_message(self, **kwargs) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -598,7 +603,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_notice(self, **kwargs) -> Type[Matcher]:
|
||||
def on_notice(self, **kwargs) -> type[Matcher]:
|
||||
"""注册一个通知事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -616,7 +621,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_request(self, **kwargs) -> Type[Matcher]:
|
||||
def on_request(self, **kwargs) -> type[Matcher]:
|
||||
"""注册一个请求事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -635,8 +640,8 @@ class MatcherGroup(_Group):
|
||||
return matcher
|
||||
|
||||
def on_startswith(
|
||||
self, msg: Union[str, Tuple[str, ...]], **kwargs
|
||||
) -> Type[Matcher]:
|
||||
self, msg: Union[str, tuple[str, ...]], **kwargs
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息的**文本部分**以指定内容开头时响应。
|
||||
|
||||
参数:
|
||||
@@ -656,7 +661,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_endswith(self, msg: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
|
||||
def on_endswith(self, msg: Union[str, tuple[str, ...]], **kwargs) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息的**文本部分**以指定内容结尾时响应。
|
||||
|
||||
参数:
|
||||
@@ -676,7 +681,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_fullmatch(self, msg: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
|
||||
def on_fullmatch(self, msg: Union[str, tuple[str, ...]], **kwargs) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息的**文本部分**与指定内容完全一致时响应。
|
||||
|
||||
参数:
|
||||
@@ -696,7 +701,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_keyword(self, keywords: Set[str], **kwargs) -> Type[Matcher]:
|
||||
def on_keyword(self, keywords: set[str], **kwargs) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应。
|
||||
|
||||
参数:
|
||||
@@ -717,11 +722,11 @@ class MatcherGroup(_Group):
|
||||
|
||||
def on_command(
|
||||
self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
cmd: Union[str, tuple[str, ...]],
|
||||
aliases: Optional[set[Union[str, tuple[str, ...]]]] = None,
|
||||
force_whitespace: Optional[Union[str, bool]] = None,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息以指定命令开头时响应。
|
||||
|
||||
命令匹配规则参考: `命令形式匹配 <rule.md#command-command>`_
|
||||
@@ -748,11 +753,11 @@ class MatcherGroup(_Group):
|
||||
|
||||
def on_shell_command(
|
||||
self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
cmd: Union[str, tuple[str, ...]],
|
||||
aliases: Optional[set[Union[str, tuple[str, ...]]]] = None,
|
||||
parser: Optional[ArgumentParser] = None,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个支持 `shell_like` 解析参数的命令消息事件响应器。
|
||||
|
||||
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
|
||||
@@ -780,7 +785,7 @@ class MatcherGroup(_Group):
|
||||
|
||||
def on_regex(
|
||||
self, pattern: str, flags: Union[int, re.RegexFlag] = 0, **kwargs
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息匹配正则表达式时响应。
|
||||
|
||||
命令匹配规则参考: `正则匹配 <rule.md#regex-regex-flags-0>`_
|
||||
@@ -803,8 +808,8 @@ class MatcherGroup(_Group):
|
||||
return matcher
|
||||
|
||||
def on_type(
|
||||
self, types: Union[Type[Event], Tuple[Type[Event]]], **kwargs
|
||||
) -> Type[Matcher]:
|
||||
self, types: Union[type[Event], tuple[type[Event]]], **kwargs
|
||||
) -> type[Matcher]:
|
||||
"""注册一个事件响应器,并且当事件为指定类型时响应。
|
||||
|
||||
参数:
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import Dict, AsyncGenerator
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
@@ -15,7 +15,7 @@ __plugin_meta__ = PluginMetadata(
|
||||
supported_adapters=None,
|
||||
)
|
||||
|
||||
_running_matcher: Dict[str, int] = {}
|
||||
_running_matcher: dict[str, int] = {}
|
||||
|
||||
|
||||
async def matcher_mutex(event: Event) -> AsyncGenerator[bool, None]:
|
||||
|
@@ -15,19 +15,16 @@ from argparse import Action
|
||||
from gettext import gettext
|
||||
from argparse import ArgumentError
|
||||
from contextvars import ContextVar
|
||||
from collections.abc import Sequence
|
||||
from itertools import chain, product
|
||||
from argparse import Namespace as Namespace
|
||||
from argparse import ArgumentParser as ArgParser
|
||||
from typing import (
|
||||
IO,
|
||||
TYPE_CHECKING,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
TypeVar,
|
||||
Optional,
|
||||
Sequence,
|
||||
TypedDict,
|
||||
NamedTuple,
|
||||
cast,
|
||||
@@ -63,7 +60,7 @@ T = TypeVar("T")
|
||||
|
||||
|
||||
class CMD_RESULT(TypedDict):
|
||||
command: Optional[Tuple[str, ...]]
|
||||
command: Optional[tuple[str, ...]]
|
||||
raw_command: Optional[str]
|
||||
command_arg: Optional[Message]
|
||||
command_start: Optional[str]
|
||||
@@ -72,7 +69,7 @@ class CMD_RESULT(TypedDict):
|
||||
|
||||
class TRIE_VALUE(NamedTuple):
|
||||
command_start: str
|
||||
command: Tuple[str, ...]
|
||||
command: tuple[str, ...]
|
||||
|
||||
|
||||
parser_message: ContextVar[str] = ContextVar("parser_message")
|
||||
@@ -149,7 +146,7 @@ class StartswithRule:
|
||||
|
||||
__slots__ = ("msg", "ignorecase")
|
||||
|
||||
def __init__(self, msg: Tuple[str, ...], ignorecase: bool = False):
|
||||
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
|
||||
self.msg = msg
|
||||
self.ignorecase = ignorecase
|
||||
|
||||
@@ -181,7 +178,7 @@ class StartswithRule:
|
||||
return False
|
||||
|
||||
|
||||
def startswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
def startswith(msg: Union[str, tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
"""匹配消息纯文本开头。
|
||||
|
||||
参数:
|
||||
@@ -204,7 +201,7 @@ class EndswithRule:
|
||||
|
||||
__slots__ = ("msg", "ignorecase")
|
||||
|
||||
def __init__(self, msg: Tuple[str, ...], ignorecase: bool = False):
|
||||
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
|
||||
self.msg = msg
|
||||
self.ignorecase = ignorecase
|
||||
|
||||
@@ -236,7 +233,7 @@ class EndswithRule:
|
||||
return False
|
||||
|
||||
|
||||
def endswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
def endswith(msg: Union[str, tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
"""匹配消息纯文本结尾。
|
||||
|
||||
参数:
|
||||
@@ -259,7 +256,7 @@ class FullmatchRule:
|
||||
|
||||
__slots__ = ("msg", "ignorecase")
|
||||
|
||||
def __init__(self, msg: Tuple[str, ...], ignorecase: bool = False):
|
||||
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
|
||||
self.msg = tuple(map(str.casefold, msg) if ignorecase else msg)
|
||||
self.ignorecase = ignorecase
|
||||
|
||||
@@ -290,7 +287,7 @@ class FullmatchRule:
|
||||
return False
|
||||
|
||||
|
||||
def fullmatch(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
def fullmatch(msg: Union[str, tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
"""完全匹配消息。
|
||||
|
||||
参数:
|
||||
@@ -361,7 +358,7 @@ class CommandRule:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cmds: List[Tuple[str, ...]],
|
||||
cmds: list[tuple[str, ...]],
|
||||
force_whitespace: Optional[Union[str, bool]] = None,
|
||||
):
|
||||
self.cmds = tuple(cmds)
|
||||
@@ -380,7 +377,7 @@ class CommandRule:
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
cmd: Optional[Tuple[str, ...]] = Command(),
|
||||
cmd: Optional[tuple[str, ...]] = Command(),
|
||||
cmd_arg: Optional[Message] = CommandArg(),
|
||||
cmd_whitespace: Optional[str] = CommandWhitespace(),
|
||||
) -> bool:
|
||||
@@ -394,7 +391,7 @@ class CommandRule:
|
||||
|
||||
|
||||
def command(
|
||||
*cmds: Union[str, Tuple[str, ...]],
|
||||
*cmds: Union[str, tuple[str, ...]],
|
||||
force_whitespace: Optional[Union[str, bool]] = None,
|
||||
) -> Rule:
|
||||
"""匹配消息命令。
|
||||
@@ -424,7 +421,7 @@ def command(
|
||||
config = get_driver().config
|
||||
command_start = config.command_start
|
||||
command_sep = config.command_sep
|
||||
commands: List[Tuple[str, ...]] = []
|
||||
commands: list[tuple[str, ...]] = []
|
||||
for command in cmds:
|
||||
if isinstance(command, str):
|
||||
command = (command,)
|
||||
@@ -460,23 +457,23 @@ class ArgumentParser(ArgParser):
|
||||
self,
|
||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||
namespace: None = None,
|
||||
) -> Tuple[Namespace, List[Union[str, MessageSegment]]]: ...
|
||||
) -> tuple[Namespace, list[Union[str, MessageSegment]]]: ...
|
||||
|
||||
@overload
|
||||
def parse_known_args(
|
||||
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
|
||||
) -> Tuple[T, List[Union[str, MessageSegment]]]: ...
|
||||
) -> tuple[T, list[Union[str, MessageSegment]]]: ...
|
||||
|
||||
@overload
|
||||
def parse_known_args(
|
||||
self, *, namespace: T
|
||||
) -> Tuple[T, List[Union[str, MessageSegment]]]: ...
|
||||
) -> tuple[T, list[Union[str, MessageSegment]]]: ...
|
||||
|
||||
def parse_known_args(
|
||||
def parse_known_args( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self,
|
||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||
namespace: Optional[T] = None,
|
||||
) -> Tuple[Union[Namespace, T], List[Union[str, MessageSegment]]]: ...
|
||||
) -> tuple[Union[Namespace, T], list[Union[str, MessageSegment]]]: ...
|
||||
|
||||
@overload
|
||||
def parse_args(
|
||||
@@ -506,7 +503,7 @@ class ArgumentParser(ArgParser):
|
||||
|
||||
def _parse_optional(
|
||||
self, arg_string: Union[str, MessageSegment]
|
||||
) -> Optional[Tuple[Optional[Action], str, Optional[str]]]:
|
||||
) -> Optional[tuple[Optional[Action], str, Optional[str]]]:
|
||||
return (
|
||||
super()._parse_optional(arg_string) if isinstance(arg_string, str) else None
|
||||
)
|
||||
@@ -533,7 +530,7 @@ class ShellCommandRule:
|
||||
|
||||
__slots__ = ("cmds", "parser")
|
||||
|
||||
def __init__(self, cmds: List[Tuple[str, ...]], parser: Optional[ArgumentParser]):
|
||||
def __init__(self, cmds: list[tuple[str, ...]], parser: Optional[ArgumentParser]):
|
||||
self.cmds = tuple(cmds)
|
||||
self.parser = parser
|
||||
|
||||
@@ -553,7 +550,7 @@ class ShellCommandRule:
|
||||
async def __call__(
|
||||
self,
|
||||
state: T_State,
|
||||
cmd: Optional[Tuple[str, ...]] = Command(),
|
||||
cmd: Optional[tuple[str, ...]] = Command(),
|
||||
msg: Optional[Message] = CommandArg(),
|
||||
) -> bool:
|
||||
if cmd not in self.cmds or msg is None:
|
||||
@@ -571,7 +568,7 @@ class ShellCommandRule:
|
||||
try:
|
||||
args = self.parser.parse_args(state[SHELL_ARGV])
|
||||
state[SHELL_ARGS] = args
|
||||
except ArgumentError as e: # pragma: py-gte-39
|
||||
except ArgumentError as e:
|
||||
state[SHELL_ARGS] = ParserExit(status=2, message=str(e))
|
||||
except ParserExit as e:
|
||||
state[SHELL_ARGS] = e
|
||||
@@ -581,7 +578,7 @@ class ShellCommandRule:
|
||||
|
||||
|
||||
def shell_command(
|
||||
*cmds: Union[str, Tuple[str, ...]], parser: Optional[ArgumentParser] = None
|
||||
*cmds: Union[str, tuple[str, ...]], parser: Optional[ArgumentParser] = None
|
||||
) -> Rule:
|
||||
"""匹配 `shell_like` 形式的消息命令。
|
||||
|
||||
@@ -629,7 +626,7 @@ def shell_command(
|
||||
config = get_driver().config
|
||||
command_start = config.command_start
|
||||
command_sep = config.command_sep
|
||||
commands: List[Tuple[str, ...]] = []
|
||||
commands: list[tuple[str, ...]] = []
|
||||
for command in cmds:
|
||||
if isinstance(command, str):
|
||||
command = (command,)
|
||||
@@ -740,7 +737,7 @@ class IsTypeRule:
|
||||
|
||||
__slots__ = ("types",)
|
||||
|
||||
def __init__(self, *types: Type[Event]):
|
||||
def __init__(self, *types: type[Event]):
|
||||
self.types = types
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -756,7 +753,7 @@ class IsTypeRule:
|
||||
return isinstance(event, self.types)
|
||||
|
||||
|
||||
def is_type(*types: Type[Event]) -> Rule:
|
||||
def is_type(*types: type[Event]) -> Rule:
|
||||
"""匹配事件类型。
|
||||
|
||||
参数:
|
||||
|
@@ -45,26 +45,38 @@ def overrides(InterfaceClass: object):
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
|
||||
def origin_is_union(origin: t.Optional[t.Type[t.Any]]) -> bool:
|
||||
def type_has_args(type_: type[t.Any]) -> bool:
|
||||
"""判断类型是否有参数"""
|
||||
return isinstance(type_, (t._GenericAlias, types.GenericAlias)) # type: ignore
|
||||
|
||||
else:
|
||||
|
||||
def type_has_args(type_: type[t.Any]) -> bool:
|
||||
return isinstance(type_, (t._GenericAlias, types.GenericAlias, types.UnionType)) # type: ignore
|
||||
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
|
||||
def origin_is_union(origin: t.Optional[type[t.Any]]) -> bool:
|
||||
"""判断是否是 Union 类型"""
|
||||
return origin is t.Union
|
||||
|
||||
else:
|
||||
|
||||
def origin_is_union(origin: t.Optional[t.Type[t.Any]]) -> bool:
|
||||
def origin_is_union(origin: t.Optional[type[t.Any]]) -> bool:
|
||||
return origin is t.Union or origin is types.UnionType
|
||||
|
||||
|
||||
def origin_is_literal(origin: t.Optional[t.Type[t.Any]]) -> bool:
|
||||
def origin_is_literal(origin: t.Optional[type[t.Any]]) -> bool:
|
||||
"""判断是否是 Literal 类型"""
|
||||
return origin is t.Literal or origin is t_ext.Literal
|
||||
|
||||
|
||||
def _literal_values(type_: t.Type[t.Any]) -> t.Tuple[t.Any, ...]:
|
||||
def _literal_values(type_: type[t.Any]) -> tuple[t.Any, ...]:
|
||||
return get_args(type_)
|
||||
|
||||
|
||||
def all_literal_values(type_: t.Type[t.Any]) -> t.List[t.Any]:
|
||||
def all_literal_values(type_: type[t.Any]) -> list[t.Any]:
|
||||
"""获取 Literal 类型包含的所有值"""
|
||||
if not origin_is_literal(get_origin(type_)):
|
||||
return [type_]
|
||||
@@ -72,7 +84,7 @@ def all_literal_values(type_: t.Type[t.Any]) -> t.List[t.Any]:
|
||||
return [x for value in _literal_values(type_) for x in all_literal_values(value)]
|
||||
|
||||
|
||||
def origin_is_annotated(origin: t.Optional[t.Type[t.Any]]) -> bool:
|
||||
def origin_is_annotated(origin: t.Optional[type[t.Any]]) -> bool:
|
||||
"""判断是否是 Annotated 类型"""
|
||||
with contextlib.suppress(TypeError):
|
||||
return origin is not None and issubclass(origin, t_ext.Annotated)
|
||||
@@ -84,28 +96,19 @@ if sys.version_info >= (3, 10):
|
||||
NONE_TYPES.add(types.NoneType)
|
||||
|
||||
|
||||
def is_none_type(type_: t.Type[t.Any]) -> bool:
|
||||
def is_none_type(type_: type[t.Any]) -> bool:
|
||||
"""判断是否是 None 类型"""
|
||||
return type_ in NONE_TYPES
|
||||
|
||||
|
||||
if sys.version_info < (3, 9): # pragma: py-lt-39
|
||||
|
||||
def evaluate_forwardref(
|
||||
ref: t.ForwardRef, globalns: t.Dict[str, t.Any], localns: t.Dict[str, t.Any]
|
||||
) -> t.Any:
|
||||
return ref._evaluate(globalns, localns)
|
||||
|
||||
else: # pragma: py-gte-39
|
||||
|
||||
def evaluate_forwardref(
|
||||
ref: t.ForwardRef, globalns: t.Dict[str, t.Any], localns: t.Dict[str, t.Any]
|
||||
ref: t.ForwardRef, globalns: dict[str, t.Any], localns: dict[str, t.Any]
|
||||
) -> t.Any:
|
||||
return ref._evaluate(globalns, localns, frozenset())
|
||||
|
||||
|
||||
# state
|
||||
T_State: TypeAlias = t.Dict[t.Any, t.Any]
|
||||
T_State: TypeAlias = dict[t.Any, t.Any]
|
||||
"""事件处理状态 State 类型"""
|
||||
|
||||
_DependentCallable: TypeAlias = t.Union[
|
||||
@@ -134,11 +137,11 @@ T_BotDisconnectionHook: TypeAlias = _DependentCallable[t.Any]
|
||||
|
||||
# api hooks
|
||||
T_CallingAPIHook: TypeAlias = t.Callable[
|
||||
["Bot", str, t.Dict[str, t.Any]], t.Awaitable[t.Any]
|
||||
["Bot", str, dict[str, t.Any]], t.Awaitable[t.Any]
|
||||
]
|
||||
"""`bot.call_api` 钩子函数"""
|
||||
T_CalledAPIHook: TypeAlias = t.Callable[
|
||||
["Bot", t.Optional[Exception], str, t.Dict[str, t.Any], t.Any], t.Awaitable[t.Any]
|
||||
["Bot", t.Optional[Exception], str, dict[str, t.Any], t.Any], t.Awaitable[t.Any]
|
||||
]
|
||||
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
|
||||
|
||||
@@ -244,5 +247,5 @@ T_PermissionUpdater: TypeAlias = _DependentCallable["Permission"]
|
||||
- MatcherParam: Matcher 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_DependencyCache: TypeAlias = t.Dict[_DependentCallable[t.Any], "Task[t.Any]"]
|
||||
T_DependencyCache: TypeAlias = dict[_DependentCallable[t.Any], "Task[t.Any]"]
|
||||
"""依赖缓存, 用于存储依赖函数的返回值"""
|
||||
|
@@ -10,36 +10,23 @@ import json
|
||||
import asyncio
|
||||
import inspect
|
||||
import importlib
|
||||
import contextlib
|
||||
import dataclasses
|
||||
from pathlib import Path
|
||||
from collections import deque
|
||||
from contextvars import copy_context
|
||||
from functools import wraps, partial
|
||||
from contextlib import asynccontextmanager
|
||||
from contextlib import AbstractContextManager, asynccontextmanager
|
||||
from typing_extensions import ParamSpec, get_args, override, get_origin
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Generic,
|
||||
Mapping,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Optional,
|
||||
Sequence,
|
||||
Coroutine,
|
||||
AsyncGenerator,
|
||||
ContextManager,
|
||||
overload,
|
||||
)
|
||||
from collections.abc import Mapping, Sequence, Coroutine, AsyncGenerator
|
||||
from typing import Any, Union, Generic, TypeVar, Callable, Optional, overload
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.typing import (
|
||||
is_none_type,
|
||||
type_has_args,
|
||||
origin_is_union,
|
||||
origin_is_literal,
|
||||
all_literal_values,
|
||||
@@ -64,8 +51,8 @@ def escape_tag(s: str) -> str:
|
||||
|
||||
|
||||
def deep_update(
|
||||
mapping: Dict[K, Any], *updating_mappings: Dict[K, Any]
|
||||
) -> Dict[K, Any]:
|
||||
mapping: dict[K, Any], *updating_mappings: dict[K, Any]
|
||||
) -> dict[K, Any]:
|
||||
"""深度更新合并字典"""
|
||||
updated_mapping = mapping.copy()
|
||||
for updating_mapping in updating_mappings:
|
||||
@@ -82,7 +69,7 @@ def deep_update(
|
||||
|
||||
|
||||
def lenient_issubclass(
|
||||
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...]]
|
||||
cls: Any, class_or_tuple: Union[type[Any], tuple[type[Any], ...]]
|
||||
) -> bool:
|
||||
"""检查 cls 是否是 class_or_tuple 中的一个类型子类并忽略类型错误。"""
|
||||
try:
|
||||
@@ -92,7 +79,7 @@ def lenient_issubclass(
|
||||
|
||||
|
||||
def generic_check_issubclass(
|
||||
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...]]
|
||||
cls: Any, class_or_tuple: Union[type[Any], tuple[type[Any], ...]]
|
||||
) -> bool:
|
||||
"""检查 cls 是否是 class_or_tuple 中的一个类型子类。
|
||||
|
||||
@@ -106,9 +93,10 @@ def generic_check_issubclass(
|
||||
则会检查其 `__bound__` 或 `__constraints__`
|
||||
是否是 class_or_tuple 中一个类型的子类或 None。
|
||||
"""
|
||||
try:
|
||||
if not type_has_args(cls):
|
||||
with contextlib.suppress(TypeError):
|
||||
return issubclass(cls, class_or_tuple)
|
||||
except TypeError:
|
||||
|
||||
origin = get_origin(cls)
|
||||
if origin_is_union(origin):
|
||||
return all(
|
||||
@@ -130,8 +118,7 @@ def generic_check_issubclass(
|
||||
elif isinstance(cls, TypeVar):
|
||||
if cls.__constraints__:
|
||||
return all(
|
||||
is_none_type(type_)
|
||||
or generic_check_issubclass(type_, class_or_tuple)
|
||||
is_none_type(type_) or generic_check_issubclass(type_, class_or_tuple)
|
||||
for type_ in cls.__constraints__
|
||||
)
|
||||
elif cls.__bound__:
|
||||
@@ -139,13 +126,13 @@ def generic_check_issubclass(
|
||||
return False
|
||||
|
||||
|
||||
def type_is_complex(type_: Type[Any]) -> bool:
|
||||
def type_is_complex(type_: type[Any]) -> bool:
|
||||
"""检查 type_ 是否是复杂类型"""
|
||||
origin = get_origin(type_)
|
||||
return _type_is_complex_inner(type_) or _type_is_complex_inner(origin)
|
||||
|
||||
|
||||
def _type_is_complex_inner(type_: Optional[Type[Any]]) -> bool:
|
||||
def _type_is_complex_inner(type_: Optional[type[Any]]) -> bool:
|
||||
if lenient_issubclass(type_, (str, bytes)):
|
||||
return False
|
||||
|
||||
@@ -200,7 +187,7 @@ def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]:
|
||||
|
||||
@asynccontextmanager
|
||||
async def run_sync_ctx_manager(
|
||||
cm: ContextManager[T],
|
||||
cm: AbstractContextManager[T],
|
||||
) -> AsyncGenerator[T, None]:
|
||||
"""一个用于包装 sync context manager 为 async context manager 的执行函数"""
|
||||
try:
|
||||
@@ -216,7 +203,7 @@ async def run_sync_ctx_manager(
|
||||
@overload
|
||||
async def run_coro_with_catch(
|
||||
coro: Coroutine[Any, Any, T],
|
||||
exc: Tuple[Type[Exception], ...],
|
||||
exc: tuple[type[Exception], ...],
|
||||
return_on_err: None = None,
|
||||
) -> Union[T, None]: ...
|
||||
|
||||
@@ -224,14 +211,14 @@ async def run_coro_with_catch(
|
||||
@overload
|
||||
async def run_coro_with_catch(
|
||||
coro: Coroutine[Any, Any, T],
|
||||
exc: Tuple[Type[Exception], ...],
|
||||
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], ...],
|
||||
exc: tuple[type[Exception], ...],
|
||||
return_on_err: Optional[R] = None,
|
||||
) -> Optional[Union[T, R]]:
|
||||
"""运行协程并当遇到指定异常时返回指定值。
|
||||
@@ -289,7 +276,7 @@ class classproperty(Generic[T]):
|
||||
def __init__(self, func: Callable[[Any], T]) -> None:
|
||||
self.func = func
|
||||
|
||||
def __get__(self, instance: Any, owner: Optional[Type[Any]] = None) -> T:
|
||||
def __get__(self, instance: Any, owner: Optional[type[Any]] = None) -> T:
|
||||
return self.func(type(instance) if owner is None else owner)
|
||||
|
||||
|
||||
|
@@ -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.8+-blue.svg" alt="python">
|
||||
<img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="python">
|
||||
</p>
|
||||
|
||||
## 使用方式
|
||||
|
@@ -12,7 +12,7 @@ include = ["nonebot_plugin_docs/dist/**/*"]
|
||||
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
python = "^3.9"
|
||||
nonebot2 = "^2.0.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
802
poetry.lock
generated
802
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "nonebot2"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
description = "An asynchronous python bot framework."
|
||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||
license = "MIT"
|
||||
@@ -25,7 +25,7 @@ include = ["nonebot/py.typed"]
|
||||
"Funding" = "https://afdian.net/@nonebot"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
python = "^3.9"
|
||||
yarl = "^1.7.2"
|
||||
pygtrie = "^2.4.1"
|
||||
loguru = ">=0.6.0,<1.0.0"
|
||||
@@ -44,7 +44,7 @@ uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
|
||||
], optional = true }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = "^0.2.0"
|
||||
ruff = "^0.4.0"
|
||||
isort = "^5.10.1"
|
||||
black = "^24.0.0"
|
||||
nonemoji = "^0.1.2"
|
||||
@@ -66,12 +66,12 @@ all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "strict"
|
||||
addopts = "--cov=nonebot --cov-append --cov-report=term-missing"
|
||||
addopts = "--cov=nonebot --cov-report=term-missing"
|
||||
filterwarnings = ["error", "ignore::DeprecationWarning"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ["py38", "py39", "py310", "py311"]
|
||||
target-version = ["py39", "py310", "py311", "py312"]
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
'''
|
||||
@@ -87,18 +87,37 @@ extra_standard_library = ["typing_extensions"]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py38"
|
||||
target-version = "py39"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "W", "F", "UP", "C", "T", "PYI", "PT", "Q"]
|
||||
ignore = ["E402", "C901", "UP037"]
|
||||
select = [
|
||||
"F", # Pyflakes
|
||||
"W", # pycodestyle warnings
|
||||
"E", # pycodestyle errors
|
||||
"UP", # pyupgrade
|
||||
"ASYNC", # flake8-async
|
||||
"C4", # flake8-comprehensions
|
||||
"T10", # flake8-debugger
|
||||
"T20", # flake8-print
|
||||
"PYI", # flake8-pyi
|
||||
"PT", # flake8-pytest-style
|
||||
"Q", # flake8-quotes
|
||||
"RUF", # Ruff-specific rules
|
||||
]
|
||||
ignore = [
|
||||
"E402", # module-import-not-at-top-of-file
|
||||
"UP037", # quoted-annotation
|
||||
"RUF001", # ambiguous-unicode-character-string
|
||||
"RUF002", # ambiguous-unicode-character-docstring
|
||||
"RUF003", # ambiguous-unicode-character-comment
|
||||
]
|
||||
|
||||
[tool.ruff.lint.flake8-pytest-style]
|
||||
fixture-parentheses = false
|
||||
mark-parentheses = false
|
||||
|
||||
[tool.pyright]
|
||||
pythonVersion = "3.8"
|
||||
pythonVersion = "3.9"
|
||||
pythonPlatform = "All"
|
||||
defineConstant = { PYDANTIC_V2 = true }
|
||||
executionEnvironments = [
|
||||
@@ -108,7 +127,7 @@ executionEnvironments = [
|
||||
{ root = "./" },
|
||||
]
|
||||
|
||||
typeCheckingMode = "basic"
|
||||
typeCheckingMode = "standard"
|
||||
reportShadowedImports = false
|
||||
disableBytesTypePromotions = true
|
||||
|
||||
|
@@ -4,4 +4,4 @@
|
||||
cd "$(dirname "$0")/../tests"
|
||||
|
||||
# Run the tests
|
||||
pytest -n auto --cov-report xml $@
|
||||
pytest -n auto --cov-append --cov-report xml $@
|
||||
|
@@ -21,8 +21,6 @@ rules =
|
||||
"sys_platform != 'win32'": py-win32
|
||||
"sys_platform != 'linux'": py-linux
|
||||
"sys_platform != 'darwin'": py-darwin
|
||||
"sys_version_info < (3, 9)": py-gte-39
|
||||
"sys_version_info >= (3, 9)": py-lt-39
|
||||
"sys_version_info < (3, 11)": py-gte-311
|
||||
"sys_version_info >= (3, 11)": py-lt-311
|
||||
"package_version('pydantic') < (2,)": pydantic-v2
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import os
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Set, Generator
|
||||
from typing import TYPE_CHECKING
|
||||
from collections.abc import Generator
|
||||
|
||||
import pytest
|
||||
from nonebug import NONEBOT_INIT_KWARGS
|
||||
@@ -38,13 +39,13 @@ def load_driver(request: pytest.FixtureRequest) -> Driver:
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def load_plugin(nonebug_init: None) -> Set["Plugin"]:
|
||||
def load_plugin(nonebug_init: None) -> set["Plugin"]:
|
||||
# preload global plugins
|
||||
return nonebot.load_plugins(str(Path(__file__).parent / "plugins"))
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def load_builtin_plugin(nonebug_init: None) -> Set["Plugin"]:
|
||||
def load_builtin_plugin(nonebug_init: None) -> set["Plugin"]:
|
||||
# preload builtin plugins
|
||||
return nonebot.load_builtin_plugins("echo", "single_session")
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import json
|
||||
import base64
|
||||
import socket
|
||||
from typing import Dict, List, Union, TypeVar
|
||||
from typing import Union, TypeVar
|
||||
|
||||
from wsproto.events import Ping
|
||||
from werkzeug import Request, Response
|
||||
@@ -31,7 +31,7 @@ def json_safe(string, content_type="application/octet-stream") -> str:
|
||||
).decode("utf-8")
|
||||
|
||||
|
||||
def flattern(d: "MultiDict[K, V]") -> Dict[K, Union[V, List[V]]]:
|
||||
def flattern(d: "MultiDict[K, V]") -> dict[K, Union[V, list[V]]]:
|
||||
return {k: v[0] if len(v) == 1 else v for k, v in d.to_dict(flat=False).items()}
|
||||
|
||||
|
||||
|
15
tests/plugins/metadata_3.py
Normal file
15
tests/plugins/metadata_3.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="测试插件3",
|
||||
description="测试继承适配器, 使用内置适配器全名",
|
||||
usage="无法使用",
|
||||
type="application",
|
||||
homepage="https://nonebot.dev",
|
||||
supported_adapters={
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"nonebot.adapters.onebot.v12",
|
||||
"~qq",
|
||||
},
|
||||
extra={"author": "NoneBot"},
|
||||
)
|
@@ -8,5 +8,5 @@ manager = PluginManager(
|
||||
_managers.append(manager)
|
||||
|
||||
# test load nested plugin with require
|
||||
manager.load_plugin("nested_subplugin")
|
||||
manager.load_plugin("nested_subplugin2")
|
||||
manager.load_plugin("plugins.nested.plugins.nested_subplugin")
|
||||
manager.load_plugin("nested:nested_subplugin2")
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing_extensions import Annotated
|
||||
from typing import Annotated
|
||||
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import Arg, ArgStr, ArgPlainText
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from typing import Annotated
|
||||
from dataclasses import dataclass
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
@@ -32,8 +32,8 @@ async def gen_async():
|
||||
|
||||
@dataclass
|
||||
class ClassDependency:
|
||||
x: int = Depends(gen_sync)
|
||||
y: int = Depends(gen_async)
|
||||
x: int = Depends(gen_sync) # noqa: RUF009
|
||||
y: int = Depends(gen_async) # noqa: RUF009
|
||||
|
||||
|
||||
class FooBot(Bot): ...
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import List, Match, Tuple
|
||||
from re import Match
|
||||
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters import Message
|
||||
@@ -32,7 +32,7 @@ async def legacy_state(state):
|
||||
async def not_legacy_state(state: int): ...
|
||||
|
||||
|
||||
async def command(cmd: Tuple[str, ...] = Command()) -> Tuple[str, ...]:
|
||||
async def command(cmd: tuple[str, ...] = Command()) -> tuple[str, ...]:
|
||||
return cmd
|
||||
|
||||
|
||||
@@ -59,8 +59,8 @@ async def shell_command_args(
|
||||
|
||||
|
||||
async def shell_command_argv(
|
||||
shell_command_argv: List[str] = ShellCommandArgv(),
|
||||
) -> List[str]:
|
||||
shell_command_argv: list[str] = ShellCommandArgv(),
|
||||
) -> list[str]:
|
||||
return shell_command_argv
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ async def regex_dict(regex_dict: dict = RegexDict()) -> dict:
|
||||
return regex_dict
|
||||
|
||||
|
||||
async def regex_group(regex_group: Tuple = RegexGroup()) -> Tuple:
|
||||
async def regex_group(regex_group: tuple = RegexGroup()) -> tuple:
|
||||
return regex_group
|
||||
|
||||
|
||||
@@ -80,8 +80,8 @@ async def regex_str(
|
||||
entire: str = RegexStr(),
|
||||
type_: str = RegexStr("type"),
|
||||
second: str = RegexStr(2),
|
||||
groups: Tuple[str, ...] = RegexStr(1, "arg"),
|
||||
) -> Tuple[str, str, str, Tuple[str, ...]]:
|
||||
groups: tuple[str, ...] = RegexStr(1, "arg"),
|
||||
) -> tuple[str, str, str, tuple[str, ...]]:
|
||||
return entire, type_, second, groups
|
||||
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
from typing import Type
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from nonebot.adapters import Event
|
||||
@@ -52,7 +51,7 @@ matcher_on = on(
|
||||
)
|
||||
|
||||
|
||||
def matcher_on_factory() -> Type[Matcher]:
|
||||
def matcher_on_factory() -> type[Matcher]:
|
||||
return on(
|
||||
"test",
|
||||
rule=rule,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
@@ -27,7 +27,7 @@ async def test_bot_call_api(app: App):
|
||||
async def test_bot_calling_api_hook_simple(app: App):
|
||||
runned: bool = False
|
||||
|
||||
async def calling_api_hook(bot: Bot, api: str, data: Dict[str, Any]):
|
||||
async def calling_api_hook(bot: Bot, api: str, data: dict[str, Any]):
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
@@ -53,7 +53,7 @@ async def test_bot_calling_api_hook_simple(app: App):
|
||||
async def test_bot_calling_api_hook_mock(app: App):
|
||||
runned: bool = False
|
||||
|
||||
async def calling_api_hook(bot: Bot, api: str, data: Dict[str, Any]):
|
||||
async def calling_api_hook(bot: Bot, api: str, data: dict[str, Any]):
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
@@ -84,7 +84,7 @@ async def test_bot_called_api_hook_simple(app: App):
|
||||
bot: Bot,
|
||||
exception: Optional[Exception],
|
||||
api: str,
|
||||
data: Dict[str, Any],
|
||||
data: dict[str, Any],
|
||||
result: Any,
|
||||
):
|
||||
nonlocal runned
|
||||
@@ -116,7 +116,7 @@ async def test_bot_called_api_hook_mock(app: App):
|
||||
bot: Bot,
|
||||
exception: Optional[Exception],
|
||||
api: str,
|
||||
data: Dict[str, Any],
|
||||
data: dict[str, Any],
|
||||
result: Any,
|
||||
):
|
||||
nonlocal runned
|
||||
|
@@ -192,6 +192,11 @@ def test_message_contains():
|
||||
assert message.has("foo") is False
|
||||
assert "foo" not in message
|
||||
|
||||
assert not bool(FakeMessageSegment.text(""))
|
||||
msg_with_empty_seg = FakeMessage([FakeMessageSegment.text("")])
|
||||
assert msg_with_empty_seg.has("text") is True
|
||||
assert "text" in msg_with_empty_seg
|
||||
|
||||
|
||||
def test_message_only():
|
||||
message = FakeMessage(
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
from dataclasses import dataclass
|
||||
|
||||
import pytest
|
||||
@@ -11,6 +11,7 @@ from nonebot.compat import (
|
||||
PydanticUndefined,
|
||||
model_dump,
|
||||
custom_validation,
|
||||
type_validate_json,
|
||||
type_validate_python,
|
||||
)
|
||||
|
||||
@@ -66,3 +67,26 @@ async def test_custom_validation():
|
||||
|
||||
assert type_validate_python(TestModel, {"test": 1}) == TestModel(test=1)
|
||||
assert called == [1, 2]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_json():
|
||||
class TestModel(BaseModel):
|
||||
test1: int
|
||||
test2: str
|
||||
test3: bool
|
||||
test4: dict
|
||||
test5: list
|
||||
test6: Optional[int]
|
||||
|
||||
assert type_validate_json(
|
||||
TestModel,
|
||||
"{"
|
||||
' "test1": 1,'
|
||||
' "test2": "2",'
|
||||
' "test3": true,'
|
||||
' "test4": {},'
|
||||
' "test5": [],'
|
||||
' "test6": null'
|
||||
"}",
|
||||
) == TestModel(test1=1, test2="2", test3=True, test4={}, test5=[], test6=None)
|
||||
|
@@ -1,9 +1,10 @@
|
||||
from typing import List, Union, Optional
|
||||
from typing import TYPE_CHECKING, Union, Optional
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
from nonebot.config import DOTENV_TYPE, BaseSettings, SettingsError
|
||||
from nonebot.compat import PYDANTIC_V2
|
||||
from nonebot.config import DOTENV_TYPE, BaseSettings, SettingsError, SettingsConfig
|
||||
|
||||
|
||||
class Simple(BaseModel):
|
||||
@@ -14,23 +15,38 @@ class Simple(BaseModel):
|
||||
|
||||
|
||||
class Example(BaseSettings):
|
||||
if TYPE_CHECKING:
|
||||
_env_file: Optional[DOTENV_TYPE] = ".env", ".env.example"
|
||||
_env_nested_delimiter: Optional[str] = "__"
|
||||
|
||||
simple: str = ""
|
||||
complex: List[int] = [1]
|
||||
complex_none: Optional[List[int]] = None
|
||||
complex_union: Union[int, List[int]] = 1
|
||||
nested: Simple = Simple()
|
||||
nested_inner: Simple = Simple()
|
||||
if PYDANTIC_V2:
|
||||
model_config = SettingsConfig(
|
||||
env_file=(".env", ".env.example"), env_nested_delimiter="__"
|
||||
)
|
||||
else:
|
||||
|
||||
class Config:
|
||||
class Config( # pyright: ignore[reportIncompatibleVariableOverride]
|
||||
SettingsConfig
|
||||
):
|
||||
env_file = ".env", ".env.example"
|
||||
env_nested_delimiter = "__"
|
||||
|
||||
simple: str = ""
|
||||
complex: list[int] = Field(default=[1])
|
||||
complex_none: Optional[list[int]] = None
|
||||
complex_union: Union[int, list[int]] = 1
|
||||
nested: Simple = Simple()
|
||||
nested_inner: Simple = Simple()
|
||||
|
||||
|
||||
class ExampleWithoutDelimiter(Example):
|
||||
class Config:
|
||||
if PYDANTIC_V2:
|
||||
model_config = SettingsConfig(env_nested_delimiter=None)
|
||||
else:
|
||||
|
||||
class Config( # pyright: ignore[reportIncompatibleVariableOverride]
|
||||
SettingsConfig
|
||||
):
|
||||
env_nested_delimiter = None
|
||||
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
import asyncio
|
||||
from typing import Any, Set, Optional
|
||||
from typing import Any, Optional
|
||||
from http.cookies import SimpleCookie
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
@@ -306,6 +307,121 @@ async def test_http_client(driver: Driver, server_url: URL):
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
|
||||
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_http_client_session(driver: Driver, server_url: URL):
|
||||
assert isinstance(driver, HTTPClientMixin)
|
||||
|
||||
session = driver.get_session(
|
||||
params={"session": "test"},
|
||||
headers={"X-Session": "test"},
|
||||
cookies={"session": "test"},
|
||||
)
|
||||
request = Request("GET", server_url)
|
||||
with pytest.raises(RuntimeError):
|
||||
await session.request(request)
|
||||
|
||||
with pytest.raises(RuntimeError): # noqa: PT012
|
||||
async with session:
|
||||
async with session:
|
||||
...
|
||||
|
||||
async with session as session:
|
||||
# simple post with query, headers, cookies and content
|
||||
request = Request(
|
||||
"POST",
|
||||
server_url,
|
||||
params={"param": "test"},
|
||||
headers={"X-Test": "test"},
|
||||
cookies={"cookie": "test"},
|
||||
content="test",
|
||||
)
|
||||
response = await session.request(request)
|
||||
assert response.status_code == 200
|
||||
assert response.content
|
||||
data = json.loads(response.content)
|
||||
assert data["method"] == "POST"
|
||||
assert data["args"] == {"session": "test", "param": "test"}
|
||||
assert data["headers"].get("X-Session") == "test"
|
||||
assert data["headers"].get("X-Test") == "test"
|
||||
assert {
|
||||
key: cookie.value
|
||||
for key, cookie in SimpleCookie(data["headers"].get("Cookie")).items()
|
||||
} == {
|
||||
"session": "test",
|
||||
"cookie": "test",
|
||||
}
|
||||
assert data["data"] == "test"
|
||||
|
||||
# post with data body
|
||||
request = Request("POST", server_url, data={"form": "test"})
|
||||
response = await session.request(request)
|
||||
assert response.status_code == 200
|
||||
assert response.content
|
||||
data = json.loads(response.content)
|
||||
assert data["method"] == "POST"
|
||||
assert data["args"] == {"session": "test"}
|
||||
assert data["headers"].get("X-Session") == "test"
|
||||
assert {
|
||||
key: cookie.value
|
||||
for key, cookie in SimpleCookie(data["headers"].get("Cookie")).items()
|
||||
} == {"session": "test"}
|
||||
assert data["form"] == {"form": "test"}
|
||||
|
||||
# post with json body
|
||||
request = Request("POST", server_url, json={"json": "test"})
|
||||
response = await session.request(request)
|
||||
assert response.status_code == 200
|
||||
assert response.content
|
||||
data = json.loads(response.content)
|
||||
assert data["method"] == "POST"
|
||||
assert data["args"] == {"session": "test"}
|
||||
assert data["headers"].get("X-Session") == "test"
|
||||
assert {
|
||||
key: cookie.value
|
||||
for key, cookie in SimpleCookie(data["headers"].get("Cookie")).items()
|
||||
} == {"session": "test"}
|
||||
assert data["json"] == {"json": "test"}
|
||||
|
||||
# post with files and form data
|
||||
request = Request(
|
||||
"POST",
|
||||
server_url,
|
||||
data={"form": "test"},
|
||||
files=[
|
||||
("test1", b"test"),
|
||||
("test2", ("test.txt", b"test")),
|
||||
("test3", ("test.txt", b"test", "text/plain")),
|
||||
],
|
||||
)
|
||||
response = await session.request(request)
|
||||
assert response.status_code == 200
|
||||
assert response.content
|
||||
data = json.loads(response.content)
|
||||
assert data["method"] == "POST"
|
||||
assert data["args"] == {"session": "test"}
|
||||
assert data["headers"].get("X-Session") == "test"
|
||||
assert {
|
||||
key: cookie.value
|
||||
for key, cookie in SimpleCookie(data["headers"].get("Cookie")).items()
|
||||
} == {"session": "test"}
|
||||
assert data["form"] == {"form": "test"}
|
||||
assert data["files"] == {
|
||||
"test1": "test",
|
||||
"test2": "test",
|
||||
"test3": "test",
|
||||
}, "file parsing error"
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
@@ -363,8 +479,8 @@ async def test_combine_driver(driver: Driver, driver_type: str):
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_connect_hook(app: App, driver: Driver):
|
||||
with pytest.MonkeyPatch.context() as m:
|
||||
conn_hooks: Set[Dependent[Any]] = set()
|
||||
disconn_hooks: Set[Dependent[Any]] = set()
|
||||
conn_hooks: set[Dependent[Any]] = set()
|
||||
disconn_hooks: set[Dependent[Any]] = set()
|
||||
m.setattr(Driver, "_bot_connection_hook", conn_hooks)
|
||||
m.setattr(Driver, "_bot_disconnection_hook", disconn_hooks)
|
||||
|
||||
@@ -409,9 +525,9 @@ async def test_bot_connect_hook(app: App, driver: Driver):
|
||||
|
||||
disconn_should_be_called = True
|
||||
|
||||
if conn_hook not in {hook.call for hook in conn_hooks}:
|
||||
if conn_hook not in {hook.call for hook in conn_hooks}: # type: ignore
|
||||
pytest.fail("on_bot_connect hook not registered")
|
||||
if disconn_hook not in {hook.call for hook in disconn_hooks}:
|
||||
if disconn_hook not in {hook.call for hook in disconn_hooks}: # type: ignore
|
||||
pytest.fail("on_bot_disconnect hook not registered")
|
||||
|
||||
async with app.test_api() as ctx:
|
||||
|
@@ -29,8 +29,10 @@ async def test_matcher_info(app: App):
|
||||
assert matcher.module is sys.modules["plugins.matcher.matcher_info"]
|
||||
assert matcher.module_name == "plugins.matcher.matcher_info"
|
||||
|
||||
assert matcher._source.plugin_id == "matcher:matcher_info"
|
||||
assert matcher._source.plugin_name == "matcher_info"
|
||||
assert matcher.plugin is get_plugin("matcher_info")
|
||||
assert matcher.plugin is get_plugin("matcher:matcher_info")
|
||||
assert matcher.plugin_id == "matcher:matcher_info"
|
||||
assert matcher.plugin_name == "matcher_info"
|
||||
|
||||
assert (
|
||||
@@ -244,7 +246,7 @@ async def test_default_permission_updater(app: App):
|
||||
matcher = test_permission_updater()
|
||||
new_perm = await matcher.update_permission(bot, event)
|
||||
assert len(new_perm.checkers) == 1
|
||||
checker = list(new_perm.checkers)[0].call
|
||||
checker = next(iter(new_perm.checkers)).call
|
||||
assert isinstance(checker, User)
|
||||
assert checker.users == ("test",)
|
||||
assert checker.perm is default_permission
|
||||
@@ -258,7 +260,7 @@ async def test_user_permission_updater(app: App):
|
||||
)
|
||||
|
||||
event = make_fake_event(_session_id="test")()
|
||||
user_permission = list(test_user_permission_updater.permission.checkers)[0].call
|
||||
user_permission = next(iter(test_user_permission_updater.permission.checkers)).call
|
||||
assert isinstance(user_permission, User)
|
||||
assert user_permission.perm is default_permission
|
||||
async with app.test_api() as ctx:
|
||||
@@ -266,7 +268,7 @@ async def test_user_permission_updater(app: App):
|
||||
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
|
||||
checker = next(iter(new_perm.checkers)).call
|
||||
assert isinstance(checker, User)
|
||||
assert checker.users == ("test",)
|
||||
assert checker.perm is default_permission
|
||||
|
@@ -212,7 +212,7 @@ async def test_event(app: App):
|
||||
ctx.pass_params(event=fake_fooevent)
|
||||
ctx.should_return(fake_fooevent)
|
||||
|
||||
with pytest.raises(TypeMisMatch): # noqa: PT012
|
||||
with pytest.raises(TypeMisMatch):
|
||||
async with app.test_dependent(sub_event, allow_types=[EventParam]) as ctx:
|
||||
ctx.pass_params(event=fake_event)
|
||||
|
||||
@@ -436,7 +436,7 @@ async def test_matcher(app: App):
|
||||
ctx.pass_params(matcher=foo_matcher)
|
||||
ctx.should_return(foo_matcher)
|
||||
|
||||
with pytest.raises(TypeMisMatch): # noqa: PT012
|
||||
with pytest.raises(TypeMisMatch):
|
||||
async with app.test_dependent(sub_matcher, allow_types=[MatcherParam]) as ctx:
|
||||
ctx.pass_params(matcher=fake_matcher)
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import Tuple, Optional
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
@@ -57,7 +57,7 @@ async def test_permission(app: App):
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("type", "expected"), [("message", True), ("notice", False)])
|
||||
async def test_message(type: str, expected: bool):
|
||||
dependent = list(MESSAGE.checkers)[0]
|
||||
dependent = next(iter(MESSAGE.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, Message)
|
||||
@@ -69,7 +69,7 @@ async def test_message(type: str, expected: bool):
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("notice", True)])
|
||||
async def test_notice(type: str, expected: bool):
|
||||
dependent = list(NOTICE.checkers)[0]
|
||||
dependent = next(iter(NOTICE.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, Notice)
|
||||
@@ -81,7 +81,7 @@ async def test_notice(type: str, expected: bool):
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("request", True)])
|
||||
async def test_request(type: str, expected: bool):
|
||||
dependent = list(REQUEST.checkers)[0]
|
||||
dependent = next(iter(REQUEST.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, Request)
|
||||
@@ -95,7 +95,7 @@ async def test_request(type: str, expected: bool):
|
||||
("type", "expected"), [("message", False), ("meta_event", True)]
|
||||
)
|
||||
async def test_metaevent(type: str, expected: bool):
|
||||
dependent = list(METAEVENT.checkers)[0]
|
||||
dependent = next(iter(METAEVENT.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, MetaEvent)
|
||||
@@ -116,7 +116,7 @@ async def test_metaevent(type: str, expected: bool):
|
||||
],
|
||||
)
|
||||
async def test_superuser(app: App, type: str, user_id: str, expected: bool):
|
||||
dependent = list(SUPERUSER.checkers)[0]
|
||||
dependent = next(iter(SUPERUSER.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, SuperUser)
|
||||
@@ -138,9 +138,9 @@ async def test_superuser(app: App, type: str, user_id: str, expected: bool):
|
||||
],
|
||||
)
|
||||
async def test_user(
|
||||
app: App, session_ids: Tuple[str, ...], session_id: Optional[str], expected: bool
|
||||
app: App, session_ids: tuple[str, ...], session_id: Optional[str], expected: bool
|
||||
):
|
||||
dependent = list(USER(*session_ids).checkers)[0]
|
||||
dependent = next(iter(USER(*session_ids).checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, User)
|
||||
|
@@ -10,18 +10,43 @@ async def test_get_plugin():
|
||||
# check simple plugin
|
||||
plugin = nonebot.get_plugin("export")
|
||||
assert plugin
|
||||
assert plugin.id_ == "export"
|
||||
assert plugin.name == "export"
|
||||
assert plugin.module_name == "plugins.export"
|
||||
|
||||
# check sub plugin
|
||||
plugin = nonebot.get_plugin("nested_subplugin")
|
||||
plugin = nonebot.get_plugin("nested:nested_subplugin")
|
||||
assert plugin
|
||||
assert plugin.id_ == "nested:nested_subplugin"
|
||||
assert plugin.name == "nested_subplugin"
|
||||
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
|
||||
|
||||
# check get plugin by module name
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_plugin_by_module_name():
|
||||
# check get plugin by exact module name
|
||||
plugin = nonebot.get_plugin_by_module_name("plugins.nested")
|
||||
assert plugin
|
||||
assert plugin.id_ == "nested"
|
||||
assert plugin.name == "nested"
|
||||
assert plugin.module_name == "plugins.nested"
|
||||
|
||||
# check get plugin by sub module name
|
||||
plugin = nonebot.get_plugin_by_module_name("plugins.nested.utils")
|
||||
assert plugin
|
||||
assert plugin.id_ == "nested"
|
||||
assert plugin.name == "nested"
|
||||
assert plugin.module_name == "plugins.nested"
|
||||
|
||||
# check get plugin by sub plugin exact module name
|
||||
plugin = nonebot.get_plugin_by_module_name(
|
||||
"plugins.nested.plugins.nested_subplugin"
|
||||
)
|
||||
assert plugin
|
||||
assert plugin.id_ == "nested:nested_subplugin"
|
||||
assert plugin.name == "nested_subplugin"
|
||||
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_available_plugin():
|
||||
@@ -31,8 +56,8 @@ async def test_get_available_plugin():
|
||||
_managers.append(PluginManager(["plugins.export", "plugin.require"]))
|
||||
|
||||
# check get available plugins
|
||||
plugin_names = nonebot.get_available_plugin_names()
|
||||
assert plugin_names == {"export", "require"}
|
||||
plugin_ids = nonebot.get_available_plugin_names()
|
||||
assert plugin_ids == {"export", "require"}
|
||||
finally:
|
||||
_managers.clear()
|
||||
_managers.extend(old_managers)
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import sys
|
||||
from typing import Set
|
||||
from pathlib import Path
|
||||
from dataclasses import asdict
|
||||
|
||||
@@ -22,7 +21,7 @@ async def test_load_plugin():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_plugins(load_plugin: Set[Plugin], load_builtin_plugin: Set[Plugin]):
|
||||
async def test_load_plugins(load_plugin: set[Plugin], load_builtin_plugin: set[Plugin]):
|
||||
loaded_plugins = {
|
||||
plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
|
||||
}
|
||||
@@ -30,12 +29,13 @@ async def test_load_plugins(load_plugin: Set[Plugin], load_builtin_plugin: Set[P
|
||||
|
||||
# check simple plugin
|
||||
assert "plugins.export" in sys.modules
|
||||
assert "plugin._hidden" not in sys.modules
|
||||
|
||||
# check sub plugin
|
||||
plugin = nonebot.get_plugin("nested_subplugin")
|
||||
plugin = nonebot.get_plugin("nested:nested_subplugin")
|
||||
assert plugin
|
||||
assert "plugins.nested.plugins.nested_subplugin" in sys.modules
|
||||
assert plugin.parent_plugin == nonebot.get_plugin("nested")
|
||||
assert plugin.parent_plugin is nonebot.get_plugin("nested")
|
||||
|
||||
# check load again
|
||||
with pytest.raises(RuntimeError):
|
||||
@@ -47,8 +47,8 @@ async def test_load_plugins(load_plugin: Set[Plugin], load_builtin_plugin: Set[P
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_nested_plugin():
|
||||
parent_plugin = nonebot.get_plugin("nested")
|
||||
sub_plugin = nonebot.get_plugin("nested_subplugin")
|
||||
sub_plugin2 = nonebot.get_plugin("nested_subplugin2")
|
||||
sub_plugin = nonebot.get_plugin("nested:nested_subplugin")
|
||||
sub_plugin2 = nonebot.get_plugin("nested:nested_subplugin2")
|
||||
assert parent_plugin
|
||||
assert sub_plugin
|
||||
assert sub_plugin2
|
||||
@@ -90,12 +90,16 @@ async def test_require_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||
|
||||
monkeypatch.setattr("nonebot.plugin.load._find_manager_by_name", _patched_find)
|
||||
|
||||
# require use module name
|
||||
nonebot.require("plugins.export")
|
||||
# require use plugin id
|
||||
nonebot.require("export")
|
||||
nonebot.require("nested:nested_subplugin")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||
m = PluginManager(["dynamic.require_not_loaded"])
|
||||
m = PluginManager(["dynamic.require_not_loaded"], ["dynamic/require_not_loaded/"])
|
||||
_managers.append(m)
|
||||
num_managers = len(_managers)
|
||||
|
||||
@@ -107,7 +111,11 @@ async def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||
|
||||
monkeypatch.setattr(PluginManager, "load_plugin", _patched_load)
|
||||
|
||||
# require standalone plugin
|
||||
nonebot.require("dynamic.require_not_loaded")
|
||||
# require searched plugin
|
||||
nonebot.require("dynamic.require_not_loaded.subplugin1")
|
||||
nonebot.require("require_not_loaded:subplugin2")
|
||||
|
||||
assert len(_managers) == num_managers
|
||||
|
||||
@@ -150,32 +158,82 @@ async def test_plugin_metadata():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_inherit_supported_adapters():
|
||||
async def test_inherit_supported_adapters_not_found():
|
||||
with pytest.raises(RuntimeError):
|
||||
inherit_supported_adapters("some_plugin_not_exist")
|
||||
|
||||
with pytest.raises(ValueError, match="has no metadata!"):
|
||||
inherit_supported_adapters("export")
|
||||
|
||||
echo = nonebot.get_plugin("echo")
|
||||
assert echo
|
||||
assert echo.metadata
|
||||
assert inherit_supported_adapters("echo") is None
|
||||
|
||||
plugin_1 = nonebot.get_plugin("metadata")
|
||||
assert plugin_1
|
||||
assert plugin_1.metadata
|
||||
assert inherit_supported_adapters("metadata") == {
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("inherit_plugins", "expected"),
|
||||
[
|
||||
(("echo",), None),
|
||||
(
|
||||
("metadata",),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"plugins.metadata:FakeAdapter",
|
||||
}
|
||||
|
||||
plugin_2 = nonebot.get_plugin("metadata_2")
|
||||
assert plugin_2
|
||||
assert plugin_2.metadata
|
||||
assert inherit_supported_adapters("metadata", "metadata_2") == {
|
||||
"nonebot.adapters.onebot.v11"
|
||||
}
|
||||
assert inherit_supported_adapters("metadata", "echo", "metadata_2") == {
|
||||
"nonebot.adapters.onebot.v11"
|
||||
}
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata_2",),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"nonebot.adapters.onebot.v12",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata_3",),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"nonebot.adapters.onebot.v12",
|
||||
"nonebot.adapters.qq",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata", "metadata_2"),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata", "metadata_3"),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata_2", "metadata_3"),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"nonebot.adapters.onebot.v12",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata", "metadata_2", "metadata_3"),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata", "echo"),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"plugins.metadata:FakeAdapter",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata", "metadata_2", "echo"),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_inherit_supported_adapters_combine(
|
||||
inherit_plugins: tuple[str], expected: set[str]
|
||||
):
|
||||
assert inherit_supported_adapters(*inherit_plugins) == expected
|
||||
|
@@ -1,11 +1,17 @@
|
||||
import pytest
|
||||
|
||||
from nonebot.plugin import PluginManager
|
||||
from nonebot.plugin import PluginManager, _managers
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_plugin_name():
|
||||
m = PluginManager(plugins=["dynamic.manager"])
|
||||
_managers.append(m)
|
||||
|
||||
# load by plugin id
|
||||
module1 = m.load_plugin("manager")
|
||||
# load by module name
|
||||
module2 = m.load_plugin("dynamic.manager")
|
||||
assert module1
|
||||
assert module2
|
||||
assert module1 is module2
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import Type, Callable, Optional
|
||||
from typing import Callable, Optional
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -104,7 +104,7 @@ from nonebot.rule import (
|
||||
)
|
||||
async def test_on(
|
||||
matcher_name: str,
|
||||
pre_rule_factory: Optional[Callable[[Type[Event]], T_RuleChecker]],
|
||||
pre_rule_factory: Optional[Callable[[type[Event]], T_RuleChecker]],
|
||||
has_permission: bool,
|
||||
):
|
||||
import plugins.plugin.matchers as module
|
||||
@@ -145,6 +145,7 @@ async def test_on(
|
||||
assert matcher.plugin is plugin
|
||||
assert matcher in plugin.matcher
|
||||
assert matcher.module is module
|
||||
assert matcher.plugin_id == "plugin"
|
||||
assert matcher.plugin_name == "plugin"
|
||||
assert matcher.module_name == "plugins.plugin.matchers"
|
||||
|
||||
@@ -163,6 +164,7 @@ async def test_runtime_on():
|
||||
assert matcher.plugin is plugin
|
||||
assert matcher not in plugin.matcher
|
||||
assert matcher.module is module
|
||||
assert matcher.plugin_id == "plugin"
|
||||
assert matcher.plugin_name == "plugin"
|
||||
assert matcher.module_name == "plugins.plugin.matchers"
|
||||
finally:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import re
|
||||
import sys
|
||||
from typing import Match, Tuple, Union, Optional
|
||||
from re import Match
|
||||
from typing import Union, Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
@@ -163,14 +163,14 @@ async def test_trie(app: App):
|
||||
],
|
||||
)
|
||||
async def test_startswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
msg: Union[str, tuple[str, ...]],
|
||||
ignorecase: bool,
|
||||
type: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
test_startswith = startswith(msg, ignorecase)
|
||||
dependent = list(test_startswith.checkers)[0]
|
||||
dependent = next(iter(test_startswith.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
msg = (msg,) if isinstance(msg, str) else msg
|
||||
@@ -203,14 +203,14 @@ async def test_startswith(
|
||||
],
|
||||
)
|
||||
async def test_endswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
msg: Union[str, tuple[str, ...]],
|
||||
ignorecase: bool,
|
||||
type: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
test_endswith = endswith(msg, ignorecase)
|
||||
dependent = list(test_endswith.checkers)[0]
|
||||
dependent = next(iter(test_endswith.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
msg = (msg,) if isinstance(msg, str) else msg
|
||||
@@ -243,14 +243,14 @@ async def test_endswith(
|
||||
],
|
||||
)
|
||||
async def test_fullmatch(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
msg: Union[str, tuple[str, ...]],
|
||||
ignorecase: bool,
|
||||
type: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
test_fullmatch = fullmatch(msg, ignorecase)
|
||||
dependent = list(test_fullmatch.checkers)[0]
|
||||
dependent = next(iter(test_fullmatch.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
msg = (msg,) if isinstance(msg, str) else msg
|
||||
@@ -279,13 +279,13 @@ async def test_fullmatch(
|
||||
],
|
||||
)
|
||||
async def test_keyword(
|
||||
kws: Tuple[str, ...],
|
||||
kws: tuple[str, ...],
|
||||
type: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
test_keyword = keyword(*kws)
|
||||
dependent = list(test_keyword.checkers)[0]
|
||||
dependent = next(iter(test_keyword.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, KeywordsRule)
|
||||
@@ -323,15 +323,15 @@ async def test_keyword(
|
||||
],
|
||||
)
|
||||
async def test_command(
|
||||
cmds: Tuple[Tuple[str, ...]],
|
||||
cmds: tuple[tuple[str, ...]],
|
||||
force_whitespace: Optional[Union[str, bool]],
|
||||
cmd: Tuple[str, ...],
|
||||
cmd: tuple[str, ...],
|
||||
whitespace: Optional[str],
|
||||
arg_text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
test_command = command(*cmds, force_whitespace=force_whitespace)
|
||||
dependent = list(test_command.checkers)[0]
|
||||
dependent = next(iter(test_command.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, CommandRule)
|
||||
@@ -352,7 +352,7 @@ async def test_shell_command():
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
test_not_cmd = shell_command(CMD)
|
||||
dependent = list(test_not_cmd.checkers)[0]
|
||||
dependent = next(iter(test_not_cmd.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
@@ -361,7 +361,7 @@ async def test_shell_command():
|
||||
assert not await dependent(event=event, state=state)
|
||||
|
||||
test_no_parser = shell_command(CMD)
|
||||
dependent = list(test_no_parser.checkers)[0]
|
||||
dependent = next(iter(test_no_parser.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
@@ -375,7 +375,7 @@ async def test_shell_command():
|
||||
parser.add_argument("-a", required=True)
|
||||
|
||||
test_simple_parser = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_simple_parser.checkers)[0]
|
||||
dependent = next(iter(test_simple_parser.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message("-a 1")
|
||||
@@ -386,7 +386,7 @@ async def test_shell_command():
|
||||
assert state[SHELL_ARGS] == Namespace(a="1")
|
||||
|
||||
test_parser_help = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_parser_help.checkers)[0]
|
||||
dependent = next(iter(test_parser_help.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message("-h")
|
||||
@@ -399,7 +399,7 @@ async def test_shell_command():
|
||||
assert state[SHELL_ARGS].message == parser.format_help()
|
||||
|
||||
test_parser_error = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_parser_error.checkers)[0]
|
||||
dependent = next(iter(test_parser_error.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
@@ -412,7 +412,7 @@ async def test_shell_command():
|
||||
assert state[SHELL_ARGS].message.startswith(parser.format_usage() + "test: error:")
|
||||
|
||||
test_parser_remain_args = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_parser_remain_args.checkers)[0]
|
||||
dependent = next(iter(test_parser_remain_args.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = MessageSegment.text("-a 1 2") + MessageSegment.image("test")
|
||||
@@ -425,7 +425,7 @@ async def test_shell_command():
|
||||
assert state[SHELL_ARGS].message.startswith(parser.format_usage() + "test: error:")
|
||||
|
||||
test_message_parser = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_message_parser.checkers)[0]
|
||||
dependent = next(iter(test_message_parser.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = MessageSegment.text("-a") + MessageSegment.image("test")
|
||||
@@ -435,12 +435,11 @@ async def test_shell_command():
|
||||
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]
|
||||
dependent = next(iter(test_not_exit.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
@@ -476,7 +475,7 @@ async def test_regex(
|
||||
matched: Optional[Match[str]],
|
||||
):
|
||||
test_regex = regex(pattern)
|
||||
dependent = list(test_regex.checkers)[0]
|
||||
dependent = next(iter(test_regex.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, RegexRule)
|
||||
@@ -499,7 +498,7 @@ async def test_regex(
|
||||
@pytest.mark.parametrize("expected", [True, False])
|
||||
async def test_to_me(expected: bool):
|
||||
test_to_me = to_me()
|
||||
dependent = list(test_to_me.checkers)[0]
|
||||
dependent = next(iter(test_to_me.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, ToMeRule)
|
||||
@@ -515,7 +514,7 @@ async def test_is_type():
|
||||
Event3 = make_fake_event()
|
||||
|
||||
test_type = is_type(Event1, Event2)
|
||||
dependent = list(test_type.checkers)[0]
|
||||
dependent = next(iter(test_type.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, IsTypeRule)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import json
|
||||
from typing import Dict, List, Union, Literal, TypeVar, ClassVar
|
||||
from typing import Dict, List, Union, Literal, TypeVar, ClassVar # noqa: UP035
|
||||
|
||||
from utils import FakeMessage, FakeMessageSegment
|
||||
from nonebot.utils import (
|
||||
@@ -26,8 +26,10 @@ def test_generic_check_issubclass():
|
||||
assert generic_check_issubclass(Union[int, float, None], (int, float))
|
||||
assert generic_check_issubclass(Literal[1, 2, 3], int)
|
||||
assert not generic_check_issubclass(Literal[1, 2, "3"], int)
|
||||
assert generic_check_issubclass(List[int], list)
|
||||
assert generic_check_issubclass(Dict[str, int], dict)
|
||||
assert generic_check_issubclass(List[int], list) # noqa: UP006
|
||||
assert generic_check_issubclass(Dict[str, int], dict) # noqa: UP006
|
||||
assert generic_check_issubclass(list[int], list)
|
||||
assert generic_check_issubclass(dict[str, int], dict)
|
||||
assert not generic_check_issubclass(ClassVar[int], int)
|
||||
assert generic_check_issubclass(TypeVar("T", int, float), (int, float))
|
||||
assert generic_check_issubclass(TypeVar("T", bound=int), (int, float))
|
||||
|
@@ -1,5 +1,6 @@
|
||||
from typing import Union, Optional
|
||||
from typing_extensions import override
|
||||
from typing import Type, Union, Mapping, Iterable, Optional
|
||||
from collections.abc import Mapping, Iterable
|
||||
|
||||
from pydantic import Extra, create_model
|
||||
|
||||
@@ -76,7 +77,7 @@ class FakeMessage(Message[FakeMessageSegment]):
|
||||
|
||||
|
||||
def make_fake_event(
|
||||
_base: Optional[Type[Event]] = None,
|
||||
_base: Optional[type[Event]] = None,
|
||||
_type: str = "message",
|
||||
_name: str = "test",
|
||||
_description: str = "test",
|
||||
@@ -85,7 +86,7 @@ def make_fake_event(
|
||||
_message: Optional[Message] = None,
|
||||
_to_me: bool = True,
|
||||
**fields,
|
||||
) -> Type[Event]:
|
||||
) -> type[Event]:
|
||||
Base = _base or Event
|
||||
|
||||
class FakeEvent(Base, extra=Extra.forbid):
|
||||
|
@@ -8,7 +8,7 @@ slug: /
|
||||
|
||||
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架(下称 NoneBot),它基于 Python 的类型注解和异步优先特性(兼容同步),能够为你的需求实现提供便捷灵活的支持。同时,NoneBot 拥有大量的开发者为其开发插件,用户无需编写任何代码,仅需完成环境配置及插件安装,就可以正常使用 NoneBot。
|
||||
|
||||
需要注意的是,NoneBot 仅支持 **Python 3.8 以上版本**
|
||||
需要注意的是,NoneBot 仅支持 **Python 3.9 以上版本**
|
||||
|
||||
## 特色
|
||||
|
||||
|
@@ -89,7 +89,7 @@ async def _(bot): ... # 兼容性处理
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="3.9" label="Python 3.9">
|
||||
|
||||
```python
|
||||
from typing import Union
|
||||
@@ -127,7 +127,7 @@ async def _(event): ... # 兼容性处理
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="3.9" label="Python 3.9">
|
||||
|
||||
```python
|
||||
from typing import Union
|
||||
@@ -191,7 +191,7 @@ async def _(e: ActionFailed | NetworkError): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="3.9" label="Python 3.9">
|
||||
|
||||
```python {6,9}
|
||||
from typing import Union
|
||||
@@ -216,8 +216,8 @@ async def _(e: Union[ActionFailed, NetworkError]): ...
|
||||
|
||||
子依赖使用 `Depends` 标记进行定义,其参数即依赖的函数或可调用对象,同样会被解析为 `Dependent` 对象,将会在依赖注入期间执行。我们来看一个例子:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {5,15}
|
||||
from typing import Annotated
|
||||
@@ -239,7 +239,7 @@ async def _(event: Annotated[Event, Depends(check)]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3,13}
|
||||
from nonebot import on_command
|
||||
@@ -287,8 +287,8 @@ async def _():
|
||||
|
||||
NoneBot 在执行子依赖时,会将其返回值缓存起来。当我们在使用子依赖时,`Depends` 具有一个参数 `use_cache`,默认为 `True`。此时在事件处理流程中,多次使用同一个子依赖时,将会使用缓存中的结果而不会重复执行。这在很多情景中非常有用,例如:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {7}
|
||||
import random
|
||||
@@ -302,7 +302,7 @@ async def _(x: Annotated[int, Depends(random_result)]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {6}
|
||||
import random
|
||||
@@ -319,8 +319,8 @@ async def _(x: int = Depends(random_result)):
|
||||
|
||||
此时,在同一事件处理流程中,这个随机函数的返回值将会保持一致。如果我们希望每次都重新执行子依赖,可以将 `use_cache` 设置为 `False`。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {7}
|
||||
import random
|
||||
@@ -334,7 +334,7 @@ async def _(x: Annotated[int, Depends(random_result, use_cache=False)]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {6}
|
||||
import random
|
||||
@@ -357,8 +357,8 @@ async def _(x: int = Depends(random_result, use_cache=False)):
|
||||
|
||||
在依赖注入系统中,我们可以对子依赖的返回值进行自动类型转换与校验。这个功能由 Pydantic 支持,因此我们通过参数类型注解自动使用 Pydantic 支持的类型转换。例如:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {6,9}
|
||||
from typing import Annotated
|
||||
@@ -374,7 +374,7 @@ async def _(user_id: Annotated[int, Depends(get_user_id, validate=True)]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4,7}
|
||||
from nonebot.params import Depends
|
||||
@@ -392,8 +392,8 @@ async def _(user_id: int = Depends(get_user_id, validate=True)):
|
||||
|
||||
在进行类型自动转换的同时,Pydantic 还支持对数据进行更多的限制,如:大于、小于、长度等。使用方法如下:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {7,10}
|
||||
from typing import Annotated
|
||||
@@ -410,7 +410,7 @@ async def _(user_id: Annotated[int, Depends(get_user_id, validate=Field(gt=100))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {5,8}
|
||||
from pydantic import Field
|
||||
@@ -431,8 +431,8 @@ async def _(user_id: int = Depends(get_user_id, validate=Field(gt=100))):
|
||||
|
||||
在前面的事例中,我们使用了函数作为子依赖。实际上,我们还可以使用类作为依赖。当我们在实例化一个类的时候,其实我们就在调用它,类本身也是一个可调用对象。例如:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {16}
|
||||
from typing import Annotated
|
||||
@@ -455,7 +455,7 @@ async def _(data: Annotated[ClassDependency, Depends(ClassDependency)]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {15}
|
||||
from dataclasses import dataclass
|
||||
@@ -481,8 +481,8 @@ async def _(data: ClassDependency = Depends(ClassDependency)):
|
||||
|
||||
可以看到,我们使用 `dataclass` 定义了一个类。由于这个类的 `__init__` 方法可以被依赖注入系统解析,因此,我们可以将其作为子依赖进行声明。特别地,对于类依赖,`Depends` 的参数可以为空,NoneBot 将会使用参数的类型注解进行解析与推断:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
@@ -492,7 +492,7 @@ async def _(data: Annotated[ClassDependency, Depends()]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python
|
||||
async def _(data: ClassDependency = Depends()):
|
||||
@@ -510,11 +510,12 @@ NoneBot 的依赖注入支持依赖项在事件处理流程结束后进行一些
|
||||
|
||||
我们可以看下述代码段, 使用 `httpx.AsyncClient` 异步网络 IO,并在事件处理流程中共用一个 client:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {15}
|
||||
from typing import Annotated, AsyncGenerator
|
||||
from typing import Annotated
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
import httpx
|
||||
from nonebot.params import Depends
|
||||
@@ -533,10 +534,10 @@ async def _(x: Annotated[httpx.AsyncClient, Depends(get_client)]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {15}
|
||||
from typing import AsyncGenerator
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
import httpx
|
||||
from nonebot.params import Depends
|
||||
@@ -566,11 +567,10 @@ async def _(x: httpx.AsyncClient = Depends(get_client)):
|
||||
在 Python 里,为类定义 `__call__` 方法就可以使得这个类的实例成为一个可调用对象。因此,我们也可以将定义了 `__call__` 方法的类的实例作为依赖。事实上,NoneBot 的[内置响应规则](./matcher.md#内置响应规则)就广泛使用了这种方式,以 `is_type` 规则为例:
|
||||
|
||||
```python
|
||||
from typing import Type
|
||||
from nonebot.adapters import Event
|
||||
|
||||
class IsTypeRule:
|
||||
def __init__(self, *types: Type[Event]):
|
||||
def __init__(self, *types: type[Event]):
|
||||
self.types = types
|
||||
|
||||
async def __call__(self, event: Event) -> bool:
|
||||
@@ -587,8 +587,8 @@ class IsTypeRule:
|
||||
|
||||
获取当前事件的类型。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -598,7 +598,7 @@ async def _(foo: Annotated[str, EventType()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import EventType
|
||||
@@ -613,8 +613,8 @@ async def _(foo: str = EventType()): ...
|
||||
|
||||
获取当前事件的消息。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {5}
|
||||
from typing import Annotated
|
||||
@@ -625,7 +625,7 @@ async def _(foo: Annotated[Message, EventMessage()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4}
|
||||
from nonebot.adapters import Message
|
||||
@@ -641,8 +641,8 @@ async def _(foo: Message = EventMessage()): ...
|
||||
|
||||
获取当前事件的消息纯文本部分。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -652,7 +652,7 @@ async def _(foo: Annotated[str, EventPlainText()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import EventPlainText
|
||||
@@ -667,8 +667,8 @@ async def _(foo: str = EventPlainText()): ...
|
||||
|
||||
获取当前事件是否与机器人相关。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -678,7 +678,7 @@ async def _(foo: Annotated[bool, EventToMe()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import EventToMe
|
||||
@@ -693,8 +693,8 @@ async def _(foo: bool = EventToMe()): ...
|
||||
|
||||
获取当前命令型消息的元组形式命令名。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -704,13 +704,12 @@ async def _(foo: Annotated[tuple[str, ...], Command()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4}
|
||||
from typing import Tuple
|
||||
from nonebot.params import Command
|
||||
|
||||
async def _(foo: Tuple[str, ...] = Command()): ...
|
||||
async def _(foo: tuple[str, ...] = Command()): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -724,8 +723,8 @@ async def _(foo: Tuple[str, ...] = Command()): ...
|
||||
|
||||
获取当前命令型消息的文本形式命令名。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -735,7 +734,7 @@ async def _(foo: Annotated[str, RawCommand()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import RawCommand
|
||||
@@ -754,8 +753,8 @@ async def _(foo: str = RawCommand()): ...
|
||||
|
||||
获取命令型消息命令后跟随的参数。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {5}
|
||||
from typing import Annotated
|
||||
@@ -766,7 +765,7 @@ async def _(foo: Annotated[Message, CommandArg()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4}
|
||||
from nonebot.adapters import Message
|
||||
@@ -786,8 +785,8 @@ async def _(foo: Message = CommandArg()): ...
|
||||
|
||||
获取命令型消息命令前缀。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -797,7 +796,7 @@ async def _(foo: Annotated[str, CommandStart()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import CommandStart
|
||||
@@ -816,8 +815,8 @@ async def _(foo: str = CommandStart()): ...
|
||||
|
||||
获取命令型消息命令与参数间空白符。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -827,7 +826,7 @@ async def _(foo: Annotated[str, CommandWhitespace()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import CommandWhitespace
|
||||
@@ -854,10 +853,16 @@ from typing import Annotated
|
||||
from nonebot.params import ShellCommandArgs
|
||||
|
||||
async def _(foo: Annotated[list[str | MessageSegment], ShellCommandArgv()]): ...
|
||||
```
|
||||
|
||||
```python {4}
|
||||
from nonebot.params import ShellCommandArgs
|
||||
|
||||
async def _(foo: list[str | MessageSegment] = ShellCommandArgv()): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.9" label="Python 3.9+">
|
||||
<TabItem value="3.9" label="Python 3.9">
|
||||
|
||||
```python {4}
|
||||
from typing import Union, Annotated
|
||||
@@ -866,14 +871,11 @@ from nonebot.params import ShellCommandArgs
|
||||
async def _(foo: Annotated[list[Union[str, MessageSegment]], ShellCommandArgv()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
|
||||
```python {4}
|
||||
from typing import List, Union
|
||||
from typing import Union
|
||||
from nonebot.params import ShellCommandArgs
|
||||
|
||||
async def _(foo: List[Union[str, MessageSegment]] = ShellCommandArgv()): ...
|
||||
async def _(foo: list[Union[str, MessageSegment]] = ShellCommandArgv()): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -889,8 +891,8 @@ async def _(foo: List[Union[str, MessageSegment]] = ShellCommandArgv()): ...
|
||||
由于 `ArgumentParser` 在解析到 `--help` 参数时也会抛出异常,这种情况下错误码为 `0` 且错误信息即为帮助信息。
|
||||
:::
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {14,22}
|
||||
from typing import Annotated
|
||||
@@ -919,7 +921,7 @@ async def _(foo: Annotated[Namespace, ShellCommandArgs()]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {12,20}
|
||||
from nonebot import on_shell_command
|
||||
@@ -948,12 +950,40 @@ async def _(foo: Namespace = ShellCommandArgs()):
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### RegexMatched
|
||||
|
||||
获取正则匹配结果的对象。
|
||||
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {5}
|
||||
from re import Match
|
||||
from typing import Annotated
|
||||
from nonebot.params import RegexMatched
|
||||
|
||||
async def _(foo: Annotated[Match[str], RegexMatched()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4}
|
||||
from re import Match
|
||||
from nonebot.params import RegexMatched
|
||||
|
||||
async def _(foo: Match[str] = RegexMatched()): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### RegexStr
|
||||
|
||||
获取正则匹配结果的文本。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -963,7 +993,7 @@ async def _(foo: Annotated[str, RegexStr()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import RegexStr
|
||||
@@ -978,8 +1008,8 @@ async def _(foo: str = RegexStr()): ...
|
||||
|
||||
获取正则匹配结果的 group 元组。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Any, Annotated
|
||||
@@ -989,13 +1019,13 @@ async def _(foo: Annotated[tuple[Any, ...], RegexGroup()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4}
|
||||
from typing import Tuple, Any
|
||||
from typing import Any
|
||||
from nonebot.params import RegexGroup
|
||||
|
||||
async def _(foo: Tuple[Any, ...] = RegexGroup()): ...
|
||||
async def _(foo: tuple[Any, ...] = RegexGroup()): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -1005,8 +1035,8 @@ async def _(foo: Tuple[Any, ...] = RegexGroup()): ...
|
||||
|
||||
获取正则匹配结果的 group 字典。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Any, Annotated
|
||||
@@ -1016,13 +1046,13 @@ async def _(foo: Annotated[dict[str, Any], RegexDict()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4}
|
||||
from typing import Any, Dict
|
||||
from typing import Any
|
||||
from nonebot.params import RegexDict
|
||||
|
||||
async def _(foo: Dict[str, Any] = RegexDict()): ...
|
||||
async def _(foo: dict[str, Any] = RegexDict()): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -1032,8 +1062,8 @@ async def _(foo: Dict[str, Any] = RegexDict()): ...
|
||||
|
||||
获取触发响应器的消息前缀字符串。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -1043,7 +1073,7 @@ async def _(foo: Annotated[str, Startswith()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import Startswith
|
||||
@@ -1058,8 +1088,8 @@ async def _(foo: str = Startswith()): ...
|
||||
|
||||
获取触发响应器的消息后缀字符串。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -1069,7 +1099,7 @@ async def _(foo: Annotated[str, Endswith()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import Endswith
|
||||
@@ -1084,8 +1114,8 @@ async def _(foo: str = Endswith()): ...
|
||||
|
||||
获取触发响应器的消息字符串。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -1095,7 +1125,7 @@ async def _(foo: Annotated[str, Fullmatch()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import Fullmatch
|
||||
@@ -1110,8 +1140,8 @@ async def _(foo: str = Fullmatch()): ...
|
||||
|
||||
获取触发响应器的关键字字符串。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -1121,7 +1151,7 @@ async def _(foo: Annotated[str, Keyword()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import Keyword
|
||||
@@ -1136,8 +1166,8 @@ async def _(foo: str = Keyword()): ...
|
||||
|
||||
获取某次 `receive` 接收的事件。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {7}
|
||||
from typing import Annotated
|
||||
@@ -1150,7 +1180,7 @@ async def _(foo: Annotated[Event, Received("id")]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {5}
|
||||
from nonebot.adapters import Event
|
||||
@@ -1167,8 +1197,8 @@ async def _(foo: Event = Received("id")): ...
|
||||
|
||||
获取最近一次 `receive` 接收的事件。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {7}
|
||||
from typing import Annotated
|
||||
@@ -1181,7 +1211,7 @@ async def _(foo: Annotated[Event, LastReceived()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {5}
|
||||
from nonebot.adapters import Event
|
||||
@@ -1198,8 +1228,8 @@ async def _(foo: Event = LastReceived()): ...
|
||||
|
||||
获取某次 `got` 接收的参数。如果 `Arg` 参数留空,则使用函数的参数名作为要获取的参数。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {7,8}
|
||||
from typing import Annotated
|
||||
@@ -1213,7 +1243,7 @@ async def _(foo: Annotated[Message, Arg("key")]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {5,6}
|
||||
from nonebot.params import Arg
|
||||
@@ -1231,8 +1261,8 @@ async def _(foo: Message = Arg("key")): ...
|
||||
|
||||
获取某次 `got` 接收的参数,并转换为字符串。如果 `Arg` 参数留空,则使用函数的参数名作为要获取的参数。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {6,7}
|
||||
from typing import Annotated
|
||||
@@ -1245,7 +1275,7 @@ async def _(foo: Annotated[str, ArgStr("key")]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4,5}
|
||||
from nonebot.params import ArgStr
|
||||
@@ -1262,8 +1292,8 @@ async def _(foo: str = ArgStr("key")): ...
|
||||
|
||||
获取某次 `got` 接收的参数的纯文本部分。如果 `Arg` 参数留空,则使用函数的参数名作为要获取的参数。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {6,7}
|
||||
from typing import Annotated
|
||||
@@ -1276,7 +1306,7 @@ async def _(foo: Annotated[str, ArgPlainText("key")]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4,5}
|
||||
from nonebot.params import ArgPlainText
|
||||
|
@@ -579,13 +579,13 @@ nonebot.init(command_start={"/", ""}, command_sep={".", " "})
|
||||
- **类型**: `timedelta`
|
||||
- **默认值**: `timedelta(minutes=2)`
|
||||
|
||||
用户会话超时时间,配置格式参考 [Datetime Types](https://docs.pydantic.dev/usage/types/#datetime-types),可以为单位为秒的 `int | float` 等。
|
||||
用户会话超时时间,配置格式参考 [Datetime Types](https://docs.pydantic.dev/latest/api/standard_library_types/#datetimetimedelta)。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
||||
```dotenv
|
||||
SESSION_EXPIRE_TIMEOUT=120
|
||||
SESSION_EXPIRE_TIMEOUT=00:02:00
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -593,9 +593,9 @@ SESSION_EXPIRE_TIMEOUT=120
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set SESSION_EXPIRE_TIMEOUT '120'
|
||||
set SESSION_EXPIRE_TIMEOUT '00:02:00'
|
||||
# linux/macOS
|
||||
export SESSION_EXPIRE_TIMEOUT='120'
|
||||
export SESSION_EXPIRE_TIMEOUT='00:02:00'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
@@ -21,7 +21,7 @@ options:
|
||||
`RuleChecker` 是一个返回值为 `bool` 类型的依赖函数,即 `RuleChecker` 支持依赖注入。我们可以根据上一节中添加的[配置项](./config.mdx#插件配置),在 `weather` 插件目录中编写一个响应规则:
|
||||
|
||||
```python {3,4} title=weather/__init__.py
|
||||
plugin_config = Config.parse_obj(get_driver().config)
|
||||
plugin_config = get_plugin_config(Config)
|
||||
|
||||
async def is_enable() -> bool:
|
||||
return plugin_config.weather_plugin_enabled
|
||||
@@ -57,7 +57,7 @@ weather = on_command("天气", rule=rule)
|
||||
```python {10} title=weather/__init__.py
|
||||
from nonebot.rule import to_me
|
||||
|
||||
plugin_config = Config.parse_obj(get_driver().config)
|
||||
plugin_config = get_plugin_config(Config)
|
||||
|
||||
async def is_enable() -> bool:
|
||||
return plugin_config.weather_plugin_enabled
|
||||
|
@@ -102,17 +102,16 @@ async def got_location(location: str = ArgPlainText()):
|
||||
|
||||
</details>
|
||||
|
||||
```python {5-10,14-16,18-19}
|
||||
```python {5-9,13-15,17-18}
|
||||
from nonebot.rule import to_me
|
||||
from arclet.alconna import Alconna, Args
|
||||
from nonebot_plugin_alconna import Match, on_alconna
|
||||
|
||||
weather = on_alconna(
|
||||
Alconna("天气", Args["location?", str]),
|
||||
aliases={"weather", "天气预报"},
|
||||
rule=to_me(),
|
||||
)
|
||||
weather.shortcut("weather", {"command": "天气"})
|
||||
weather.shortcut("天气预报", {"command": "天气"})
|
||||
|
||||
|
||||
@weather.handle()
|
||||
@@ -133,7 +132,7 @@ async def got_location(location: str):
|
||||
或阅读 [Alconna 基本介绍](./command.md) 一节。
|
||||
|
||||
关于更多 `on_alconna` 的使用方法,可参考 [插件文档](https://github.com/nonebot/plugin-alconna/blob/master/docs.md),
|
||||
或阅读 [响应规则的使用](./matcher.md) 一节。
|
||||
或阅读 [响应规则的使用](./matcher.mdx) 一节。
|
||||
|
||||
## 交流与反馈
|
||||
|
||||
|
@@ -5,7 +5,7 @@ description: Alconna 基本介绍
|
||||
|
||||
# Alconna 本体
|
||||
|
||||
[`Alconna`](https://github.com/ArcletProject/Alconna) 隶属于 `ArcletProject`,是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
|
||||
[`Alconna`](https://github.com/ArcletProject/Alconna) 隶属于 `ArcletProject`,是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
|
||||
|
||||
我们通过一个例子来讲解 **Alconna** 的核心 —— `Args`, `Subcommand`, `Option`:
|
||||
|
||||
@@ -34,9 +34,7 @@ print(res.all_matched_args)
|
||||
|
||||
这段代码通过`Alconna`创捷了一个接受主命令名为`pip`, 子命令为`install`且子命令接受一个 **Args** 参数`package`和二个 **Option** 参数`-r`和`-i`的命令参数解析器, 通过`parse`方法返回解析结果 **Arparma** 的实例。
|
||||
|
||||
## 组成
|
||||
|
||||
### 命令头
|
||||
## 命令头
|
||||
|
||||
命令头是指命令的前缀 (Prefix) 与命令名 (Command) 的组合,例如 !help 中的 ! 与 help。
|
||||
|
||||
@@ -55,15 +53,15 @@ print(res.all_matched_args)
|
||||
| [123, "foo"] | "bar" | `[123, "bar"]` 或 `"foobar"` 或 `["foo", "bar"]` | 混合头 |
|
||||
| [(int, "foo"), (456, "bar")] | "baz" | `[123, "foobaz"]` 或 `[456, "foobaz"]` 或 `[456, "barbaz"]` | 对头 |
|
||||
|
||||
无前缀的类型头:此时会将传入的值尝试转为 BasePattern,例如 `int` 会转为 `nepattern.INTEGER`。此时命令头会匹配对应的类型, 例如 `int` 会匹配 `123` 或 `"456"`,但不会匹配 `"foo"`。同时,Alconna 会将命令头匹配到的值转为对应的类型,例如 `int` 会将 `"123"` 转为 `123`。
|
||||
对于无前缀的类型头,此时会将传入的值尝试转为 BasePattern,例如 `int` 会转为 `nepattern.INTEGER`。如此该命令头会匹配对应的类型, 例如 `int` 会匹配 `123` 或 `"456"`,但不会匹配 `"foo"`。解析后,Alconna 会将命令头匹配到的值转为对应的类型,例如 `int` 会将 `"123"` 转为 `123`。
|
||||
|
||||
:::tip
|
||||
|
||||
**正则只在命令名上生效,命令前缀中的正则会被转义**
|
||||
**正则内容只在命令名上生效,前缀中的正则会被转义**
|
||||
|
||||
:::
|
||||
|
||||
除了通过传入 `re:xxx` 来使用正则表达式外,Alconna 还提供了一种更加简洁的方式来使用正则表达式,那就是 Bracket Header:
|
||||
除了通过传入 `re:xxx` 来使用正则表达式外,Alconna 还提供了一种更加简洁的方式来使用正则表达式,称为 Bracket Header:
|
||||
|
||||
```python
|
||||
from alconna import Alconna
|
||||
@@ -82,34 +80,34 @@ Bracket Header 类似 python 里的 f-string 写法,通过 "{}" 声明匹配
|
||||
- "{:\d+}" ⇔ "(\d+)"
|
||||
- "{foo:int}" ⇔ "(?P<foo>\d+)",其中 "int" 部分若能转为 `BasePattern` 则读取里面的表达式
|
||||
|
||||
### 参数声明(Args)
|
||||
## 参数声明(Args)
|
||||
|
||||
`Args` 是用于声明命令参数的组件, 可以通过以下几种方式构造 **Args** :
|
||||
`Args` 是用于声明命令参数的组件, 可以通过以下几种方式构造 **Args** :
|
||||
|
||||
- `Args[key, var, default][key1, var1, default1][...]`
|
||||
- `Args[(key, var, default)]`
|
||||
- `Args.key[var, default]`
|
||||
|
||||
其中,key **一定**是字符串,而 var 一般为参数的类型,default 为具体的值或者 **arclet.alconna.args.Field**
|
||||
其中,key **一定**是字符串,而 var 一般为参数的类型,default 为具体的值或者 **arclet.alconna.args.Field**
|
||||
|
||||
其与函数签名类似,但是允许含有默认值的参数在前;同时支持 keyword-only 参数不依照构造顺序传入 (但是仍需要在非 keyword-only 参数之后)。
|
||||
|
||||
#### key
|
||||
### key
|
||||
|
||||
`key` 的作用是用以标记解析出来的参数并存放于 **Arparma** 中,以方便用户调用。
|
||||
`key` 的作用是用以标记解析出来的参数并存放于 **Arparma** 中,以方便用户调用。
|
||||
|
||||
其有三种为 Args 注解的标识符: `?`、`/`、 `!`, 标识符与 key 之间建议以 `;` 分隔:
|
||||
其有三种为 Args 注解的标识符: `?`、`/`、 `!`, 标识符与 key 之间建议以 `;` 分隔:
|
||||
|
||||
- `!` 标识符表示该处传入的参数应**不是**规定的类型,或**不在**指定的值中。
|
||||
- `?` 标识符表示该参数为**可选**参数,会在无参数匹配时跳过。
|
||||
- `/` 标识符表示该参数的类型注解需要隐藏。
|
||||
- `!` 标识符表示该处传入的参数应**不是**规定的类型,或**不在**指定的值中。
|
||||
- `?` 标识符表示该参数为**可选**参数,会在无参数匹配时跳过。
|
||||
- `/` 标识符表示该参数的类型注解需要隐藏。
|
||||
|
||||
另外,对于参数的注释也可以标记在 `key` 中,其与 key 或者标识符 以 `#` 分割:
|
||||
`foo#这是注释;?` 或 `foo?#这是注释`
|
||||
另外,对于参数的注释也可以标记在 `key` 中,其与 key 或者标识符 以 `#` 分割:
|
||||
`foo#这是注释;?` 或 `foo?#这是注释`
|
||||
|
||||
:::tip
|
||||
|
||||
`Args` 中的 `key` 在实际命令中并不需要传入(keyword 参数除外):
|
||||
`Args` 中的 `key` 在实际命令中并不需要传入(keyword 参数除外):
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Args
|
||||
@@ -120,7 +118,7 @@ alc.parse("test --foo abc") # 错误
|
||||
alc.parse("test abc") # 正确
|
||||
```
|
||||
|
||||
若需要 `test --foo abc`,你应该使用 `Option`:
|
||||
若需要 `test --foo abc`,你应该使用 `Option`:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Args, Option
|
||||
@@ -131,11 +129,11 @@ alc = Alconna("test", Option("--foo", Args["foo", str]))
|
||||
|
||||
:::
|
||||
|
||||
#### var
|
||||
### var
|
||||
|
||||
var 负责命令参数的**类型检查**与**类型转化**
|
||||
|
||||
`Args` 的`var`表面上看需要传入一个 `type`,但实际上它需要的是一个 `nepattern.BasePattern` 的实例:
|
||||
`Args` 的`var`表面上看需要传入一个 `type`,但实际上它需要的是一个 `nepattern.BasePattern` 的实例:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Args
|
||||
@@ -146,89 +144,109 @@ from nepattern import BasePattern
|
||||
args = Args["foo", BasePattern("@\d+")]
|
||||
```
|
||||
|
||||
示例中可以传入 `str` 是因为 `str` 已经注册在了 `nepattern.global_patterns` 中,因此会替换为 `nepattern.global_patterns[str]`
|
||||
`pip` 示例中可以传入 `str` 是因为 `str` 已经注册在了 `nepattern.global_patterns` 中,因此会替换为 `nepattern.global_patterns[str]`
|
||||
|
||||
`nepattern.global_patterns`默认支持的类型有:
|
||||
|
||||
- `str`: 匹配任意字符串
|
||||
- `int`: 匹配整数
|
||||
- `float`: 匹配浮点数
|
||||
- `bool`: 匹配 `True` 与 `False` 以及他们小写形式
|
||||
- `hex`: 匹配 `0x` 开头的十六进制字符串
|
||||
- `bool`: 匹配 `True` 与 `False` 以及他们小写形式
|
||||
- `hex`: 匹配 `0x` 开头的十六进制字符串
|
||||
- `url`: 匹配网址
|
||||
- `email`: 匹配 `xxxx@xxx` 的字符串
|
||||
- `ipv4`: 匹配 `xxx.xxx.xxx.xxx` 的字符串
|
||||
- `list`: 匹配类似 `["foo","bar","baz"]` 的字符串
|
||||
- `dict`: 匹配类似 `{"foo":"bar","baz":"qux"}` 的字符串
|
||||
- `datetime`: 传入一个 `datetime` 支持的格式字符串,或时间戳
|
||||
- `email`: 匹配 `xxxx@xxx` 的字符串
|
||||
- `ipv4`: 匹配 `xxx.xxx.xxx.xxx` 的字符串
|
||||
- `list`: 匹配类似 `["foo","bar","baz"]` 的字符串
|
||||
- `dict`: 匹配类似 `{"foo":"bar","baz":"qux"}` 的字符串
|
||||
- `datetime`: 传入一个 `datetime` 支持的格式字符串,或时间戳
|
||||
- `Any`: 匹配任意类型
|
||||
- `AnyString`: 匹配任意类型,转为 `str`
|
||||
- `Number`: 匹配 `int` 与 `float`,转为 `int`
|
||||
- `AnyString`: 匹配任意类型,转为 `str`
|
||||
- `Number`: 匹配 `int` 与 `float`,转为 `int`
|
||||
|
||||
同时可以使用 typing 中的类型:
|
||||
|
||||
- `Literal[X]`: 匹配其中的任意一个值
|
||||
- `Union[X, Y]`: 匹配其中的任意一个类型
|
||||
- `Optional[xxx]`: 会自动将默认值设为 `None`,并在解析失败时使用默认值
|
||||
- `List[X]`: 匹配一个列表,其中的元素为 `X` 类型
|
||||
- `Dict[X, Y]`: 匹配一个字典,其中的 key 为 `X` 类型,value 为 `Y` 类型
|
||||
- `Optional[xxx]`: 会自动将默认值设为 `None`,并在解析失败时使用默认值
|
||||
- `List[X]`: 匹配一个列表,其中的元素为 `X` 类型
|
||||
- `Dict[X, Y]`: 匹配一个字典,其中的 key 为 `X` 类型,value 为 `Y` 类型
|
||||
- ...
|
||||
|
||||
:::tip
|
||||
|
||||
几类特殊的传入标记:
|
||||
|
||||
- `"foo"`: 匹配字符串 "foo" (若没有某个 `BasePattern` 与之关联)
|
||||
- `RawStr("foo")`: 匹配字符串 "foo" (不会被 `BasePattern` 替换)
|
||||
- `"foo"`: 匹配字符串 "foo" (若没有某个 `BasePattern` 与之关联)
|
||||
- `RawStr("foo")`: 匹配字符串 "foo" (即使有 `BasePattern` 与之关联也不会被替换)
|
||||
- `"foo|bar|baz"`: 匹配 "foo" 或 "bar" 或 "baz"
|
||||
- `[foo, bar, Baz, ...]`: 匹配其中的任意一个值或类型
|
||||
- `Callable[[X], Y]`: 匹配一个参数为 `X` 类型的值,并返回通过该函数调用得到的 `Y` 类型的值
|
||||
- `"re:xxx"`: 匹配一个正则表达式 `xxx`,会返回 Match[0]
|
||||
- `"rep:xxx"`: 匹配一个正则表达式 `xxx`,会返回 `re.Match` 对象
|
||||
- `Callable[[X], Y]`: 匹配一个参数为 `X` 类型的值,并返回通过该函数调用得到的 `Y` 类型的值
|
||||
- `"re:xxx"`: 匹配一个正则表达式 `xxx`,会返回 Match[0]
|
||||
- `"rep:xxx"`: 匹配一个正则表达式 `xxx`,会返回 `re.Match` 对象
|
||||
- `{foo: bar, baz: qux}`: 匹配字典中的任意一个键, 并返回对应的值 (特殊的键 ... 会匹配任意的值)
|
||||
- ...
|
||||
|
||||
**特别的**,你可以不传入 `var`,此时会使用 `key` 作为 `var`, 匹配 `key` 字符串。
|
||||
|
||||
:::
|
||||
|
||||
`MultiVar` 则是一个特殊的标注,用于告知解析器该参数可以接受多个值,其构造方法形如 `MultiVar(str)`。 同样的还有 `KeyWordVar`,其构造方法形如 `KeyWordVar(str)`,用于告知解析器该参数为一个 keyword-only 参数。
|
||||
#### MultiVar 与 KeyWordVar
|
||||
|
||||
`MultiVar` 是一个特殊的标注,用于告知解析器该参数可以接受多个值,类似于函数中的 `*args`,其构造方法形如 `MultiVar(str)`。
|
||||
|
||||
同样的还有 `KeyWordVar`,类似于函数中的 `*, name: type`,其构造方法形如 `KeyWordVar(str)`,用于告知解析器该参数为一个 keyword-only 参数。
|
||||
|
||||
:::tip
|
||||
|
||||
`MultiVar` 与 `KeyWordVar` 组合时,代表该参数为一个可接受多个 key-value 的参数,其构造方法形如 `MultiVar(KeyWordVar(str))`
|
||||
`MultiVar` 与 `KeyWordVar` 组合时,代表该参数为一个可接受多个 key-value 的参数,类似于函数中的 `**kwargs`,其构造方法形如 `MultiVar(KeyWordVar(str))`
|
||||
|
||||
`MultiVar` 与 `KeyWordVar` 也可以传入 `default` 参数,用于指定默认值
|
||||
`MultiVar` 与 `KeyWordVar` 也可以传入 `default` 参数,用于指定默认值
|
||||
|
||||
`MultiVar` 不能在 `KeyWordVar` 之后传入
|
||||
`MultiVar` 不能在 `KeyWordVar` 之后传入
|
||||
|
||||
:::
|
||||
|
||||
### Option 和 Subcommand
|
||||
### default
|
||||
|
||||
`Option` 可以传入一组 `alias`,如 `Option("--foo|-F|--FOO|-f")` 或 `Option("--foo", alias=["-F"]`
|
||||
`default` 传入的是该参数的默认值或者 `Field`,以携带对于该参数的更多信息。
|
||||
|
||||
传入别名后,`option` 会选择其中长度最长的作为选项名称。若传入为 "--foo|-f",则命令名称为 "--foo"
|
||||
默认情况下 (即不声明) `default` 的值为特殊值 `Empty`。这也意味着你可以将默认值设置为 `None` 表示默认值为空值。
|
||||
|
||||
`Field` 构造需要的参数说明如下:
|
||||
|
||||
- default: 参数单元的默认值
|
||||
- alias: 参数单元默认值的别名
|
||||
- completion: 参数单元的补全说明生成函数
|
||||
- unmatch_tips: 参数单元的错误提示生成函数,其接收一个表示匹配失败的元素的参数
|
||||
- missing_tips: 参数单元的缺失提示生成函数
|
||||
|
||||
## 选项与子命令(Option & Subcommand)
|
||||
|
||||
`Option` 和 `Subcommand` 可以传入一组 `alias`,如 `Option("--foo|-F|--FOO|-f")`,`Subcommand("foo", alias=["F"])`
|
||||
|
||||
传入别名后,选项与子命令会选择其中长度最长的作为其名称。若传入为 "--foo|-f",则命令名称为 "--foo"
|
||||
|
||||
:::tip 特别提醒!!!
|
||||
|
||||
在 Alconna 中 Option 的名字或别名**没有要求**必须在前面写上 `-`
|
||||
Option 的名字或别名**没有要求**必须在前面写上 `-`
|
||||
|
||||
Option 与 Subcommand 的唯一区别在于 Subcommand 可以传入自己的 **Option** 与 **Subcommand**
|
||||
|
||||
:::
|
||||
|
||||
`Subcommand` 可以传入自己的 **Option** 与 **Subcommand**
|
||||
|
||||
他们拥有如下共同参数:
|
||||
|
||||
- `help_text`: 传入该组件的帮助信息
|
||||
- `dest`: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
|
||||
- `requires`: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
|
||||
对于命令 `test foo bar baz qux <a:int>` 来讲,因为`foo bar baz` 仅需要判断是否相等, 所以可以这么编写:
|
||||
对于命令 `test foo bar baz qux <a:int>` 来讲,因为`foo bar baz` 仅需要判断是否相等, 所以可以这么编写:
|
||||
|
||||
```python
|
||||
Alconna("test", Option("qux", Args.a[int], requires=["foo", "bar", "baz"]))
|
||||
```
|
||||
|
||||
- `default`: 默认值,在该组件未被解析时使用使用该值替换。
|
||||
特别的,使用 `OptionResult` 或 `SubcomanndResult` 可以设置包括参数字典在内的默认值:
|
||||
特别的,使用 `OptionResult` 或 `SubcomanndResult` 可以设置包括参数字典在内的默认值:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Option, OptionResult
|
||||
@@ -237,25 +255,27 @@ opt1 = Option("--foo", default=False)
|
||||
opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
|
||||
```
|
||||
|
||||
`Option` 可以特别设置传入一类 `Action`,作为解析操作
|
||||
### Action
|
||||
|
||||
`Action` 分为三类:
|
||||
`Option` 可以特别设置传入一类 `Action`,作为解析操作
|
||||
|
||||
`Action` 分为三类:
|
||||
|
||||
- `store`: 无 Args 时, 仅存储一个值, 默认为 Ellipsis; 有 Args 时, 后续的解析结果会覆盖之前的值
|
||||
- `append`: 无 Args 时, 将多个值存为列表, 默认为 Ellipsis; 有 Args 时, 每个解析结果会追加到列表中, 当存在默认值并且不为列表时, 会自动将默认值变成列表, 以保证追加的正确性
|
||||
- `count`: 无 Args 时, 计数器加一; 有 Args 时, 表现与 STORE 相同, 当存在默认值并且不为数字时, 会自动将默认值变成 1, 以保证计数器的正确性。
|
||||
|
||||
`Alconna` 提供了预制的几类 `Action`:
|
||||
`Alconna` 提供了预制的几类 `Action`:
|
||||
|
||||
- `store`(默认),`store_value`,`store_true`,`store_false`
|
||||
- `append`,`append_value`
|
||||
- `count`
|
||||
|
||||
### Arparma
|
||||
## 解析结果(Arparma)
|
||||
|
||||
`Alconna.parse` 会返回由 **Arparma** 承载的解析结果
|
||||
`Alconna.parse` 会返回由 **Arparma** 承载的解析结果
|
||||
|
||||
`Arparma` 会有如下参数:
|
||||
`Arparma` 有如下属性:
|
||||
|
||||
- 调试类
|
||||
|
||||
@@ -272,22 +292,53 @@ opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
|
||||
- other_args: 除主参数外的其他解析结果
|
||||
- all_matched_args: 所有 Args 的解析结果
|
||||
|
||||
`Arparma` 同时提供了便捷的查询方法 `query[type]()`,会根据传入的 `path` 查找参数并返回
|
||||
`Arparma` 同时提供了便捷的查询方法 `query[type]()`,会根据传入的 `path` 查找参数并返回
|
||||
|
||||
`path` 支持如下:
|
||||
`path` 支持如下:
|
||||
|
||||
- `main_args`, `options`, ...: 返回对应的属性
|
||||
- `main_args`, `options`, ...: 返回对应的属性
|
||||
- `args`: 返回 all_matched_args
|
||||
- `main_args.xxx`, `options.xxx`, ...: 返回字典中 `xxx`键对应的值
|
||||
- `args.xxx`: 返回 all_matched_args 中 `xxx`键对应的值
|
||||
- `options.foo`, `foo`: 返回选项 `foo` 的解析结果 (OptionResult)
|
||||
- `options.foo.value`, `foo.value`: 返回选项 `foo` 的解析值
|
||||
- `options.foo.args`, `foo.args`: 返回选项 `foo` 的解析参数字典
|
||||
- `options.foo.args.bar`, `foo.bar`: 返回选项 `foo` 的参数字典中 `bar` 键对应的值 ...
|
||||
- `main_args.xxx`, `options.xxx`, ...: 返回字典中 `xxx`键对应的值
|
||||
- `args.xxx`: 返回 all_matched_args 中 `xxx`键对应的值
|
||||
- `options.foo`, `foo`: 返回选项 `foo` 的解析结果 (OptionResult)
|
||||
- `options.foo.value`, `foo.value`: 返回选项 `foo` 的解析值
|
||||
- `options.foo.args`, `foo.args`: 返回选项 `foo` 的解析参数字典
|
||||
- `options.foo.args.bar`, `foo.bar`: 返回选项 `foo` 的参数字典中 `bar` 键对应的值 ...
|
||||
|
||||
## 元数据(CommandMeta)
|
||||
|
||||
`Alconna` 的元数据相当于其配置,拥有以下条目:
|
||||
|
||||
- `description`: 命令的描述
|
||||
- `usage`: 命令的用法
|
||||
- `example`: 命令的使用样例
|
||||
- `author`: 命令的作者
|
||||
- `fuzzy_match`: 命令是否开启模糊匹配
|
||||
- `fuzzy_threshold`: 模糊匹配阈值
|
||||
- `raise_exception`: 命令是否抛出异常
|
||||
- `hide`: 命令是否对 manager 隐藏
|
||||
- `hide_shortcut`: 命令的快捷指令是否在 help 信息中隐藏
|
||||
- `keep_crlf`: 命令解析时是否保留换行字符
|
||||
- `compact`: 命令是否允许第一个参数紧随头部
|
||||
- `strict`: 命令是否严格匹配,若为 False 则未知参数将作为名为 $extra 的参数
|
||||
- `context_style`: 命令上下文插值的风格,None 为关闭,bracket 为 {...},parentheses 为 $(...)
|
||||
- `extra`: 命令的自定义额外信息
|
||||
|
||||
元数据一定使用 `meta=...` 形式传入:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, CommandMeta
|
||||
|
||||
alc = Alconna(..., meta=CommandMeta("foo", example="bar"))
|
||||
```
|
||||
|
||||
## 命名空间配置
|
||||
|
||||
命名空间配置 (以下简称命名空间) 相当于`Alconna`的设置,`Alconna`默认使用“Alconna”命名空间,命名空间有以下几个属性:
|
||||
命名空间配置 (以下简称命名空间) 相当于 `Alconna` 的默认配置,其优先度低于 `CommandMeta`。
|
||||
|
||||
`Alconna` 默认使用 "Alconna" 命名空间。
|
||||
|
||||
命名空间有以下几个属性:
|
||||
|
||||
- name: 命名空间名称
|
||||
- prefixes: 默认前缀配置
|
||||
@@ -296,9 +347,11 @@ opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
|
||||
- fuzzy_match: 默认是否开启模糊匹配
|
||||
- raise_exception: 默认是否抛出异常
|
||||
- builtin_option_name: 默认的内置选项名称(--help, --shortcut, --comp)
|
||||
- disable_builtin_options: 默认禁用的内置选项(--help, --shortcut, --comp)
|
||||
- enable_message_cache: 默认是否启用消息缓存
|
||||
- compact: 默认是否开启紧凑模式
|
||||
- strict: 命令是否严格匹配
|
||||
- context_style: 命令上下文插值的风格
|
||||
- ...
|
||||
|
||||
### 新建命名空间并替换
|
||||
@@ -307,7 +360,7 @@ opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
|
||||
from arclet.alconna import Alconna, namespace, Namespace, Subcommand, Args, config
|
||||
|
||||
|
||||
ns = Namespace("foo", prefixes=["/"]) # 创建 "foo"命名空间配置, 它要求创建的Alconna的主命令前缀必须是/
|
||||
ns = Namespace("foo", prefixes=["/"]) # 创建 "foo"命名空间配置, 它要求创建的Alconna的主命令前缀必须是/
|
||||
|
||||
alc = Alconna("pip", Subcommand("install", Args["package", str]), namespace=ns) # 在创建Alconna时候传入命名空间以替换默认命名空间
|
||||
|
||||
@@ -342,15 +395,15 @@ with namespace(config.default_namespace.name) as np:
|
||||
|
||||
快捷命令可以做到标识一段命令, 并且传递参数给原命令
|
||||
|
||||
一般情况下你可以通过 `Alconna.shortcut` 进行快捷指令操作 (创建,删除)
|
||||
一般情况下你可以通过 `Alconna.shortcut` 进行快捷指令操作 (创建,删除)
|
||||
|
||||
`shortcut` 的第一个参数为快捷指令名称,第二个参数为 `ShortcutArgs`,作为快捷指令的配置:
|
||||
`shortcut` 的第一个参数为快捷指令名称,第二个参数为 `ShortcutArgs`,作为快捷指令的配置:
|
||||
|
||||
```python
|
||||
class ShortcutArgs(TypedDict):
|
||||
"""快捷指令参数"""
|
||||
|
||||
command: NotRequired[DataCollection[Any]]
|
||||
command: NotRequired[str]
|
||||
"""快捷指令的命令"""
|
||||
args: NotRequired[list[Any]]
|
||||
"""快捷指令的附带参数"""
|
||||
@@ -358,6 +411,10 @@ class ShortcutArgs(TypedDict):
|
||||
"""是否允许命令后随参数"""
|
||||
prefix: NotRequired[bool]
|
||||
"""是否调用时保留指令前缀"""
|
||||
wrapper: NotRequired[ShortcutRegWrapper]
|
||||
"""快捷指令的正则匹配结果的额外处理函数"""
|
||||
humanized: NotRequired[str]
|
||||
"""快捷指令的人类可读描述"""
|
||||
```
|
||||
|
||||
### args的使用
|
||||
@@ -400,28 +457,28 @@ alc.parse("echo hello world!")
|
||||
# hello world!
|
||||
```
|
||||
|
||||
当 `fuzzy` 为 False 时,第一个例子中传入 `"涩图1张 abc"` 之类的快捷指令将视为解析失败
|
||||
当 `fuzzy` 为 False 时,第一个例子中传入 `"涩图1张 abc"` 之类的快捷指令将视为解析失败
|
||||
|
||||
快捷指令允许三类特殊的 placeholder:
|
||||
|
||||
- `{%X}`: 如 `setu {%0}`,表示此处填入快捷指令后随的第 X 个参数。
|
||||
- `{%X}`: 如 `setu {%0}`,表示此处填入快捷指令后随的第 X 个参数。
|
||||
|
||||
例如,若快捷指令为 `涩图`, 配置为 `{"command": "setu {%0}"}`, 则指令 `涩图 1` 相当于 `setu 1`
|
||||
例如,若快捷指令为 `涩图`, 配置为 `{"command": "setu {%0}"}`, 则指令 `涩图 1` 相当于 `setu 1`
|
||||
|
||||
- `{*}`: 表示此处填入所有后随参数,并且可以通过 `{*X}` 的方式指定组合参数之间的分隔符。
|
||||
- `{*}`: 表示此处填入所有后随参数,并且可以通过 `{*X}` 的方式指定组合参数之间的分隔符。
|
||||
|
||||
- `{X}`: 表示此处填入可能的正则匹配的组:
|
||||
|
||||
- 若 `command` 中存在匹配组 `(xxx)`,则 `{X}` 表示第 X 个匹配组的内容
|
||||
- 若 `command` 中存储匹配组 `(?P<xxx>...)`, 则 `{X}` 表示 **名字** 为 X 的匹配结果
|
||||
- 若 `command` 中存在匹配组 `(xxx)`,则 `{X}` 表示第 X 个匹配组的内容
|
||||
- 若 `command` 中存储匹配组 `(?P<xxx>...)`, 则 `{X}` 表示 **名字** 为 X 的匹配结果
|
||||
|
||||
除此之外, 通过 **Alconna** 内置选项 `--shortcut` 可以动态操作快捷指令
|
||||
除此之外, 通过 **Alconna** 内置选项 `--shortcut` 可以动态操作快捷指令
|
||||
|
||||
例如:
|
||||
|
||||
- `cmd --shortcut <key> <cmd>` 来增加一个快捷指令
|
||||
- `cmd --shortcut list` 来列出当前指令的所有快捷指令
|
||||
- `cmd --shortcut delete key` 来删除一个快捷指令
|
||||
- `cmd --shortcut <key> <cmd>` 来增加一个快捷指令
|
||||
- `cmd --shortcut list` 来列出当前指令的所有快捷指令
|
||||
- `cmd --shortcut delete key` 来删除一个快捷指令
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Args
|
||||
@@ -437,7 +494,7 @@ alc.parse("eval --shortcut list")
|
||||
|
||||
## 紧凑命令
|
||||
|
||||
`Alconna`, `Option` 与 `Subcommand` 可以设置 `compact=True` 使得解析命令时允许名称与后随参数之间没有分隔:
|
||||
`Alconna`, `Option` 与 `Subcommand` 可以设置 `compact=True` 使得解析命令时允许名称与后随参数之间没有分隔:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Option, CommandMeta, Args
|
||||
@@ -459,7 +516,7 @@ print(alc.parse("gcc -Fabc -Fdef -Fxyz").query[list]("flag.content"))
|
||||
# ['abc', 'def', 'xyz']
|
||||
```
|
||||
|
||||
当 `Option` 的 `action` 为 `count` 时,其自动支持 `compact` 特性:
|
||||
当 `Option` 的 `action` 为 `count` 时,其自动支持 `compact` 特性:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Option, count
|
||||
@@ -472,8 +529,6 @@ print(alc.parse("pp -vvv").query[int]("verbose.value"))
|
||||
|
||||
## 模糊匹配
|
||||
|
||||
模糊匹配通过在 Alconna 中设置其 CommandMeta 开启
|
||||
|
||||
模糊匹配会应用在任意需要进行名称判断的地方,如 **命令名称**,**选项名称** 和 **参数名称** (如指定需要传入参数名称)。
|
||||
|
||||
```python
|
||||
@@ -490,7 +545,7 @@ alc.parse("test_fuzy")
|
||||
|
||||
半自动补全为用户提供了推荐后续输入的功能
|
||||
|
||||
补全默认通过 `--comp` 或 `-cp` 或 `?` 触发:(命名空间配置可修改名称)
|
||||
补全默认通过 `--comp` 或 `-cp` 或 `?` 触发:(命名空间配置可修改名称)
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Args, Option
|
||||
@@ -515,9 +570,9 @@ output
|
||||
|
||||
## Duplication
|
||||
|
||||
**Duplication** 用来提供更好的自动补全,类似于 **ArgParse** 的 **Namespace**
|
||||
**Duplication** 用来提供更好的自动补全,类似于 **ArgParse** 的 **Namespace**
|
||||
|
||||
普通情况下使用,需要利用到 **ArgsStub**、**OptionStub** 和 **SubcommandStub** 三个部分
|
||||
普通情况下使用,需要利用到 **ArgsStub**、**OptionStub** 和 **SubcommandStub** 三个部分
|
||||
|
||||
以pip为例,其对应的 Duplication 应如下构造:
|
||||
|
||||
@@ -526,20 +581,20 @@ from arclet.alconna import Alconna, Args, Option, OptionResult, Duplication, Sub
|
||||
|
||||
|
||||
class MyDup(Duplication):
|
||||
verbose: OptionResult
|
||||
install: SubcommandStub
|
||||
verbose: OptionResult
|
||||
install: SubcommandStub
|
||||
|
||||
|
||||
alc = Alconna(
|
||||
"pip",
|
||||
Subcommand(
|
||||
"install",
|
||||
Args["package", str],
|
||||
Option("-r|--requirement", Args["file", str]),
|
||||
Option("-i|--index-url", Args["url", str]),
|
||||
),
|
||||
Option("-v|--version"),
|
||||
Option("-v|--verbose", action=count),
|
||||
"pip",
|
||||
Subcommand(
|
||||
"install",
|
||||
Args["package", str],
|
||||
Option("-r|--requirement", Args["file", str]),
|
||||
Option("-i|--index-url", Args["url", str]),
|
||||
),
|
||||
Option("-v|--version"),
|
||||
Option("-v|--verbose", action=count),
|
||||
)
|
||||
|
||||
res = alc.parse("pip -v install ...") # 不使用duplication获得的提示较少
|
||||
@@ -551,7 +606,7 @@ print(result.install)
|
||||
# SubcommandStub(_origin=Subcommand('install', args=Args('package': str)), _value=Ellipsis, available=True, args=ArgsStub(_origin=Args('package': str), _value={'package': '...'}, available=True), dest='install', options=[OptionStub(_origin=Option('requirement', args=Args('file': str)), _value=None, available=False, args=ArgsStub(_origin=Args('file': str), _value={}, available=False), dest='requirement', aliases=['r', 'requirement'], name='requirement'), OptionStub(_origin=Option('index-url', args=Args('url': str)), _value=None, available=False, args=ArgsStub(_origin=Args('url': str), _value={}, available=False), dest='index-url', aliases=['index-url', 'i'], name='index-url')], subcommands=[], name='install')
|
||||
```
|
||||
|
||||
**Duplication** 也可以如 **Namespace** 一样直接标明参数名称和类型:
|
||||
**Duplication** 也可以如 **Namespace** 一样直接标明参数名称和类型:
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
@@ -563,3 +618,23 @@ class MyDup(Duplication):
|
||||
file: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
```
|
||||
|
||||
## 上下文插值
|
||||
|
||||
当 `context_style` 条目被设置后,传入的命令中符合上下文插值的字段会被自动替换成当前上下文中的信息。
|
||||
|
||||
上下文可以在 `parse` 中传入:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Args, CommandMeta
|
||||
|
||||
alc = Alconna("test", Args["foo", int], meta=CommandMeta(context_style="parentheses"))
|
||||
|
||||
alc.parse("test $(bar)", {"bar": 123})
|
||||
# {"foo": 123}
|
||||
```
|
||||
|
||||
context_style 的值分两种:
|
||||
|
||||
- `"bracket"`: 插值格式为 `{...}`,例如 `{foo}`
|
||||
- `"parentheses"`: 插值格式为 `$(...)`,例如 `$(bar)`
|
||||
|
@@ -46,3 +46,31 @@ description: 配置项
|
||||
- **默认值**: `[]`
|
||||
|
||||
全局加载的扩展,路径以 . 分隔,如 `foo.bar.baz:DemoExtension`。
|
||||
|
||||
## alconna_context_style
|
||||
|
||||
- **类型**: `Optional[Literal["bracket", "parentheses"]]`
|
||||
- **默认值**: `None`
|
||||
|
||||
全局命令上下文插值的风格,None 为关闭,bracket 为 `{...}`,parentheses 为 `$(...)`。
|
||||
|
||||
## alconna_enable_saa_patch
|
||||
|
||||
- **类型**: `bool`
|
||||
- **默认值**: `False`
|
||||
|
||||
是否启用 SAA 补丁。
|
||||
|
||||
## alconna_apply_filehost
|
||||
|
||||
- **类型**: `bool`
|
||||
- **默认值**: `False`
|
||||
|
||||
是否启用文件托管。
|
||||
|
||||
## alconna_apply_fetch_targets
|
||||
|
||||
- **类型**: `bool`
|
||||
- **默认值**: `False`
|
||||
|
||||
是否启动时拉取一次发送对象列表。
|
||||
|
@@ -3,15 +3,17 @@ sidebar_position: 3
|
||||
description: 响应规则的使用
|
||||
---
|
||||
|
||||
# Alconna 响应规则
|
||||
import Messenger from "@site/src/components/Messenger";
|
||||
|
||||
以下为一个使用示例:
|
||||
# Alconna 插件
|
||||
|
||||
展示:
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna.adapters.onebot12 import Image
|
||||
from nonebot_plugin_alconna import At, on_alconna
|
||||
from nonebot_plugin_alconna import At, Image, on_alconna
|
||||
from arclet.alconna import Args, Option, Alconna, Arparma, MultiVar, Subcommand
|
||||
|
||||
|
||||
alc = Alconna(
|
||||
["/", "!"],
|
||||
"role-group",
|
||||
@@ -29,8 +31,8 @@ rg = on_alconna(alc, auto_send_output=True)
|
||||
@rg.handle()
|
||||
async def _(result: Arparma):
|
||||
if result.find("list"):
|
||||
img = await ob12_gen_role_group_list_image()
|
||||
await rg.finish(Image(img))
|
||||
img: bytes = await gen_role_group_list_image()
|
||||
await rg.finish(Image(raw=img))
|
||||
if result.find("add"):
|
||||
group = await create_role_group(result.query[str]("add.name"))
|
||||
if result.find("add.member"):
|
||||
@@ -41,45 +43,69 @@ async def _(result: Arparma):
|
||||
|
||||
## 响应器使用
|
||||
|
||||
`on_alconna` 的所有参数如下:
|
||||
本插件基于 **Alconna**,为 **Nonebot** 提供了一类新的事件响应器辅助函数 `on_alconna`:
|
||||
|
||||
- `command: Alconna | str`: Alconna 命令
|
||||
- `skip_for_unmatch: bool = True`: 是否在命令不匹配时跳过该响应
|
||||
- `auto_send_output: bool = False`: 是否自动发送输出信息并跳过响应
|
||||
- `aliases: set[str | tuple[str, ...]] | None = None`: 命令别名,作用类似于 `on_command` 中的 aliases
|
||||
- `comp_config: CompConfig | None = None`: 补全会话配置,不传入则不启用补全会话
|
||||
- `extensions: list[type[Extension] | Extension] | None = None`: 需要加载的匹配扩展,可以是扩展类或扩展实例
|
||||
- `exclude_ext: list[type[Extension] | str] | None = None`: 需要排除的匹配扩展,可以是扩展类或扩展的 id
|
||||
- `use_origin: bool = False`: 是否使用未经 to_me 等处理过的消息
|
||||
- `use_cmd_start: bool = False`: 是否使用 COMMAND_START 作为命令前缀
|
||||
- `use_cmd_sep: bool = False`: 是否使用 COMMAND_SEP 作为命令分隔符
|
||||
```python
|
||||
def on_alconna(
|
||||
command: Alconna | str,
|
||||
skip_for_unmatch: bool = True,
|
||||
auto_send_output: bool = False,
|
||||
aliases: set[str | tuple[str, ...]] | None = None,
|
||||
comp_config: CompConfig | None = None,
|
||||
extensions: list[type[Extension] | Extension] | None = None,
|
||||
exclude_ext: list[type[Extension] | str] | None = None,
|
||||
use_origin: bool = False,
|
||||
use_cmd_start: bool = False,
|
||||
use_cmd_sep: bool = False,
|
||||
**kwargs,
|
||||
...,
|
||||
):
|
||||
```
|
||||
|
||||
- `command`: Alconna 命令或字符串,字符串将通过 `AlconnaFormat` 转换为 Alconna 命令
|
||||
- `skip_for_unmatch`: 是否在命令不匹配时跳过该响应
|
||||
- `auto_send_output`: 是否自动发送输出信息并跳过响应
|
||||
- `aliases`: 命令别名, 作用类似于 `on_command` 中的 aliases
|
||||
- `comp_config`: 补全会话配置, 不传入则不启用补全会话
|
||||
- `extensions`: 需要加载的匹配扩展, 可以是扩展类或扩展实例
|
||||
- `exclude_ext`: 需要排除的匹配扩展, 可以是扩展类或扩展的id
|
||||
- `use_origin`: 是否使用未经 to_me 等处理过的消息
|
||||
- `use_cmd_start`: 是否使用 COMMAND_START 作为命令前缀
|
||||
- `use_cmd_sep`: 是否使用 COMMAND_SEP 作为命令分隔符
|
||||
|
||||
`on_alconna` 返回的是 `Matcher` 的子类 `AlconnaMatcher` ,其拓展了如下方法:
|
||||
|
||||
- `.assign(path, value, or_not)`: 用于对包含多个选项/子命令的命令的分派处理
|
||||
- `.assign(path, value, or_not)`: 用于对包含多个选项/子命令的命令的分派处理(具体请看[条件控制](./matcher.mdx#条件控制))
|
||||
- `.got_path(path, prompt, middleware)`: 在 `got` 方法的基础上,会以 path 对应的参数为准,读取传入 message 的最后一个消息段并验证转换
|
||||
- `.set_path_arg(key, value)`, `.get_path_arg(key)`: 类似 `set_arg` 和 `got_arg`,为 `got_path` 的特化版本
|
||||
- `.reject_path(path[, prompt, fallback])`: 类似于 `reject_arg`,对应 `got_path`
|
||||
- `.dispatch`: 同样的分派处理,但是是类似 `CommandGroup` 一样返回新的 `AlconnaMatcher`
|
||||
- `.got`, `send`, `reject`, ... : 拓展了 prompt 类型,即支持使用 `UniMessage` 作为 prompt
|
||||
|
||||
用例:
|
||||
实例:
|
||||
|
||||
```python
|
||||
from nonebot import require
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
from arclet.alconna import Alconna, Option, Args
|
||||
from nonebot_plugin_alconna import on_alconna, AlconnaMatch, Match, UniMessage
|
||||
from nonebot_plugin_alconna import on_alconna, Match, UniMessage
|
||||
|
||||
login = on_alconna(Alconna(["/"], "login", Args["password?", str], Option("-r|--recall")))
|
||||
|
||||
login = on_alconna(Alconna(["/"], "login", Args["password?", str], Option("-r|--recall"))) # 这里["/"]指命令前缀必须是/
|
||||
|
||||
# /login -r 触发
|
||||
@login.assign("recall")
|
||||
async def login_exit():
|
||||
await login.finish("已退出")
|
||||
|
||||
# /login xxx 触发
|
||||
@login.assign("password")
|
||||
async def login_handle(pw: Match[str] = AlconnaMatch("password")):
|
||||
async def login_handle(pw: Match[str]):
|
||||
if pw.available:
|
||||
login.set_path_arg("password", pw.result)
|
||||
|
||||
# /login 触发
|
||||
@login.got_path("password", prompt=UniMessage.template("{:At(user, $event.get_user_id())} 请输入密码"))
|
||||
async def login_got(password: str):
|
||||
assert password
|
||||
@@ -88,17 +114,32 @@ async def login_got(password: str):
|
||||
|
||||
## 依赖注入
|
||||
|
||||
`Alconna` 的解析结果会放入 `Arparma` 类中,或用户指定的 `Duplication` 类。
|
||||
本插件提供了一系列依赖注入函数,便于在响应函数中获取解析结果:
|
||||
|
||||
而 `AlconnaMatcher` 在原有 Matcher 的基础上拓展了允许的依赖注入:
|
||||
- `AlconnaResult`: `CommandResult` 类型的依赖注入函数
|
||||
- `AlconnaMatches`: `Arparma` 类型的依赖注入函数
|
||||
- `AlconnaDuplication`: `Duplication` 类型的依赖注入函数
|
||||
- `AlconnaMatch`: `Match` 类型的依赖注入函数
|
||||
- `AlconnaQuery`: `Query` 类型的依赖注入函数
|
||||
|
||||
同时,基于 [`Annotated` 支持](https://github.com/nonebot/nonebot2/pull/1832),添加了两类注解:
|
||||
|
||||
- `AlcMatches`:同 `AlconnaMatches`
|
||||
- `AlcResult`:同 `AlconnaResult`
|
||||
|
||||
可以看到,本插件提供了几类额外的模型:
|
||||
|
||||
- `CommandResult`: 解析结果,包括了源命令 `source: Alconna` ,解析结果 `result: Arparma`,以及可能的输出信息 `output: str | None` 字段
|
||||
- `Match`: 匹配项,表示参数是否存在于 `all_matched_args` 内,可用 `Match.available` 判断是否匹配,`Match.result` 获取匹配的值
|
||||
- `Query`: 查询项,表示参数是否可由 `Arparma.query` 查询并获得结果,可用 `Query.available` 判断是否查询成功,`Query.result` 获取查询结果
|
||||
|
||||
**Alconna** 默认依赖注入的目标参数皆不需要使用依赖注入函数, 该效果对于 `AlconnaMatcher.got_path` 下的 Arg 同样有效:
|
||||
|
||||
```python
|
||||
@cmd.handle()
|
||||
async def handle(
|
||||
result: CommandResult,
|
||||
arp: Arparma,
|
||||
dup: Duplication, # 基类或子类都可以
|
||||
ext: Extension,
|
||||
dup: Duplication,
|
||||
source: Alconna,
|
||||
abc: str, # 类似 Match, 但是若匹配结果不存在对应字段则跳过该 handler
|
||||
foo: Match[str],
|
||||
@@ -107,12 +148,6 @@ async def handle(
|
||||
...
|
||||
```
|
||||
|
||||
可以看到,本插件提供了几类额外的模型:
|
||||
|
||||
- `CommandResult`: 解析结果,包括了源命令 `source: Alconna` ,解析结果 `result: Arparma`,以及可能的输出信息 `output: str | None` 字段
|
||||
- `Match`: 匹配项,表示参数是否存在于 `all_matched_args` 内,可用 `Match.available` 判断是否匹配,`Match.result` 获取匹配的值
|
||||
- `Query`: 查询项,表示参数是否可由 `Arparma.query` 查询并获得结果,可用 `Query.available` 判断是否查询成功,`Query.result` 获取查询结果
|
||||
|
||||
:::note
|
||||
|
||||
如果你更喜欢 Depends 式的依赖注入,`nonebot_plugin_alconna` 同时提供了一系列的依赖注入函数,他们包括:
|
||||
@@ -130,14 +165,19 @@ async def handle(
|
||||
实例:
|
||||
|
||||
```python
|
||||
...
|
||||
from nonebot import require
|
||||
require("nonebot_plugin_alconna")
|
||||
...
|
||||
|
||||
from nonebot_plugin_alconna import on_alconna, Match, Query, AlconnaQuery
|
||||
from nonebot_plugin_alconna import (
|
||||
on_alconna,
|
||||
Match,
|
||||
Query,
|
||||
AlconnaMatch,
|
||||
AlcResult
|
||||
)
|
||||
from arclet.alconna import Alconna, Args, Option, Arparma
|
||||
|
||||
|
||||
test = on_alconna(
|
||||
Alconna(
|
||||
"test",
|
||||
@@ -147,41 +187,34 @@ test = on_alconna(
|
||||
auto_send_output=True
|
||||
)
|
||||
|
||||
|
||||
@test.handle()
|
||||
async def handle_test1(result: AlcResult):
|
||||
await test.send(f"matched: {result.matched}")
|
||||
await test.send(f"maybe output: {result.output}")
|
||||
|
||||
@test.handle()
|
||||
async def handle_test2(bar: Match[int]):
|
||||
async def handle_test2(result: Arparma):
|
||||
await test.send(f"head result: {result.header_result}")
|
||||
await test.send(f"args: {result.all_matched_args}")
|
||||
|
||||
@test.handle()
|
||||
async def handle_test3(bar: Match[int] = AlconnaMatch("bar")):
|
||||
if bar.available:
|
||||
await test.send(f"foo={bar.result}")
|
||||
|
||||
@test.handle()
|
||||
async def handle_test3(qux: Query[bool] = AlconnaQuery("baz.qux", False)):
|
||||
async def handle_test4(qux: Query[bool] = Query("baz.qux", False)):
|
||||
if qux.available:
|
||||
await test.send(f"baz.qux={qux.result}")
|
||||
```
|
||||
|
||||
## 消息段标注
|
||||
## 多平台适配
|
||||
|
||||
示例中使用了消息段标注,其中 `At` 属于通用标注,而 `Image` 属于 `onebot12` 适配器下的标注。
|
||||
本插件提供了通用消息段标注, 通用消息段序列, 使插件使用者可以忽略平台之间字段的差异
|
||||
|
||||
适配器下的消息段标注会匹配特定的 `MessageSegment`:
|
||||
响应器使用示例中使用了消息段标注,其中 `At` 属于通用标注,而 `Image` 属于 `onebot12` 适配器下的标注。
|
||||
|
||||
而通用标注与适配器标注的区别在于,通用标注会匹配多个适配器中相似类型的消息段,并返回
|
||||
`nonebot_plugin_alconna.uniseg` 中定义的 [`Segment` 模型](./utils.md#通用消息段)
|
||||
|
||||
例如:
|
||||
|
||||
```python
|
||||
...
|
||||
ats = result.query[tuple[At, ...]]("add.member.target")
|
||||
group.extend(member.target for member in ats)
|
||||
```
|
||||
|
||||
这样插件使用者就不用考虑平台之间字段的差异
|
||||
具体介绍和使用请查看 [通用信息组件](./uniseg.mdx#通用消息段)
|
||||
|
||||
本插件为以下适配器提供了专门的适配器标注:
|
||||
|
||||
@@ -192,8 +225,8 @@ group.extend(member.target for member in ats)
|
||||
| [飞书](https://github.com/nonebot/adapter-feishu) | adapters.feishu |
|
||||
| [GitHub](https://github.com/nonebot/adapter-github) | adapters.github |
|
||||
| [QQ bot](https://github.com/nonebot/adapter-qq) | adapters.qq |
|
||||
| [QQ 频道 bot](https://github.com/nonebot/adapter-qq) | adapters.qqguild |
|
||||
| [钉钉](https://github.com/nonebot/adapter-ding) | adapters.ding |
|
||||
| [Dodo](https://github.com/nonebot/adapter-dodo) | adapters.dodo |
|
||||
| [Console](https://github.com/nonebot/adapter-console) | adapters.console |
|
||||
| [开黑啦](https://github.com/Tian-que/nonebot-adapter-kaiheila) | adapters.kook |
|
||||
| [Mirai](https://github.com/ieew/nonebot_adapter_mirai2) | adapters.mirai |
|
||||
@@ -201,7 +234,6 @@ group.extend(member.target for member in ats)
|
||||
| [MineCraft](https://github.com/17TheWord/nonebot-adapter-minecraft) | adapters.minecraft |
|
||||
| [BiliBili Live](https://github.com/wwweww/adapter-bilibili) | adapters.bilibili |
|
||||
| [Walle-Q](https://github.com/onebot-walle/nonebot_adapter_walleq) | adapters.onebot12 |
|
||||
| [Villa](https://github.com/CMHopeSunshine/nonebot-adapter-villa) | adapters.villa |
|
||||
| [Discord](https://github.com/nonebot/adapter-discord) | adapters.discord |
|
||||
| [Red 协议](https://github.com/nonebot/adapter-red) | adapters.red |
|
||||
| [Satori 协议](https://github.com/nonebot/adapter-satori) | adapters.satori |
|
||||
@@ -219,6 +251,7 @@ require("nonebot_plugin_alconna")
|
||||
from arclet.alconna import Alconna, Subcommand, Option, Args
|
||||
from nonebot_plugin_alconna import on_alconna, CommandResult
|
||||
|
||||
|
||||
pip = Alconna(
|
||||
"pip",
|
||||
Subcommand(
|
||||
@@ -262,6 +295,7 @@ async def update(arp: CommandResult):
|
||||
```python
|
||||
from nonebot_plugin_alconna import At, Match, UniMessage, on_alconna
|
||||
|
||||
|
||||
test_cmd = on_alconna(Alconna("test", Args["target?", Union[str, At]]))
|
||||
|
||||
@test_cmd.handle()
|
||||
@@ -305,15 +339,101 @@ async def tt(target: Union[str, At]):
|
||||
|
||||
:::
|
||||
|
||||
## 响应器创建装饰
|
||||
|
||||
本插件提供了一个 `funcommand` 装饰器, 其用于将一个接受任意参数, 返回 `str` 或 `Message` 或 `MessageSegment` 的函数转换为命令响应器:
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna import funcommand
|
||||
|
||||
|
||||
@funcommand()
|
||||
async def echo(msg: str):
|
||||
return msg
|
||||
```
|
||||
|
||||
其等同于:
|
||||
|
||||
```python
|
||||
from arclet.alconna import Alconna, Args
|
||||
from nonebot_plugin_alconna import on_alconna, AlconnaMatch, Match
|
||||
|
||||
|
||||
echo = on_alconna(Alconna("echo", Args["msg", str]))
|
||||
|
||||
@echo.handle()
|
||||
async def echo_exit(msg: Match[str] = AlconnaMatch("msg")):
|
||||
await echo.finish(msg.result)
|
||||
|
||||
```
|
||||
|
||||
## 类Koishi构造器
|
||||
|
||||
本插件提供了一个 `Command` 构造器,其基于 `arclet.alconna.tools` 中的 `AlconnaString`, 以类似 `Koishi` 中注册命令的方式来构建一个 **AlconnaMatcher** :
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna import Command, Arparma
|
||||
|
||||
|
||||
book = (
|
||||
Command("book", "测试")
|
||||
.option("writer", "-w <id:int>")
|
||||
.option("writer", "--anonymous", {"id": 0})
|
||||
.usage("book [-w <id:int> | --anonymous]")
|
||||
.shortcut("测试", {"args": ["--anonymous"]})
|
||||
.build()
|
||||
)
|
||||
|
||||
@book.handle()
|
||||
async def _(arp: Arparma):
|
||||
await book.send(str(arp.options))
|
||||
```
|
||||
|
||||
甚至,你可以设置 `action` 来设定响应行为:
|
||||
|
||||
```python
|
||||
book = (
|
||||
Command("book", "测试")
|
||||
.option("writer", "-w <id:int>")
|
||||
.option("writer", "--anonymous", {"id": 0})
|
||||
.usage("book [-w <id:int> | --anonymous]")
|
||||
.shortcut("测试", {"args": ["--anonymous"]})
|
||||
.action(lambda options: str(options)) # 会自动通过 bot.send 发送
|
||||
.build()
|
||||
)
|
||||
```
|
||||
|
||||
## 返回值中间件
|
||||
|
||||
在 `AlconnaMatch`,`AlconnaQuery` 或 `got_path` 中,你可以使用 `middleware` 参数来传入一个对返回值进行处理的函数:
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna import image_fetch
|
||||
|
||||
|
||||
mask_cmd = on_alconna(
|
||||
Alconna("search", Args["img?", Image]),
|
||||
)
|
||||
|
||||
|
||||
@mask_cmd.handle()
|
||||
async def mask_h(matcher: AlconnaMatcher, img: Match[bytes] = AlconnaMatch("img", image_fetch)):
|
||||
result = await search_img(img.result)
|
||||
await matcher.send(result.content)
|
||||
```
|
||||
|
||||
其中,`image_fetch` 是一个中间件,其接受一个 `Image` 对象,并提取图片的二进制数据返回。
|
||||
|
||||
## 匹配拓展
|
||||
|
||||
本插件提供了一个 `Extension` 类,其用于自定义 AlconnaMatcher 的部分行为。
|
||||
本插件提供了一个 `Extension` 类,其用于自定义 AlconnaMatcher 的部分行为
|
||||
|
||||
例如 `LLMExtension` (仅举例):
|
||||
例如一个 `LLMExtension` 可以如下实现 (仅举例):
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna import Extension, Alconna, on_alconna, Interface
|
||||
|
||||
|
||||
class LLMExtension(Extension):
|
||||
@property
|
||||
def priority(self) -> int:
|
||||
@@ -347,7 +467,7 @@ matcher = on_alconna(
|
||||
...
|
||||
```
|
||||
|
||||
那么使用了 `LLMExtension` 的响应器便能接受任何能通过 llm 翻译为具体命令的自然语言消息,同时可以在响应器中为所有 `llm` 参数注入模型变量
|
||||
那么添加了 `LLMExtension` 的响应器便能接受任何能通过 llm 翻译为具体命令的自然语言消息,同时可以在响应器中为所有 `llm` 参数注入模型变量。
|
||||
|
||||
目前 `Extension` 的功能有:
|
||||
|
||||
@@ -355,19 +475,20 @@ matcher = on_alconna(
|
||||
- `output_converter`: 输出信息的自定义转换方法
|
||||
- `message_provider`: 从传入事件中自定义提取消息的方法
|
||||
- `receive_provider`: 对传入的消息 (Message 或 UniMessage) 的额外处理
|
||||
- `context_provider`: 对命令上下文的额外处理
|
||||
- `permission_check`: 命令对消息解析并确认头部匹配(即确认选择响应)时对发送者的权限判断
|
||||
- `parse_wrapper`: 对命令解析结果的额外处理
|
||||
- `send_wrapper`: 对发送的消息 (Message 或 UniMessage) 的额外处理
|
||||
- `before_catch`: 自定义依赖注入的绑定确认函数
|
||||
- `catch`: 自定义依赖注入处理函数
|
||||
- `post_init`: 响应器创建后对命令对象的额外除了
|
||||
- `post_init`: 响应器创建后对命令对象的额外处理
|
||||
|
||||
例如内置的 `DiscordSlashExtension`,其可自动将 Alconna 对象翻译成 slash 指令并注册,且将收到的指令交互事件转为指令供命令解析:
|
||||
例如内置的 `DiscordSlashExtension`,其可自动将 Alconna 对象翻译成 slash 指令并注册,且将收到的指令交互事件转为指令供命令解析:
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna import Match, on_alconna
|
||||
from nonebot_plugin_alconna.builtins.plugins.discord import DiscordSlashExtension
|
||||
|
||||
from nonebot_plugin_alconna.adapters.discord import DiscordSlashExtension
|
||||
|
||||
alc = Alconna(
|
||||
["/"],
|
||||
@@ -388,8 +509,99 @@ async def remove(plugin: Match[str], time: Match[int]):
|
||||
await matcher.finish(f"removed {plugin.result} with {time.result if time.available else -1}")
|
||||
```
|
||||
|
||||
目前插件提供了 4 个内置的 `Extension`,它们在 `nonebot_plugin_alconna.builtins.extensions` 下:
|
||||
|
||||
- `ReplyRecordExtension`: 将消息事件中的回复暂存在 extension 中,使得解析用的消息不带回复信息,同时可以在后续的处理中获取回复信息。
|
||||
- `DiscordSlashExtension`: 将 Alconna 的命令自动转换为 Discord 的 Slash Command,并将 Slash Command 的交互事件转换为消息交给 Alconna 处理。
|
||||
- `MarkdownOutputExtension`: 将 Alconna 的自动输出转换为 Markdown 格式
|
||||
- `TelegramSlashExtension`: 将 Alconna 的命令注册在 Telegram 上以获得提示。
|
||||
|
||||
:::tip
|
||||
|
||||
全局的 Extension 可延迟加载 (即若有全局拓展加载于部分 AlconnaMatcher 之后,这部分响应器会被追加拓展)
|
||||
|
||||
:::
|
||||
|
||||
## 补全会话
|
||||
|
||||
补全会话基于 [`半自动补全`](./command.md#半自动补全),用于指令参数缺失或参数错误时给予交互式提示,类似于 `got-reject`:
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna import Alconna, Args, Field, At, on_alconna
|
||||
|
||||
alc = Alconna(
|
||||
"添加教师",
|
||||
Args["name", str, Field(completion=lambda: "请输入姓名")],
|
||||
Args["phone", int, Field(completion=lambda: "请输入手机号")],
|
||||
Args["at", [str, At], Field(completion=lambda: "请输入教师号")],
|
||||
)
|
||||
|
||||
cmd = on_alconna(alc, comp_config={"lite": True}, skip_for_unmatch=False)
|
||||
|
||||
@cmd.handle()
|
||||
async def handle(result: Arparma):
|
||||
cmd.finish("添加成功")
|
||||
```
|
||||
|
||||
此时,当用户输入 `添加教师` 时,会自动提示用户输入姓名,手机号和教师号,用户输入后会自动进入下一个提示:
|
||||
|
||||
<Messenger
|
||||
msgs={[
|
||||
{ position: "right", msg: "添加教师" },
|
||||
{ position: "left", msg: "以下是建议的输入: \n- name: 请输入姓名" },
|
||||
{ position: "right", msg: "foo" },
|
||||
{ position: "left", msg: "以下是建议的输入: \n- phone: 请输入手机号" },
|
||||
{ position: "right", msg: "12345" },
|
||||
{ position: "left", msg: "以下是建议的输入: \n- at: 请输入教师号" },
|
||||
{ position: "right", msg: "@me" },
|
||||
{ position: "left", msg: "添加成功" },
|
||||
]}
|
||||
/>
|
||||
|
||||
补全会话配置如下:
|
||||
|
||||
```python
|
||||
class CompConfig(TypedDict):
|
||||
tab: NotRequired[str]
|
||||
"""用于切换提示的指令的名称"""
|
||||
enter: NotRequired[str]
|
||||
"""用于输入提示的指令的名称"""
|
||||
exit: NotRequired[str]
|
||||
"""用于退出会话的指令的名称"""
|
||||
timeout: NotRequired[int]
|
||||
"""超时时间"""
|
||||
hide_tabs: NotRequired[bool]
|
||||
"""是否隐藏所有提示"""
|
||||
hides: NotRequired[Set[Literal["tab", "enter", "exit"]]]
|
||||
"""隐藏的指令"""
|
||||
disables: NotRequired[Set[Literal["tab", "enter", "exit"]]]
|
||||
"""禁用的指令"""
|
||||
lite: NotRequired[bool]
|
||||
"""是否使用简洁版本的补全会话(相当于同时配置 disables、hides、hide_tabs)"""
|
||||
```
|
||||
|
||||
## 内置插件
|
||||
|
||||
类似于 Nonebot 本身提供的内置插件,`nonebot_plugin_alconna` 提供了两个内置插件:`echo` 和 `help`。
|
||||
|
||||
你可以用本插件的 `load_builtin_plugin(s)` 来加载它们:
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna import load_builtin_plugins
|
||||
|
||||
load_builtin_plugins("echo", "help")
|
||||
```
|
||||
|
||||
其中 `help` 仅能列出所有 Alconna 指令。
|
||||
|
||||
<Messenger
|
||||
msgs={[
|
||||
{ position: "right", msg: "/帮助" },
|
||||
{
|
||||
position: "left",
|
||||
msg: "# 当前可用的命令有:\n 0 /echo : echo 指令\n 1 /help : 显示所有命令帮助\n# 输入'命令名 -h|--help' 查看特定命令的语法",
|
||||
},
|
||||
{ position: "right", msg: "/echo [图片]" },
|
||||
{ position: "left", msg: "[图片]" },
|
||||
]}
|
||||
/>
|
@@ -12,27 +12,30 @@ import TabItem from "@theme/TabItem";
|
||||
|
||||
## 通用消息段
|
||||
|
||||
适配器下的消息段标注会匹配适配器特定的 `MessageSegment`, 而通用消息段与适配器消息段的区别在于:
|
||||
通用消息段会匹配多个适配器中相似类型的消息段,并返回 `uniseg` 模块中定义的 [`Segment` 模型](https://nonebot.dev/docs/next/best-practice/alconna/utils#%E9%80%9A%E7%94%A8%E6%B6%88%E6%81%AF%E6%AE%B5), 以达到**跨平台接收消息**的作用。
|
||||
适配器下的消息段标注会匹配适配器特定的 `MessageSegment`, 而通用消息段与适配器消息段的区别在于:
|
||||
通用消息段会匹配多个适配器中相似类型的消息段,并返回 `uniseg` 模块中定义的 [`Segment` 模型](https://nonebot.dev/docs/next/best-practice/alconna/utils#%E9%80%9A%E7%94%A8%E6%B6%88%E6%81%AF%E6%AE%B5), 以达到**跨平台接收消息**的作用。
|
||||
|
||||
`nonebot-plugin-alconna.uniseg` 提供了类似 `MessageSegment` 的通用消息段,并可在 `Alconna` 下直接标注使用:
|
||||
|
||||
```python
|
||||
class Segment:
|
||||
"""基类标注"""
|
||||
children: List["Segment"]
|
||||
|
||||
class Text(Segment):
|
||||
"""Text对象, 表示一类文本元素"""
|
||||
text: str
|
||||
style: Optional[str]
|
||||
styles: Dict[Tuple[int, int], List[str]]
|
||||
|
||||
class At(Segment):
|
||||
"""At对象, 表示一类提醒某用户的元素"""
|
||||
type: Literal["user", "role", "channel"]
|
||||
flag: Literal["user", "role", "channel"]
|
||||
target: str
|
||||
display: Optional[str]
|
||||
|
||||
class AtAll(Segment):
|
||||
"""AtAll对象, 表示一类提醒所有人的元素"""
|
||||
here: bool
|
||||
|
||||
class Emoji(Segment):
|
||||
"""Emoji对象, 表示一类表情元素"""
|
||||
@@ -42,17 +45,23 @@ class Emoji(Segment):
|
||||
class Media(Segment):
|
||||
url: Optional[str]
|
||||
id: Optional[str]
|
||||
path: Optional[str]
|
||||
raw: Optional[bytes]
|
||||
path: Optional[Union[str, Path]]
|
||||
raw: Optional[Union[bytes, BytesIO]]
|
||||
mimetype: Optional[str]
|
||||
name: str
|
||||
|
||||
to_url: ClassVar[Optional[MediaToUrl]]
|
||||
|
||||
class Image(Media):
|
||||
"""Image对象, 表示一类图片元素"""
|
||||
|
||||
class Audio(Media):
|
||||
"""Audio对象, 表示一类音频元素"""
|
||||
duration: Optional[int]
|
||||
|
||||
class Voice(Media):
|
||||
"""Voice对象, 表示一类语音元素"""
|
||||
duration: Optional[int]
|
||||
|
||||
class Video(Media):
|
||||
"""Video对象, 表示一类视频元素"""
|
||||
@@ -73,17 +82,41 @@ class Reference(Segment):
|
||||
"""Reference对象,表示一类引用消息。转发消息 (Forward) 也属于此类"""
|
||||
id: Optional[str]
|
||||
"""此处不一定是消息ID,可能是其他ID,如消息序号等"""
|
||||
content: Optional[Union[Message, str, List[Union[RefNode, CustomNode]]]]
|
||||
children: List[Union[RefNode, CustomNode]]
|
||||
|
||||
class Card(Segment):
|
||||
type: Literal["xml", "json"]
|
||||
raw: str
|
||||
class Hyper(Segment):
|
||||
"""Hyper对象,表示一类超级消息。如卡片消息、ark消息、小程序等"""
|
||||
format: Literal["xml", "json"]
|
||||
raw: Optional[str]
|
||||
content: Optional[Union[dict, list]]
|
||||
|
||||
class Other(Segment):
|
||||
"""其他 Segment"""
|
||||
origin: MessageSegment
|
||||
|
||||
```
|
||||
|
||||
此类消息段通过 `UniMessage.export` 可以转为特定的 `MessageSegment`
|
||||
:::tips
|
||||
|
||||
或许你注意到了 `Segment` 上有一个 `children` 属性。
|
||||
|
||||
这是因为在 [`Satori`](https://satori.js.org/zh-CN/) 协议的规定下,一类元素可以用其子元素来代表一类兼容性消息
|
||||
(例如,qq 的商场表情在某些平台上可以用图片代替)。
|
||||
|
||||
为此,本插件提供了两种方式来表达 "获取子元素" 的方法:
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna.builtins.uniseg.chronocat import MarketFace
|
||||
from nonebot_plugin_alconna import Args, Image, Alconna, select, select_first
|
||||
|
||||
# 表示这个指令需要的图片要么直接是 Image 要么是在 MarketFace 元素内的 Image
|
||||
alc1 = Alconna("make_meme", Args["img", [Image, Image.from_(MarketFace)]])
|
||||
|
||||
# 表示这个指令需要的图片会在目标元素下进行搜索,将所有符合 Image 的元素选出来并将第一个作为结果
|
||||
alc2 = Alconna("make_meme", Args["img", select(Image, index=0)]) # 也可以使用 select_first(Image)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 通用消息序列
|
||||
|
||||
@@ -127,6 +160,7 @@ matcher = on_xxx(...)
|
||||
@matcher.handle()
|
||||
async def _(message: Message = EventMessage()):
|
||||
msg = await UniMessage.generate(message=message)
|
||||
msg1 = UniMessage.generate_without_reply(message=message)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -148,6 +182,21 @@ async def handle_test():
|
||||
await test.send(await UniMessage(Image(path="path/to/img")).export())
|
||||
```
|
||||
|
||||
除此之外 `UniMessage.send` 方法基于 `UniMessage.export` 并调用各适配器下的发送消息方法,返回一个 `Receipt` 对象,用于修改/撤回消息:
|
||||
|
||||
```python
|
||||
from nonebot import Bot, on_command
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
|
||||
|
||||
test = on_command("test")
|
||||
|
||||
@test.handle()
|
||||
async def handle():
|
||||
receipt = await UniMessage.text("hello!").send(at_sender=True, reply_to=True)
|
||||
await receipt.recall(delay=1)
|
||||
```
|
||||
|
||||
而在 `AlconnaMatcher` 下,`got`, `send`, `reject` 等可以发送消息的方法皆支持使用 `UniMessage`,不需要手动调用 export 方法:
|
||||
|
||||
```python
|
||||
@@ -168,24 +217,9 @@ async def tt(target: At):
|
||||
await test_cmd.send(UniMessage([target, "\ndone."]))
|
||||
```
|
||||
|
||||
除此之外 `UniMessage.send` 方法基于 `UniMessage.export` 并调用各适配器下的发送消息方法,返回一个 `Receipt` 对象,用于修改/撤回消息:
|
||||
|
||||
```python
|
||||
from nonebot import Bot, on_command
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
|
||||
|
||||
test = on_command("test")
|
||||
|
||||
@test.handle()
|
||||
async def handle():
|
||||
receipt = await UniMessage.text("hello!").send(at_sender=True, reply_to=True)
|
||||
await receipt.recall(delay=1)
|
||||
```
|
||||
|
||||
:::caution
|
||||
|
||||
在响应器以外的地方,`bot` 参数必须手动传入。
|
||||
在响应器以外的地方,除非启用了 `alconna_apply_fetch_targets` 配置项,否则 `bot` 参数必须手动传入。
|
||||
|
||||
:::
|
||||
|
||||
@@ -391,7 +425,7 @@ message.count(Text) == 2
|
||||
message.get(Text, 1) == UniMessage([Text("test1")])
|
||||
```
|
||||
|
||||
### 消息发送
|
||||
## 消息发送
|
||||
|
||||
前面提到,通用消息可用 `UniMessage.send` 发送自身:
|
||||
|
||||
@@ -408,6 +442,25 @@ async def send(
|
||||
|
||||
实际上,`UniMessage` 同时提供了获取消息事件 id 与消息发送对象的方法:
|
||||
|
||||
<Tabs groupId="get_unimsg">
|
||||
<TabItem value="depend" label="使用依赖注入">
|
||||
|
||||
通过提供的 `MessageTarget`, `MessageId` 或 `MsgTarget`, `MsgId` 依赖注入器来获取消息事件 id 与消息发送对象。
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna.uniseg import MessageId, MsgTarget
|
||||
|
||||
|
||||
matcher = on_xxx(...)
|
||||
|
||||
@matcher.handle()
|
||||
asycn def _(target: MsgTarget, msg_id: MessageId):
|
||||
...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="method" label="使用 UniMessage 的方法">
|
||||
|
||||
```python
|
||||
from nonebot import Event, Bot
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage, Target
|
||||
@@ -422,24 +475,116 @@ asycn def _(bot: Bot, event: Event):
|
||||
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
`send`, `get_target`, `get_message_id` 中与 `event`, `bot` 相关的参数都会尝试从上下文中获取对象。
|
||||
|
||||
其中,`Target`:
|
||||
### 消息发送对象
|
||||
|
||||
消息发送对象是用来描述响应消息时的发送对象或者主动发送消息时的目标对象的对象,它包含了以下属性:
|
||||
|
||||
```python
|
||||
class Target:
|
||||
id: str
|
||||
"""目标id;若为群聊则为group_id或者channel_id,若为私聊则为user_id"""
|
||||
parent_id: str = ""
|
||||
"""父级id;若为频道则为guild_id,其他情况为空字符串"""
|
||||
channel: bool = False
|
||||
"""是否为频道,仅当目标平台同时支持群聊和频道时有效"""
|
||||
private: bool = False
|
||||
parent_id: str
|
||||
"""父级id;若为频道则为guild_id,其他情况下可能为空字符串(例如 Feishu 下可作为部门 id)"""
|
||||
channel: bool
|
||||
"""是否为频道,仅当目标平台符合频道概念时"""
|
||||
private: bool
|
||||
"""是否为私聊"""
|
||||
source: str = ""
|
||||
source: str
|
||||
"""可能的事件id"""
|
||||
self_id: Union[str, None]
|
||||
"""机器人id,若为 None 则 Bot 对象会随机选择"""
|
||||
selector: Union[Callable[[Bot], Awaitable[bool]], None]
|
||||
"""选择器,用于在多个 Bot 对象中选择特定 Bot"""
|
||||
extra: Dict[str, Any]
|
||||
"""额外信息,用于适配器扩展"""
|
||||
```
|
||||
|
||||
是用来描述响应消息时的发送对象。
|
||||
其构造时需要如下参数:
|
||||
|
||||
同样的,你可以通过依赖注入的方式在响应器中直接获取它们。
|
||||
- `id` 为目标id;若为群聊则为 group_id 或者 channel_id,若为私聊则为user_id
|
||||
- `parent_id` 为父级id;若为频道则为 guild_id,其他情况下可能为空字符串(例如 Feishu 下可作为部门 id)
|
||||
- `channel` 为是否为频道,仅当目标平台符合频道概念时
|
||||
- `private` 为是否为私聊
|
||||
- `source` 为可能的事件id
|
||||
- `self_id` 为机器人id,若为 None 则 Bot 对象会随机选择
|
||||
- `selector` 为选择器,用于在多个 Bot 对象中选择特定 Bot
|
||||
- `scope` 为适配器范围,用于传入内置的特定选择器
|
||||
- `adapter` 为适配器名称,若为 None 则需要明确指定 Bot 对象
|
||||
- `platform` 为平台名称,仅当目标适配器存在多个平台时使用
|
||||
- `extra` 为额外信息,用于适配器扩展
|
||||
|
||||
通过 `Target` 对象,我们可以在 `UniMessage.send` 中指定发送对象:
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage, MsgTarget, Target, SupportScope
|
||||
|
||||
|
||||
matcher = on_xxx(...)
|
||||
|
||||
@matcher.handle()
|
||||
async def _(target: MsgTarget):
|
||||
await UniMessage("Hello!").send(target=target)
|
||||
target1 = Target("xxxx", scope=SupportScope.qq_client)
|
||||
await UniMessage("Hello!").send(target=target1)
|
||||
```
|
||||
|
||||
### 主动发送消息
|
||||
|
||||
`UniMessage.send` 也可以用于主动发送消息:
|
||||
|
||||
```python
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage, Target, SupportScope
|
||||
from nonebot import get_driver
|
||||
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
@driver.on_startup
|
||||
async def on_startup():
|
||||
target = Target("xxxx", scope=SupportScope.qq_client)
|
||||
await UniMessage("Hello!").send(target=target)
|
||||
```
|
||||
|
||||
## 自定义消息段
|
||||
|
||||
`uniseg` 提供了部分方法来允许用户自定义 Segment 的序列化和反序列化:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters import MessageSegment as BaseMessageSegment
|
||||
from nonebot.adapters.satori import Custom, Message, MessageSegment
|
||||
|
||||
from nonebot_plugin_alconna.uniseg.builder import MessageBuilder
|
||||
from nonebot_plugin_alconna.uniseg.exporter import MessageExporter
|
||||
from nonebot_plugin_alconna.uniseg import Segment, custom_handler, custom_register
|
||||
|
||||
|
||||
@dataclass
|
||||
class MarketFace(Segment):
|
||||
tabId: str
|
||||
faceId: str
|
||||
key: str
|
||||
|
||||
|
||||
@custom_register(MarketFace, "chronocat:marketface")
|
||||
def mfbuild(builder: MessageBuilder, seg: BaseMessageSegment):
|
||||
if not isinstance(seg, Custom):
|
||||
raise ValueError("MarketFace can only be built from Satori Message")
|
||||
return MarketFace(**seg.data)(*builder.generate(seg.children))
|
||||
|
||||
|
||||
@custom_handler(MarketFace)
|
||||
async def mfexport(exporter: MessageExporter, seg: MarketFace, bot: Bot, fallback: bool):
|
||||
if exporter.get_message_type() is Message:
|
||||
return MessageSegment("chronocat:marketface", seg.data)(await exporter.export(seg.children, bot, fallback))
|
||||
|
||||
```
|
||||
|
||||
具体而言,你可以使用 `custom_register` 来增加一个从 MessageSegment 到 Segment 的处理方法;使用 `custom_handler` 来增加一个从 Segment 到 MessageSegment 的处理方法。
|
||||
|
145
website/docs/best-practice/database/README.mdx
Normal file
145
website/docs/best-practice/database/README.mdx
Normal file
@@ -0,0 +1,145 @@
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
|
||||
# 数据库
|
||||
|
||||
[`nonebot-plugin-orm`](https://github.com/nonebot/plugin-orm) 是 NoneBot 的数据库支持插件。
|
||||
本插件基于 [SQLAlchemy](https://www.sqlalchemy.org/) 和 [Alembic](https://alembic.sqlalchemy.org/),提供了许多与 NoneBot 紧密集成的功能:
|
||||
|
||||
- 多 Engine / Connection 支持
|
||||
- Session 管理
|
||||
- 关系模型管理、依赖注入支持
|
||||
- 数据库迁移
|
||||
|
||||
## 安装
|
||||
|
||||
<Tabs groupId="install">
|
||||
<TabItem value="cli" label="使用 nb-cli">
|
||||
|
||||
```shell
|
||||
nb plugin install nonebot-plugin-orm
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pip" label="使用 pip">
|
||||
|
||||
```shell
|
||||
pip install nonebot-plugin-orm
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pdm" label="使用 pdm">
|
||||
|
||||
```shell
|
||||
pdm add nonebot-plugin-orm
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## 数据库驱动和后端
|
||||
|
||||
本插件只提供了 ORM 功能,没有数据库后端,也没有直接连接数据库后端的能力。
|
||||
所以你需要另行安装数据库驱动和数据库后端,并且配置数据库连接信息。
|
||||
|
||||
### SQLite
|
||||
|
||||
[SQLite](https://www.sqlite.org/) 是一个轻量级的嵌入式数据库,它的数据以单文件的形式存储在本地,不需要单独的数据库后端。
|
||||
SQLite 非常适合用于开发环境和小型应用,但是不适合用于大型应用的生产环境。
|
||||
|
||||
虽然不需要另行安装数据库后端,但你仍然需要安装数据库驱动:
|
||||
|
||||
<Tabs groupId="install">
|
||||
<TabItem value="pip" label="使用 pip">
|
||||
|
||||
```shell
|
||||
pip install "nonebot-plugin-orm[sqlite]"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pdm" label="使用 pdm">
|
||||
|
||||
```shell
|
||||
pdm add "nonebot-plugin-orm[sqlite]"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
默认情况下,数据库文件为 `<data path>/nonebot-plugin-orm/db.sqlite3`(数据目录由 [nonebot-plugin-localstore](../data-storing) 提供)。
|
||||
或者,你可以通过配置 `SQLALCHEMY_DATABASE_URL` 来指定数据库文件路径:
|
||||
|
||||
```shell
|
||||
SQLALCHEMY_DATABASE_URL=sqlite+aiosqlite:///file_path
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
[PostgreSQL](https://www.postgresql.org/) 是世界上最先进的开源关系数据库之一,对各种高级且广泛应用的功能有最好的支持,是中小型应用的首选数据库。
|
||||
|
||||
<Tabs groupId="install">
|
||||
<TabItem value="pip" label="使用 pip">
|
||||
|
||||
```shell
|
||||
pip install nonebot-plugin-orm[postgresql]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pdm" label="使用 pdm">
|
||||
|
||||
```shell
|
||||
pdm add nonebot-plugin-orm[postgresql]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
```shell
|
||||
SQLALCHEMY_DATABASE_URL=postgresql+psycopg://user:password@host:port/dbname[?key=value&key=value...]
|
||||
```
|
||||
|
||||
### MySQL / MariaDB
|
||||
|
||||
[MySQL](https://www.mysql.com/) 和 [MariaDB](https://mariadb.com/) 是经典的开源关系数据库,适合用于中小型应用。
|
||||
|
||||
<Tabs groupId="install">
|
||||
<TabItem value="pip" label="使用 pip">
|
||||
|
||||
```shell
|
||||
pip install nonebot-plugin-orm[mysql]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pdm" label="使用 pdm">
|
||||
|
||||
```shell
|
||||
pdm add nonebot-plugin-orm[mysql]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
```shell
|
||||
SQLALCHEMY_DATABASE_URL=mysql+aiomysql://user:password@host:port/dbname[?key=value&key=value...]
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
本插件提供了数据库迁移功能(此功能依赖于 [nb-cli 脚手架](../../quick-start#安装脚手架))。
|
||||
在安装了新的插件或机器人之后,你需要执行一次数据库迁移操作,将数据库同步至与机器人一致的状态:
|
||||
|
||||
```shell
|
||||
nb orm upgrade
|
||||
```
|
||||
|
||||
运行完毕后,可以检查一下:
|
||||
|
||||
```shell
|
||||
nb orm check
|
||||
```
|
||||
|
||||
如果输出是 `没有检测到新的升级操作`,那么恭喜你,数据库已经迁移完成了,你可以启动机器人了。
|
4
website/docs/best-practice/database/_category_.json
Normal file
4
website/docs/best-practice/database/_category_.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "数据库",
|
||||
"position": 7
|
||||
}
|
378
website/docs/best-practice/database/developer/README.md
Normal file
378
website/docs/best-practice/database/developer/README.md
Normal file
@@ -0,0 +1,378 @@
|
||||
# 开发者指南
|
||||
|
||||
开发者指南内容较多,故分为了一个示例以及数个专题。
|
||||
阅读(并且最好跟随实践)示例后,你将会对使用 `nonebot-plugin-orm` 开发插件有一个基本的认识。
|
||||
如果想要更深入地学习关于 [SQLAlchemy](https://www.sqlalchemy.org/) 和 [Alembic](https://alembic.sqlalchemy.org/) 的知识,或者在使用过程中遇到了问题,可以查阅专题以及其官方文档。
|
||||
|
||||
## 示例
|
||||
|
||||
### 模型定义
|
||||
|
||||
首先,我们需要设计存储的数据的结构。
|
||||
例如天气插件,需要存储**什么地方 (`location`)** 的**天气是什么 (`weather`)**。
|
||||
其中,一个地方只会有一种天气,而不同地方可能有相同的天气。
|
||||
所以,我们可以设计出如下的模型:
|
||||
|
||||
```python title=weather/__init__.py showLineNumbers
|
||||
from nonebot_plugin_orm import Model
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
|
||||
class Weather(Model):
|
||||
location: Mapped[str] = mapped_column(primary_key=True)
|
||||
weather: Mapped[str]
|
||||
```
|
||||
|
||||
其中,`primary_key=True` 意味着此列 (`location`) 是主键,即内容是唯一的且非空的。
|
||||
每一个模型必须有至少一个主键。
|
||||
|
||||
我们可以用以下代码检查模型生成的数据库模式是否正确:
|
||||
|
||||
```python
|
||||
from sqlalchemy.schema import CreateTable
|
||||
|
||||
print(CreateTable(Weather.__table__))
|
||||
```
|
||||
|
||||
```sql
|
||||
CREATE TABLE weather_weather (
|
||||
location VARCHAR NOT NULL,
|
||||
weather VARCHAR NOT NULL,
|
||||
CONSTRAINT pk_weather_weather PRIMARY KEY (location)
|
||||
)
|
||||
```
|
||||
|
||||
可以注意到表名是 `weather_weather` 而不是 `Weather` 或者 `weather`。
|
||||
这是因为 `nonebot-plugin-orm` 会自动为模型生成一个表名,规则是:`<插件模块名>_<类名小写>`。
|
||||
|
||||
你也可以通过指定 `__tablename__` 属性来自定义表名:
|
||||
|
||||
```python {2}
|
||||
class Weather(Model):
|
||||
__tablename__ = "weather"
|
||||
...
|
||||
```
|
||||
|
||||
```sql {1}
|
||||
CREATE TABLE weather (
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
但是,并不推荐你这么做,因为这可能会导致不同插件间的表名重复,引发冲突。
|
||||
特别是当你会发布插件时,你并不知道其他插件会不会使用相同的表名。
|
||||
|
||||
### 首次迁移
|
||||
|
||||
我们成功定义了模型,现在启动机器人试试吧:
|
||||
|
||||
```shell
|
||||
$ nb run
|
||||
01-02 15:04:05 [SUCCESS] nonebot | NoneBot is initializing...
|
||||
01-02 15:04:05 [ERROR] nonebot_plugin_orm | 启动检查失败
|
||||
01-02 15:04:05 [ERROR] nonebot | Application startup failed. Exiting.
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
click.exceptions.UsageError: 检测到新的升级操作:
|
||||
[('add_table',
|
||||
Table('weather', MetaData(), Column('location', String(), table=<weather>, primary_key=True, nullable=False), Column('weather', String(), table=<weather>, nullable=False), schema=None))]
|
||||
```
|
||||
|
||||
咦,发生了什么?
|
||||
`nonebot-plugin-orm` 试图阻止我们启动机器人。
|
||||
原来是我们定义了模型,但是数据库中并没有对应的表,这会导致插件不能正常运行。
|
||||
所以,我们需要迁移数据库。
|
||||
|
||||
首先,我们需要创建一个迁移脚本:
|
||||
|
||||
```shell
|
||||
nb orm revision -m "first revision" --branch-label weather
|
||||
```
|
||||
|
||||
其中,`-m` 参数是迁移脚本的描述,`--branch-label` 参数是迁移脚本的分支,一般为插件模块名。
|
||||
执行命令过后,出现了一个 `weather/migrations` 目录,其中有一个 `xxxxxxxxxxxx_first_revision.py` 文件:
|
||||
|
||||
```shell {4,5}
|
||||
weather
|
||||
├── __init__.py
|
||||
├── config.py
|
||||
└── migrations
|
||||
└── xxxxxxxxxxxx_first_revision.py
|
||||
```
|
||||
|
||||
这就是我们创建的迁移脚本,它记录了数据库模式的变化。
|
||||
我们可以查看一下它的内容:
|
||||
|
||||
```python title=weather/migrations/xxxxxxxxxxxx_first_revision.py {25-33,39-41} showLineNumbers
|
||||
"""first revision
|
||||
|
||||
迁移 ID: xxxxxxxxxxxx
|
||||
父迁移:
|
||||
创建时间: 2006-01-02 15:04:05.999999
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "xxxxxxxxxxxx"
|
||||
down_revision: str | Sequence[str] | None = None
|
||||
branch_labels: str | Sequence[str] | None = ("weather",)
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade(name: str = "") -> None:
|
||||
if name:
|
||||
return
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"weather_weather",
|
||||
sa.Column("location", sa.String(), nullable=False),
|
||||
sa.Column("weather", sa.String(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("location", name=op.f("pk_weather_weather")),
|
||||
info={"bind_key": "weather"},
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade(name: str = "") -> None:
|
||||
if name:
|
||||
return
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("weather_weather")
|
||||
# ### end Alembic commands ###
|
||||
```
|
||||
|
||||
可以注意到脚本的主体部分(其余是模版代码,请勿修改)是:
|
||||
|
||||
```python
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table( # CREATE TABLE
|
||||
"weather_weather", # weather_weather
|
||||
sa.Column("location", sa.String(), nullable=False), # location VARCHAR NOT NULL,
|
||||
sa.Column("weather", sa.String(), nullable=False), # weather VARCHAR NOT NULL,
|
||||
sa.PrimaryKeyConstraint("location", name=op.f("pk_weather_weather")), # CONSTRAINT pk_weather_weather PRIMARY KEY (location)
|
||||
info={"bind_key": "weather"},
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
```
|
||||
|
||||
```python
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("weather_weather") # DROP TABLE weather_weather;
|
||||
# ### end Alembic commands ###
|
||||
```
|
||||
|
||||
虽然我们不是很懂这些代码的意思,但是可以注意到它们几乎与 SQL 语句 (DDL) 一一对应。
|
||||
显然,它们是用来创建和删除表的。
|
||||
|
||||
我们还可以注意到,`upgrade()` 和 `downgrade()` 函数中的代码是**互逆**的。
|
||||
也就是说,执行一次 `upgrade()` 函数,再执行一次 `downgrade()` 函数后,数据库的模式就会回到原来的状态。
|
||||
|
||||
这就是迁移脚本的作用:记录数据库模式的变化,以便我们在不同的环境中(例如开发环境和生产环境)**可复现地**、**可逆地**同步数据库模式,正如 git 对我们的代码做的事情那样。
|
||||
|
||||
对了,不要忘记还有一段注释:`commands auto generated by Alembic - please adjust!`。
|
||||
它在提醒我们,这些代码是由 Alembic 自动生成的,我们应该检查它们,并且根据需要进行调整。
|
||||
|
||||
:::caution 注意
|
||||
迁移脚本冗长且繁琐,我们一般不会手写它们,而是由 Alembic 自动生成。
|
||||
一般情况下,Alembic 足够智能,可以正确地生成迁移脚本。
|
||||
但是,在复杂或有歧义的情况下,我们可能需要手动调整迁移脚本。
|
||||
所以,**永远要检查迁移脚本,并且在开发环境中测试!**
|
||||
|
||||
**迁移脚本中任何一处错误都足以使数据付之东流!**
|
||||
:::
|
||||
|
||||
确定迁移脚本正确后,我们就可以执行迁移脚本,将数据库模式同步到数据库中:
|
||||
|
||||
```shell
|
||||
nb orm upgrade
|
||||
```
|
||||
|
||||
现在,我们可以正常启动机器人了。
|
||||
|
||||
开发过程中,我们可能会频繁地修改模型,这意味着我们需要频繁地创建并执行迁移脚本,非常繁琐。
|
||||
实际上,此时我们不在乎数据安全,只需要数据库模式与模型定义一致即可。
|
||||
所以,我们可以关闭 `nonebot-plugin-orm` 的启动检查:
|
||||
|
||||
```shell title=.env.dev
|
||||
ALEMBIC_STARTUP_CHECK=false
|
||||
```
|
||||
|
||||
现在,每次启动机器人时,数据库模式会自动与模型定义同步,无需手动迁移。
|
||||
|
||||
### 会话管理
|
||||
|
||||
我们已经成功定义了模型,并且迁移了数据库,现在可以开始使用数据库了……吗?
|
||||
并不能,因为模型只是数据结构的定义,并不能通过它操作数据(如果你曾经使用过 [Tortoise ORM](https://tortoise.github.io/),可能会知道 `await Weather.get(location="上海")` 这样的面向对象编程。
|
||||
但是 SQLAlchemy 不同,选择了命令式编程)。
|
||||
我们需要使用**会话**操作数据:
|
||||
|
||||
```python title=weather/__init__.py {10,13} showLineNumbers
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot_plugin_orm import async_scoped_session
|
||||
|
||||
weather = on_command("天气")
|
||||
|
||||
|
||||
@weather.handle()
|
||||
async def _(session: async_scoped_session, args: Message = CommandArg()):
|
||||
location = args.extract_plain_text()
|
||||
|
||||
if wea := await session.get(Weather, location):
|
||||
await weather.finish(f"今天{location}的天气是{wea.weather}")
|
||||
|
||||
await weather.finish(f"未查询到{location}的天气")
|
||||
```
|
||||
|
||||
我们通过 `session: async_scoped_session` 依赖注入获得了一个会话,然后使用 `await session.get(Weather, location)` 查询数据库。
|
||||
`async_scoped_session` 是一个有作用域限制的会话,作用域为当前事件、当前事件响应器。
|
||||
会话产生的模型实例(例如此处的 `wea := await session.get(Weather, location)`)作用域与会话相同。
|
||||
|
||||
:::caution 注意
|
||||
此处提到的“会话”指的是 ORM 会话,而非 [NoneBot 会话](../../../appendices/session-control),两者的生命周期也是不同的(NoneBot 会话的生命周期中可能包含多个事件,不同的事件也会有不同的事件响应器)。
|
||||
具体而言,就是不要将 ORM 会话和模型实例存储在 NoneBot 会话状态中:
|
||||
|
||||
```python {12}
|
||||
from nonebot.params import ArgPlainText
|
||||
from nonebot.typing import T_State
|
||||
|
||||
|
||||
@weather.got("location", prompt="请输入地名")
|
||||
async def _(state: T_State, session: async_scoped_session, location: str = ArgPlainText()):
|
||||
wea = await session.get(Weather, location)
|
||||
|
||||
if not wea:
|
||||
await weather.finish(f"未查询到{location}的天气")
|
||||
|
||||
state["weather"] = wea # 不要这么做,除非你知道自己在做什么
|
||||
```
|
||||
|
||||
当然非要这么做也不是不可以:
|
||||
|
||||
```python {6}
|
||||
@weather.handle()
|
||||
async def _(state: T_State, session: async_scoped_session):
|
||||
# 通过 await session.merge(state["weather"]) 获得了此 ORM 会话中的相应模型实例,
|
||||
# 而非直接使用会话状态中的模型实例,
|
||||
# 因为先前的 ORM 会话已经关闭了。
|
||||
wea = await session.merge(state["weather"])
|
||||
await weather.finish(f"今天{state['location']}的天气是{wea.weather}")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
当有数据更改时,我们需要提交事务,也要注意会话作用域问题:
|
||||
|
||||
```python title=weather/__init__.py {12,20} showLineNumbers
|
||||
from nonebot.params import Depends
|
||||
|
||||
|
||||
async def get_weather(
|
||||
session: async_scoped_session, args: Message = CommandArg()
|
||||
) -> Weather:
|
||||
location = args.extract_plain_text()
|
||||
|
||||
if not (wea := await session.get(Weather, location)):
|
||||
wea = Weather(location=location, weather="未知")
|
||||
session.add(wea)
|
||||
# await session.commit() # 不应该在其他地方提交事务
|
||||
|
||||
return wea
|
||||
|
||||
|
||||
@weather.handle()
|
||||
async def _(session: async_scoped_session, wea: Weather = Depends(get_weather)):
|
||||
await weather.send(f"今天的天气是{wea.weather}")
|
||||
await session.commit() # 而应该在事件响应器结束前提交事务
|
||||
```
|
||||
|
||||
当然我们也可以获得一个新的会话,不过此时就要手动管理会话了:
|
||||
|
||||
```python title=weather/__init__.py {5-6} showLineNumbers
|
||||
from nonebot_plugin_orm import get_session
|
||||
|
||||
|
||||
async def get_weather(location: str) -> str:
|
||||
session = get_session()
|
||||
async with session.begin():
|
||||
wea = await session.get(Weather, location)
|
||||
|
||||
if not wea:
|
||||
wea = Weather(location=location, weather="未知")
|
||||
session.add(wea)
|
||||
|
||||
return wea.weather
|
||||
|
||||
|
||||
@weather.handle()
|
||||
async def _(args: Message = CommandArg()):
|
||||
wea = await get_weather(args.extract_plain_text())
|
||||
await weather.send(f"今天的天气是{wea}")
|
||||
```
|
||||
|
||||
### 依赖注入
|
||||
|
||||
在上面的示例中,我们都是通过会话获得数据的。
|
||||
不过,我们也可以通过依赖注入获得数据:
|
||||
|
||||
```python title=weather/__init__.py {12-14} showLineNumbers
|
||||
from sqlalchemy import select
|
||||
from nonebot.params import Depends
|
||||
from nonebot_plugin_orm import SQLDepends
|
||||
|
||||
|
||||
def extract_arg_plain_text(args: Message = CommandArg()) -> str:
|
||||
return args.extract_plain_text()
|
||||
|
||||
|
||||
@weather.handle()
|
||||
async def _(
|
||||
wea: Weather = SQLDepends(
|
||||
select(Weather).where(Weather.location == Depends(extract_arg_plain_text))
|
||||
),
|
||||
):
|
||||
await weather.send(f"今天的天气是{wea.weather}")
|
||||
```
|
||||
|
||||
其中,`SQLDepends` 是一个特殊的依赖注入,它会根据类型标注和 SQL 语句提供数据,SQL 语句中也可以有子依赖。
|
||||
|
||||
不同的类型标注也会获得不同形式的数据:
|
||||
|
||||
```python title=weather/__init__.py {5} showLineNumbers
|
||||
from collections.abc import Sequence
|
||||
|
||||
@weather.handle()
|
||||
async def _(
|
||||
weas: Sequence[Weather] = SQLDepends(
|
||||
select(Weather).where(Weather.weather == Depends(extract_arg_plain_text))
|
||||
),
|
||||
):
|
||||
await weather.send(f"今天的天气是{weas[0].weather}的城市有{','.join(wea.location for wea in weas)}")
|
||||
```
|
||||
|
||||
支持的类型标注请参见 [依赖注入](dependency)。
|
||||
|
||||
我们也可以像 [类作为依赖](../../../advanced/dependency#类作为依赖) 那样,在类属性中声明子依赖:
|
||||
|
||||
```python title=weather/__init__.py {5-6,10} showLineNumbers
|
||||
from collections.abc import Sequence
|
||||
|
||||
class Weather(Model):
|
||||
location: Mapped[str] = mapped_column(primary_key=True)
|
||||
weather: Mapped[str] = Depends(extract_arg_plain_text)
|
||||
# weather: Annotated[Mapped[str], Depends(extract_arg_plain_text)] # Annotated 支持
|
||||
|
||||
|
||||
@weather.handle()
|
||||
async def _(weas: Sequence[Weather]):
|
||||
await weather.send(
|
||||
f"今天的天气是{weas[0].weather}的城市有{','.join(wea.location for wea in weas)}"
|
||||
)
|
||||
```
|
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "开发者指南",
|
||||
"position": 3
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user