Compare commits

...

76 Commits

Author SHA1 Message Date
noneflow[bot]
35cee22cf6 🔖 Release 2.2.0 2024-02-09 08:21:37 +00:00
Ju4tCode
fbb55228f2 🔖 bump version 2.2.0 (#2569) 2024-02-09 16:14:15 +08:00
noneflow[bot]
391ac00d81 📝 Update changelog 2024-02-09 06:49:39 +00:00
lengmianzz
277b744ca3 📝 Docs: 更新 Alconna 文档 (#2568)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-02-09 14:48:48 +08:00
noneflow[bot]
a89c67a50e 📝 Update changelog 2024-02-07 08:00:35 +00:00
Ju4tCode
26b30a7b22 📝 Docs: 添加产品赞助列表 (#2566) 2024-02-07 15:59:36 +08:00
pre-commit-ci[bot]
4dae23d3bb ⬆️ auto update by pre-commit hooks (#2565)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2024-02-06 12:48:23 +08:00
noneflow[bot]
07e6c3f977 📝 Update changelog 2024-02-05 06:01:56 +00:00
Ju4tCode
dace63d9d2 Feature: 添加插件 Pydantic 相关使用方法 (#2563) 2024-02-05 14:00:49 +08:00
dependabot[bot]
2ebf956599 ⬆️ Bump the actions group with 1 update (#2564)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 13:35:34 +08:00
noneflow[bot]
b20793c67a 📝 Update changelog 2024-02-04 05:40:22 +00:00
velor2012
47e9f59cc8 🍻 publish plugin 定时提醒 (#2557) 2024-02-04 05:39:12 +00:00
noneflow[bot]
e27cac7fef 📝 Update changelog 2024-02-04 02:16:43 +00:00
Johnny Hsieh
5bfda6e2bc ✏️ Plugin: 移除不再维护的几款插件 (#2561) 2024-02-04 10:15:27 +08:00
noneflow[bot]
ef2ab7df48 📝 Update changelog 2024-02-02 02:48:11 +00:00
bingqiu456
ac1d9147d2 🍻 publish plugin 黑名单插件 (#2553) 2024-02-02 02:47:02 +00:00
dependabot[bot]
f2350909d2 ⬆️ Bump the actions group with 1 update (#2560)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2024-02-01 13:59:22 +08:00
noneflow[bot]
f14ef93808 📝 Update changelog 2024-02-01 02:42:50 +00:00
nek0us
45bd4252bf 🍻 publish plugin ChatGPT 聊天 (#2555) 2024-02-01 02:41:43 +00:00
noneflow[bot]
6b4456bf0e 📝 Update changelog 2024-02-01 02:30:11 +00:00
StarHeart
c5e114dc7f 🐛 Docs: 修复表单标签状态更新 (#2558) 2024-02-01 10:29:03 +08:00
noneflow[bot]
30ceea4287 📝 Update changelog 2024-01-29 02:40:32 +00:00
lengmianzz
380f9ff013 🍻 publish plugin BA模拟抽卡 (#2549) 2024-01-29 02:39:21 +00:00
noneflow[bot]
19ac119714 📝 Update changelog 2024-01-27 09:40:31 +00:00
HuParry
236f70183c 🍻 publish plugin 随机发送图片 (#2547) 2024-01-27 09:39:24 +00:00
noneflow[bot]
117bc35653 📝 Update changelog 2024-01-27 02:56:19 +00:00
eya46
4fcaa8d3d6 🍻 publish plugin 哪吒监控插件 (#2551) 2024-01-27 02:55:17 +00:00
noneflow[bot]
536889d3df 📝 Update changelog 2024-01-26 03:14:04 +00:00
Ju4tCode
bbd13c04cc Feature: 兼容 Pydantic v2 (#2544)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-01-26 11:12:57 +08:00
noneflow[bot]
82e4ccb227 📝 Update changelog 2024-01-24 07:25:25 +00:00
StarHeart
626cfa474f 👷 CI: 更新 prettier 配置 (#2546)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-01-24 15:24:21 +08:00
noneflow[bot]
18e9a9afd3 📝 Update changelog 2024-01-21 11:49:59 +00:00
pre-commit-ci[bot]
41b7d5a3a0 🚨 auto fix by pre-commit hooks 2024-01-21 11:48:50 +00:00
eya46
16fcd4c639 🍻 publish plugin SakuraFrp (#2542) 2024-01-21 11:48:50 +00:00
noneflow[bot]
ef3641efa6 📝 Update changelog 2024-01-21 11:32:33 +00:00
boxie123
8d95a32672 🍻 publish plugin haruka_bot_red (#2540) 2024-01-21 11:31:24 +00:00
dependabot[bot]
3a3a718779 ⬆️ Bump the actions group with 2 updates (#2539)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-18 13:58:14 +08:00
noneflow[bot]
3d1955211a 📝 Update changelog 2024-01-17 08:48:23 +00:00
zhaomaoniu
8d87715d6f 🍻 publish plugin nonebot-plugin-gemini (#2526) 2024-01-17 08:47:26 +00:00
noneflow[bot]
3c535b8e99 📝 Update changelog 2024-01-17 08:40:30 +00:00
Ju4tCode
2c6affecea 🐛 Fix: websockets 驱动器连接关闭 code 获取错误 (#2537) 2024-01-17 16:39:35 +08:00
noneflow[bot]
c2d2169a9f 📝 Update changelog 2024-01-15 05:25:48 +00:00
Ju4tCode
1153c5ff17 Feature: 使用自定义配置加载替代 pydantic-settings (#2521)
Co-authored-by: uy/sun <hmy0119@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-01-15 13:24:55 +08:00
noneflow[bot]
6c532f5926 📝 Update changelog 2024-01-15 03:52:20 +00:00
Perseus037
7083394bc9 🍻 publish plugin 最终台词 (#2522) 2024-01-15 03:51:29 +00:00
noneflow[bot]
7c58410868 📝 Update changelog 2024-01-15 02:50:11 +00:00
pk5ls20
00c3e3b713 🍻 publish plugin nonebot-plugin-nekoimage (#2532) 2024-01-15 02:49:14 +00:00
noneflow[bot]
9d4a72766d 📝 Update changelog 2024-01-15 02:38:31 +00:00
Alpaca4610
82e16b4438 🍻 publish plugin 谷歌Bard聊天 (#2528) 2024-01-15 02:37:37 +00:00
noneflow[bot]
56353f2d0a 📝 Update changelog 2024-01-15 02:31:11 +00:00
tianyisama
4d0eb94a6f 🍻 publish plugin nonebot-plugin-mypower (#2530) 2024-01-15 02:30:14 +00:00
noneflow[bot]
e1a494ecbd 📝 Update changelog 2024-01-14 06:50:02 +00:00
Ju4tCode
6b1e34da63 🐛 Fix: 修复 echo 发送空消息 (#2525) 2024-01-14 14:49:05 +08:00
noneflow[bot]
ccf9597102 📝 Update changelog 2024-01-11 03:53:02 +00:00
Bryan不可思议
5a6f4b9e1c Feature: 带参数的 RegexStr() (#2499) 2024-01-11 11:52:07 +08:00
noneflow[bot]
9b09b42f97 📝 Update changelog 2024-01-10 14:48:29 +00:00
Ju4tCode
854345e16f 📝 Docs: 添加 CITATION 文件 (#2520) 2024-01-10 22:47:27 +08:00
noneflow[bot]
e0ee865b87 📝 Update changelog 2024-01-10 02:34:49 +00:00
Pasumao
dad0c01335 🍻 publish plugin 文心一言4适配 (#2515) 2024-01-10 02:33:56 +00:00
noneflow[bot]
79ef5af19b 📝 Update changelog 2024-01-10 02:17:24 +00:00
lgc2333
b349959f93 🍻 publish plugin 最佳平替 (#2518) 2024-01-10 02:16:11 +00:00
noneflow[bot]
2e7f9612af 📝 Update changelog 2024-01-08 03:11:55 +00:00
wlm3201
8ff2303b22 🍻 publish plugin 随机MC图 (#2511) 2024-01-08 03:10:43 +00:00
noneflow[bot]
b681fdd6d6 📝 Update changelog 2024-01-04 03:12:42 +00:00
Johnny Hsieh
b65b3b438c 🐛 Fix: MessageTemplate 禁止访问私有属性 (#2509)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-01-04 11:11:37 +08:00
pre-commit-ci[bot]
580d6bab36 ⬆️ auto update by pre-commit hooks (#2510)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-01-02 11:01:26 +08:00
noneflow[bot]
90349ddd7d 📝 Update changelog 2023-12-29 08:46:49 +00:00
Perseus037
dcac421bc0 🍻 publish plugin nonebot_plugin_nikke (#2507) 2023-12-29 08:45:51 +00:00
noneflow[bot]
b4f643577f 📝 Update changelog 2023-12-29 08:25:46 +00:00
phquathi
411e7168b3 🍻 publish plugin nonebot-plugin-imagemaster (#2503) 2023-12-29 08:24:39 +00:00
noneflow[bot]
fef072a62a 📝 Update changelog 2023-12-28 08:19:24 +00:00
RF-Tar-Railt
f529e9cb23 🍻 publish plugin Waiter 插件 (#2505) 2023-12-28 08:18:34 +00:00
noneflow[bot]
cfa3bfd88c 📝 Update changelog 2023-12-28 06:51:55 +00:00
student_2333
321c99f12b ✏️ Plugin: 恢复删除的插件 nonebot-plugin-eitherchoice (#2502) 2023-12-28 14:50:54 +08:00
noneflow[bot]
73ad4992ee 📝 Update changelog 2023-12-27 02:09:54 +00:00
phquathi
ddbf37c1be 🍻 publish plugin AntiMonkey (#2500) 2023-12-27 02:08:54 +00:00
267 changed files with 10832 additions and 19568 deletions

View File

@@ -4,7 +4,7 @@
"features": {
"ghcr.io/devcontainers-contrib/features/poetry:2": {}
},
"postCreateCommand": "poetry config virtualenvs.in-project true && poetry install -E all && poetry run pre-commit install && yarn install",
"postCreateCommand": "./scripts/setup-envs.sh",
"customizations": {
"vscode": {
"settings": {

View File

@@ -6,6 +6,14 @@ inputs:
description: Python version
required: false
default: "3.10"
env-dir:
description: Environment directory
required: false
default: "."
no-root:
description: Do not install package in the environment
required: false
default: "false"
runs:
using: "composite"
@@ -19,6 +27,15 @@ runs:
python-version: ${{ inputs.python-version }}
architecture: "x64"
cache: "poetry"
cache-dependency-path: |
./poetry.lock
${{ inputs.env-dir }}/poetry.lock
- run: poetry install -E all
- run: |
cd ${{ inputs.env-dir }}
if [ "${{ inputs.no-root }}" = "true" ]; then
poetry install --all-extras --no-root
else
poetry install --all-extras
fi
shell: bash

View File

@@ -6,6 +6,7 @@ on:
- master
pull_request:
paths:
- "envs/**"
- "nonebot/**"
- "packages/**"
- "tests/**"
@@ -19,16 +20,18 @@ jobs:
name: Test Coverage
runs-on: ${{ matrix.os }}
concurrency:
group: test-coverage-${{ github.ref }}-${{ matrix.os }}-${{ matrix.python-version }}
group: test-coverage-${{ github.ref }}-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.env }}
cancel-in-progress: true
strategy:
matrix:
python-version: ["3.8", "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 }}
PYDANTIC_VERSION: ${{ matrix.env }}
steps:
- uses: actions/checkout@v4
@@ -37,15 +40,19 @@ jobs:
uses: ./.github/actions/setup-python
with:
python-version: ${{ matrix.python-version }}
env-dir: ./envs/${{ matrix.env }}
no-root: true
- name: Run Pytest
run: |
cd tests/
poetry run pytest -n auto --cov-report xml
cd ./envs/${{ matrix.env }}
poetry run bash "../../scripts/run-tests.sh"
- name: Upload coverage report
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
env_vars: OS,PYTHON_VERSION
env_vars: OS,PYTHON_VERSION,PYDANTIC_VERSION
files: ./tests/coverage.xml
flags: unittests
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -73,7 +73,7 @@ jobs:
token: ${{ steps.generate-token.outputs.token }}
- name: Cache pre-commit hooks
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: .cache/.pre-commit
key: noneflow-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}

View File

@@ -6,21 +6,42 @@ on:
- master
pull_request:
paths:
- "envs/**"
- "nonebot/**"
- "packages/**"
- "tests/**"
- ".github/actions/setup-python/**"
- ".github/workflows/pyright.yml"
- "pyproject.toml"
- "poetry.lock"
jobs:
pyright:
name: Pyright Lint
runs-on: ubuntu-latest
concurrency:
group: pyright-${{ github.ref }}-${{ matrix.env }}
cancel-in-progress: true
strategy:
matrix:
env: [pydantic-v1, pydantic-v2]
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Setup Python environment
uses: ./.github/actions/setup-python
with:
env-dir: ./envs/${{ matrix.env }}
no-root: true
- run: echo "$(poetry env info --path)/bin" >> $GITHUB_PATH
- run: |
(cd ./envs/${{ matrix.env }} && echo "$(poetry env info --path)/bin" >> $GITHUB_PATH)
if [ "${{ matrix.env }}" = "pydantic-v1" ]; then
sed -i 's/PYDANTIC_V2 = true/PYDANTIC_V2 = false/g' ./pyproject.toml
fi
shell: bash
- name: Run Pyright
uses: jakebailey/pyright-action@v1
uses: jakebailey/pyright-action@v2

View File

@@ -32,7 +32,7 @@ jobs:
- name: Setup Node Environment
uses: ./.github/actions/setup-node
- uses: release-drafter/release-drafter@v5
- uses: release-drafter/release-drafter@v6
id: release-drafter
env:
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
@@ -92,7 +92,7 @@ jobs:
if: steps.version.outputs.VERSION != steps.version.outputs.TAG_VERSION
run: exit 1
- uses: release-drafter/release-drafter@v5
- uses: release-drafter/release-drafter@v6
with:
name: Release ${{ steps.version.outputs.TAG_NAME }} 🌈
tag: ${{ steps.version.outputs.TAG_NAME }}

View File

@@ -6,14 +6,23 @@ on:
- master
pull_request:
paths:
- "envs/**"
- "nonebot/**"
- "packages/**"
- "tests/**"
- ".github/actions/setup-python/**"
- ".github/workflows/ruff.yml"
- "pyproject.toml"
- "poetry.lock"
jobs:
ruff:
name: Ruff Lint
runs-on: ubuntu-latest
concurrency:
group: pyright-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v4

View File

@@ -7,26 +7,26 @@ ci:
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.6
rev: v0.2.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
stages: [commit]
- repo: https://github.com/pycqa/isort
rev: 5.12.0
rev: 5.13.2
hooks:
- id: isort
stages: [commit]
- repo: https://github.com/psf/black
rev: 23.11.0
rev: 24.1.1
hooks:
- id: black
stages: [commit]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.3
rev: v4.0.0-alpha.8
hooks:
- id: prettier
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]

View File

@@ -5,5 +5,17 @@
"arrowParens": "always",
"singleQuote": false,
"trailingComma": "es5",
"semi": true
"semi": true,
"overrides": [
{
"files": [
"**/devcontainer.json",
"**/tsconfig.json",
"**/tsconfig.*.json"
],
"options": {
"parser": "json"
}
}
]
}

26
CITATION.cff Normal file
View File

@@ -0,0 +1,26 @@
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
title: NoneBot
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- given-names: Yongyu
family-names: Yan
email: yyy@nonebot.dev
- name: NoneBot Team
email: contact@nonebot.dev
website: 'https://github.com/nonebot'
repository-code: 'https://github.com/nonebot/nonebot2'
url: 'https://nonebot.dev/'
abstract: >-
NoneBot, an asynchronous multi-platform chatbot framework
written in Python
keywords:
- nonebot
- chatbot
- pydantic
license: MIT

View File

@@ -94,7 +94,7 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
<p align="center">
<a href="https://asciinema.org/a/569440">
<img src="https://nonebot.dev/img/setup.svg">
<img src="https://nonebot.dev/img/setup.svg" alt="setup" >
</a>
</p>
@@ -232,10 +232,52 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
### 赞助者
感谢以下产品对 NoneBot 项目提供的赞助:
<p align="center">
<a href="https://github.com/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://assets.nonebot.dev/github-dark.png">
<img src="https://assets.nonebot.dev/github-light.png" height="50" alt="GitHub">
</picture>
</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://www.netlify.com/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://assets.nonebot.dev/netlify-dark.svg">
<img src="https://assets.nonebot.dev/netlify-light.svg" height="50" alt="netlify">
</picture>
</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://sentry.io/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://assets.nonebot.dev/sentry-dark.svg">
<img src="https://assets.nonebot.dev/sentry-light.svg" height="50" alt="sentry">
</picture>
</a>
</p>
<p align="center">
<a href="https://www.docker.com/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://assets.nonebot.dev/docker-dark.svg">
<img src="https://assets.nonebot.dev/docker-light.svg" height="50" alt="docker">
</picture>
</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://www.algolia.com/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://assets.nonebot.dev/algolia-dark.svg">
<img src="https://assets.nonebot.dev/algolia-light.svg" height="50" alt="algolia">
</picture>
</a>
</p>
<p align="center">
<a href="https://www.jetbrains.com/">
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg" height="80" alt="JetBrains" >
</a>
</p>
感谢以下赞助者对 NoneBot 项目提供的资金支持:
<a href="https://assets.nonebot.dev/sponsors.svg">
<img src='https://assets.nonebot.dev/sponsors.svg'/>
<img src="https://assets.nonebot.dev/sponsors.svg" alt="sponsors" />
</a>
### 开发者
@@ -243,5 +285,5 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
感谢以下开发者对 NoneBot2 作出的贡献:
<a href="https://github.com/nonebot/nonebot2/graphs/contributors">
<img src="https://contrib.rocks/image?repo=nonebot/nonebot2&max=1000" />
<img src="https://contrib.rocks/image?repo=nonebot/nonebot2&max=1000" alt="contributors" />
</a>

View File

@@ -512,30 +512,6 @@
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_gocqhttp",
"project_link": "nonebot-plugin-gocqhttp",
"author": "mnixry",
"tags": [
{
"label": "gocqhttp",
"color": "#eabd52"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_guild_patch",
"project_link": "nonebot-plugin-guild-patch",
"author": "mnixry",
"tags": [
{
"label": "gocqhttp",
"color": "#a2ea52"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_boardgame",
"project_link": "nonebot-plugin-boardgame",
@@ -4227,6 +4203,13 @@
"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",
@@ -5315,5 +5298,207 @@
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_antimonkey",
"project_link": "nonebot-plugin-antimonkey",
"author": "phquathi",
"tags": [
{
"label": "猴子",
"color": "#ea5252"
},
{
"label": "群管",
"color": "#ea5252"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_waiter",
"project_link": "nonebot-plugin-waiter",
"author": "RF-Tar-Railt",
"tags": [
{
"label": "waiter",
"color": "#ea5252"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_imagemaster",
"project_link": "nonebot-plugin-imagemaster",
"author": "phquathi",
"tags": [
{
"label": "修图",
"color": "#52d4ea"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_nikke",
"project_link": "nonebot-plugin-nikke",
"author": "Perseus037",
"tags": [
{
"label": "nikke",
"color": "#e111b7"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_mcpic",
"project_link": "nonebot-plugin-mcpic",
"author": "wlm3201",
"tags": [
{
"label": "Minecraft",
"color": "#3cb371"
},
{
"label": "MC",
"color": "#3cb371"
},
{
"label": "图片",
"color": "#3cb371"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_pingti",
"project_link": "nonebot-plugin-pingti",
"author": "lgc2333",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_wx4",
"project_link": "nonebot-plugin-wx4",
"author": "Pasumao",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_mypower",
"project_link": "nonebot-plugin-mypower",
"author": "tianyisama",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_bard",
"project_link": "nonebot-plugin-bard",
"author": "Alpaca4610",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_nekoimage",
"project_link": "nonebot-plugin-nekoimage",
"author": "pk5ls20",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_finallines",
"project_link": "nonebot-plugin-finallines",
"author": "Perseus037",
"tags": [
{
"label": "最终台词",
"color": "#052199"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_gemini",
"project_link": "nonebot-plugin-gemini",
"author": "zhaomaoniu",
"tags": [
{
"label": "Gemini",
"color": "#3e8ffb"
}
],
"is_official": false
},
{
"module_name": "haruka_bot_red",
"project_link": "haruka_bot_red",
"author": "boxie123",
"tags": [
{
"label": "bilibili",
"color": "#c83f3f"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_enatfrp",
"project_link": "nonebot-plugin-enatfrp",
"author": "eya46",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_nezha",
"project_link": "nonebot-plugin-nezha",
"author": "eya46",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_randpic",
"project_link": "nonebot-plugin-randpic",
"author": "HuParry",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_BAdrawcard",
"project_link": "nonebot-plugin-badrawcard",
"author": "lengmianzz",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_gpt",
"project_link": "nonebot-plugin-gpt",
"author": "nek0us",
"tags": [
{
"label": "ChatGPT",
"color": "#50ec9d"
}
],
"is_official": false
},
{
"module_name": "nonebot_plugin_easy_blacklist",
"project_link": "nonebot-plugin-easy-blacklist",
"author": "bingqiu456",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot_plugin_reminder",
"project_link": "nonebot-plugin-reminder",
"author": "velor2012",
"tags": [
{
"label": "scheduler",
"color": "#ea5252"
}
],
"is_official": false
}
]

2166
envs/pydantic-v1/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
[tool.poetry]
name = "nonebot-pydantic-v1"
version = "0.1.0"
description = "Private pydantic v1 test env for nonebot"
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
[tool.poetry.dependencies]
python = "^3.8"
[tool.poetry.group.dev.dependencies]
pydantic = "^1.0.0"
nonebot-test = { path = "../test/", develop = false }
nonebot2 = { path = "../../", extras = ["all"], develop = true }
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

2238
envs/pydantic-v2/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
[tool.poetry]
name = "nonebot-pydantic-v2"
version = "0.1.0"
description = "Private pydantic v2 test env for nonebot"
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
[tool.poetry.dependencies]
python = "^3.8"
[tool.poetry.group.dev.dependencies]
pydantic = "^2.0.0"
nonebot-test = { path = "../test/", develop = false }
nonebot2 = { path = "../../", extras = ["all"], develop = true }
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -0,0 +1 @@
# fake file to make project installable

957
envs/test/poetry.lock generated Normal file
View File

@@ -0,0 +1,957 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "asgiref"
version = "3.7.2"
description = "ASGI specs, helper code, and adapters"
optional = false
python-versions = ">=3.7"
files = [
{file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"},
{file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"},
]
[package.dependencies]
typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""}
[package.extras]
tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
[[package]]
name = "async-asgi-testclient"
version = "1.4.11"
description = "Async client for testing ASGI web applications"
optional = false
python-versions = "*"
files = [
{file = "async-asgi-testclient-1.4.11.tar.gz", hash = "sha256:4449ac85d512d661998ec61f91c9ae01851639611d748d81ae7f816736551792"},
]
[package.dependencies]
multidict = ">=4.0,<7.0"
requests = ">=2.21,<3.0"
[[package]]
name = "certifi"
version = "2024.2.2"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
{file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
]
[[package]]
name = "charset-normalizer"
version = "3.3.2"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7.0"
files = [
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
{file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
{file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
{file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
{file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
{file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
{file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
{file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
{file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
{file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
{file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
]
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "coverage"
version = "7.4.1"
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"},
]
[package.dependencies]
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
[package.extras]
toml = ["tomli"]
[[package]]
name = "coverage-conditional-plugin"
version = "0.9.0"
description = "Conditional coverage based on any rules you define!"
optional = false
python-versions = ">=3.7,<4.0"
files = [
{file = "coverage_conditional_plugin-0.9.0-py3-none-any.whl", hash = "sha256:1b37bc469019d2ab5b01f5eee453abe1846b3431e64e209720c2a9ec4afb8130"},
{file = "coverage_conditional_plugin-0.9.0.tar.gz", hash = "sha256:6893dab0542695dbd5ea714281dae0dfec8d0e36480ba32d839e9fa7344f8215"},
]
[package.dependencies]
coverage = ">=7,<8"
importlib_metadata = {version = "*", markers = "python_version < \"3.10\""}
packaging = ">=20.4"
[[package]]
name = "exceptiongroup"
version = "1.2.0"
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"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "execnet"
version = "2.0.2"
description = "execnet: rapid multi-Python deployment"
optional = false
python-versions = ">=3.7"
files = [
{file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"},
{file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"},
]
[package.extras]
testing = ["hatch", "pre-commit", "pytest", "tox"]
[[package]]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "idna"
version = "3.6"
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"},
]
[[package]]
name = "importlib-metadata"
version = "7.0.1"
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"},
]
[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"]
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"]
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "loguru"
version = "0.7.2"
description = "Python logging made (stupidly) simple"
optional = false
python-versions = ">=3.5"
files = [
{file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"},
{file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"},
]
[package.dependencies]
colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
[package.extras]
dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"]
[[package]]
name = "markupsafe"
version = "2.1.5"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.7"
files = [
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
{file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"},
{file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"},
{file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"},
{file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"},
{file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"},
{file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"},
{file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"},
{file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"},
{file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"},
{file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"},
{file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"},
{file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"},
{file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"},
{file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"},
{file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"},
{file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"},
{file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"},
{file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"},
{file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"},
{file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"},
{file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"},
{file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"},
{file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"},
{file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"},
{file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"},
{file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"},
{file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"},
{file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"},
{file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"},
{file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"},
{file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"},
{file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"},
{file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"},
{file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"},
{file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"},
{file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"},
{file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"},
{file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"},
{file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"},
{file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"},
{file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"},
{file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"},
{file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"},
{file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"},
{file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"},
{file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"},
{file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"},
{file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"},
{file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
]
[[package]]
name = "multidict"
version = "6.0.5"
description = "multidict implementation"
optional = false
python-versions = ">=3.7"
files = [
{file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"},
{file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"},
{file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"},
{file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"},
{file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"},
{file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"},
{file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"},
{file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"},
{file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"},
{file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"},
{file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"},
{file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"},
{file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"},
{file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"},
{file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"},
{file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"},
{file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"},
{file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"},
{file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"},
{file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"},
{file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"},
{file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"},
{file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"},
{file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"},
{file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"},
{file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"},
{file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"},
{file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"},
{file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"},
{file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"},
{file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"},
{file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"},
{file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"},
{file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"},
{file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"},
{file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"},
{file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"},
{file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"},
{file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"},
{file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"},
{file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"},
{file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"},
{file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"},
{file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"},
{file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"},
{file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"},
{file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"},
{file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"},
{file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"},
{file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"},
{file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"},
{file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"},
{file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"},
{file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"},
{file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"},
{file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"},
{file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"},
{file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"},
{file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"},
{file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"},
{file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"},
{file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"},
{file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"},
{file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"},
{file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"},
{file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"},
{file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"},
{file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"},
{file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"},
{file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"},
{file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"},
{file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"},
{file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"},
{file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"},
{file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"},
{file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"},
{file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"},
{file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"},
{file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"},
{file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"},
]
[[package]]
name = "nonebot2"
version = "2.1.3"
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"},
]
[package.dependencies]
loguru = ">=0.6.0,<1.0.0"
pydantic = {version = ">=1.10.0,<2.0.0", extras = ["dotenv"]}
pygtrie = ">=2.4.1,<3.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"
[package.extras]
aiohttp = ["aiohttp[speedups] (>=3.9.0b0,<4.0.0)"]
all = ["Quart (>=0.18.0,<1.0.0)", "aiohttp[speedups] (>=3.9.0b0,<4.0.0)", "fastapi (>=0.93.0,<1.0.0)", "httpx[http2] (>=0.20.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)", "websockets (>=10.0)"]
fastapi = ["fastapi (>=0.93.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"]
httpx = ["httpx[http2] (>=0.20.0,<1.0.0)"]
quart = ["Quart (>=0.18.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"]
websockets = ["websockets (>=10.0)"]
[[package]]
name = "nonebug"
version = "0.3.5"
description = "nonebot2 test framework"
optional = false
python-versions = ">=3.8,<4.0"
files = [
{file = "nonebug-0.3.5-py3-none-any.whl", hash = "sha256:588831b08b3ea42d058874214bedae646e2ab8c1ec4ae1540ff789873107a8fa"},
{file = "nonebug-0.3.5.tar.gz", hash = "sha256:4d4bf9448cd1cbfaaabaab73dbe4ac8757e86dd92a41ef79cdece8dd61e724e2"},
]
[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"
typing-extensions = ">=4.0.0,<5.0.0"
[[package]]
name = "packaging"
version = "23.2"
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"},
]
[[package]]
name = "pluggy"
version = "1.4.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"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pydantic"
version = "1.10.14"
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"},
]
[package.dependencies]
python-dotenv = {version = ">=0.10.4", optional = true, markers = "extra == \"dotenv\""}
typing-extensions = ">=4.2.0"
[package.extras]
dotenv = ["python-dotenv (>=0.10.4)"]
email = ["email-validator (>=1.0.3)"]
[[package]]
name = "pygtrie"
version = "2.5.0"
description = "A pure Python trie data structure implementation."
optional = false
python-versions = "*"
files = [
{file = "pygtrie-2.5.0-py3-none-any.whl", hash = "sha256:8795cda8105493d5ae159a5bef313ff13156c5d4d72feddefacaad59f8c8ce16"},
{file = "pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2"},
]
[[package]]
name = "pytest"
version = "7.4.4"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
]
[package.dependencies]
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\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-asyncio"
version = "0.23.4"
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"},
]
[package.dependencies]
pytest = ">=7.0.0,<8"
[package.extras]
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
[[package]]
name = "pytest-cov"
version = "4.1.0"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.7"
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"},
]
[package.dependencies]
coverage = {version = ">=5.2.1", extras = ["toml"]}
pytest = ">=4.6"
[package.extras]
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
[[package]]
name = "pytest-xdist"
version = "3.5.0"
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
optional = false
python-versions = ">=3.7"
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"},
]
[package.dependencies]
execnet = ">=1.1"
pytest = ">=6.2.0"
[package.extras]
psutil = ["psutil (>=3.0)"]
setproctitle = ["setproctitle"]
testing = ["filelock"]
[[package]]
name = "python-dotenv"
version = "1.0.1"
description = "Read key-value pairs from a .env file and set them as environment variables"
optional = false
python-versions = ">=3.8"
files = [
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
]
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
name = "requests"
version = "2.31.0"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.7"
files = [
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
]
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "typing-extensions"
version = "4.9.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"},
]
[[package]]
name = "urllib3"
version = "2.2.0"
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"},
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "werkzeug"
version = "3.0.1"
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"},
]
[package.dependencies]
MarkupSafe = ">=2.1.1"
[package.extras]
watchdog = ["watchdog (>=2.3)"]
[[package]]
name = "win32-setctime"
version = "1.1.0"
description = "A small Python utility to set file creation time on Windows"
optional = false
python-versions = ">=3.5"
files = [
{file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
]
[package.extras]
dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[[package]]
name = "wsproto"
version = "1.2.0"
description = "WebSockets state-machine based protocol implementation"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"},
{file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"},
]
[package.dependencies]
h11 = ">=0.9.0,<1"
[[package]]
name = "yarl"
version = "1.9.4"
description = "Yet another URL library"
optional = false
python-versions = ">=3.7"
files = [
{file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"},
{file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"},
{file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"},
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"},
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"},
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"},
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"},
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"},
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"},
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"},
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"},
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"},
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"},
{file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"},
{file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"},
{file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"},
{file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"},
{file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"},
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"},
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"},
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"},
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"},
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"},
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"},
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"},
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"},
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"},
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"},
{file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"},
{file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"},
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"},
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"},
{file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"},
{file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"},
{file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"},
{file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"},
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"},
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"},
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"},
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"},
{file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"},
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"},
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"},
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"},
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"},
{file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"},
{file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"},
{file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"},
{file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"},
{file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"},
{file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"},
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"},
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"},
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"},
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"},
{file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"},
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"},
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"},
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"},
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"},
{file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"},
{file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"},
{file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"},
{file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"},
{file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"},
{file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"},
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"},
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"},
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"},
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"},
{file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"},
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"},
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"},
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"},
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"},
{file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"},
{file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"},
{file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"},
{file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"},
{file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"},
]
[package.dependencies]
idna = ">=2.0"
multidict = ">=4.0"
[[package]]
name = "zipp"
version = "3.17.0"
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"},
]
[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"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "ab5729309587cb130ac7848e8862368995372cf2fc91d0966598b3c6b49028e5"

21
envs/test/pyproject.toml Normal file
View File

@@ -0,0 +1,21 @@
[tool.poetry]
name = "nonebot-test"
version = "0.1.0"
description = "Private test env for nonebot"
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
packages = [{ include = "nonebot-test.py" }]
[tool.poetry.dependencies]
python = "^3.8"
nonebug = "^0.3.0"
wsproto = "^1.2.0"
pytest-cov = "^4.0.0"
pytest-xdist = "^3.0.2"
pytest-asyncio = "^0.23.2"
werkzeug = ">=2.3.6,<4.0.0"
coverage-conditional-plugin = "^0.9.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -35,6 +35,7 @@
{ref}``get_loaded_plugins` <nonebot.plugin.get_loaded_plugins>`
- `get_available_plugin_names` =>
{ref}``get_available_plugin_names` <nonebot.plugin.get_available_plugin_names>`
- `get_plugin_config` => {ref}``get_plugin_config` <nonebot.plugin.get_plugin_config>`
- `require` => {ref}``require` <nonebot.plugin.load.require>`
FrontMatter:
@@ -47,11 +48,10 @@ from importlib.metadata import version
from typing import Any, Dict, Type, Union, TypeVar, Optional, overload
import loguru
from pydantic.env_settings import DotenvType
from nonebot.config import Env, Config
from nonebot.log import logger as logger
from nonebot.adapters import Bot, Adapter
from nonebot.config import DOTENV_TYPE, Env, Config
from nonebot.utils import escape_tag, resolve_dot_notation
from nonebot.drivers import Driver, ASGIMixin, combine_driver
@@ -273,7 +273,7 @@ def _log_patcher(record: "loguru.Record"):
)
def init(*, _env_file: Optional[DotenvType] = None, **kwargs: Any) -> None:
def init(*, _env_file: Optional[DOTENV_TYPE] = None, **kwargs: Any) -> None:
"""初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。
NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。
@@ -296,9 +296,11 @@ def init(*, _env_file: Optional[DotenvType] = None, **kwargs: Any) -> None:
_env_file = _env_file or f".env.{env.environment}"
config = Config(
**kwargs,
_env_file=(".env", _env_file)
if isinstance(_env_file, (str, os.PathLike))
else _env_file,
_env_file=(
(".env", _env_file)
if isinstance(_env_file, (str, os.PathLike))
else _env_file
),
)
logger.configure(
@@ -353,10 +355,9 @@ from nonebot.plugin import load_from_json as load_from_json
from nonebot.plugin import load_from_toml as load_from_toml
from nonebot.plugin import load_all_plugins as load_all_plugins
from nonebot.plugin import on_shell_command as on_shell_command
from nonebot.plugin import get_plugin_config as get_plugin_config
from nonebot.plugin import get_loaded_plugins as get_loaded_plugins
from nonebot.plugin import load_builtin_plugin as load_builtin_plugin
from nonebot.plugin import load_builtin_plugins as load_builtin_plugins
from nonebot.plugin import get_plugin_by_module_name as get_plugin_by_module_name
from nonebot.plugin import get_available_plugin_names as get_available_plugin_names
__autodoc__ = {"internal": False}

351
nonebot/compat.py Normal file
View File

@@ -0,0 +1,351 @@
"""本模块为 Pydantic 版本兼容层模块
为兼容 Pydantic V1 与 V2 版本,定义了一系列兼容函数与类供使用。
FrontMatter:
sidebar_position: 16
description: nonebot.compat 模块
"""
from dataclasses import dataclass, is_dataclass
from typing_extensions import Self, Annotated, get_args, get_origin, is_typeddict
from typing import (
TYPE_CHECKING,
Any,
Set,
Dict,
List,
Type,
TypeVar,
Callable,
Optional,
Protocol,
Generator,
)
from pydantic import VERSION, BaseModel
from nonebot.typing import origin_is_annotated
T = TypeVar("T")
PYDANTIC_V2 = int(VERSION.split(".", 1)[0]) == 2
if TYPE_CHECKING:
class _CustomValidationClass(Protocol):
@classmethod
def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]: ...
CVC = TypeVar("CVC", bound=_CustomValidationClass)
__all__ = (
"Required",
"PydanticUndefined",
"PydanticUndefinedType",
"ConfigDict",
"DEFAULT_CONFIG",
"FieldInfo",
"ModelField",
"extract_field_info",
"model_field_validate",
"model_fields",
"model_config",
"model_dump",
"type_validate_python",
"custom_validation",
)
__autodoc__ = {
"PydanticUndefined": "Pydantic Undefined object",
"PydanticUndefinedType": "Pydantic Undefined type",
}
if PYDANTIC_V2: # pragma: pydantic-v2
from pydantic_core import CoreSchema, core_schema
from pydantic._internal._repr import display_as_type
from pydantic import TypeAdapter, GetCoreSchemaHandler
from pydantic.fields import FieldInfo as BaseFieldInfo
Required = Ellipsis
"""Alias of Ellipsis for compatibility with pydantic v1"""
# Export undefined type
from pydantic_core import PydanticUndefined as PydanticUndefined
from pydantic_core import PydanticUndefinedType as PydanticUndefinedType
# isort: split
# Export model config dict
from pydantic import ConfigDict as ConfigDict
DEFAULT_CONFIG = ConfigDict(extra="allow", arbitrary_types_allowed=True)
"""Default config for validations"""
class FieldInfo(BaseFieldInfo):
"""FieldInfo class with extra property for compatibility with pydantic v1"""
# make default can be positional argument
def __init__(self, default: Any = PydanticUndefined, **kwargs: Any) -> None:
super().__init__(default=default, **kwargs)
@property
def extra(self) -> Dict[str, Any]:
"""Extra data that is not part of the standard pydantic fields.
For compatibility with pydantic v1.
"""
# extract extra data from attributes set except used slots
# we need to call super in advance due to
# comprehension not inlined in cpython < 3.12
# https://peps.python.org/pep-0709/
slots = super().__slots__
return {k: v for k, v in self._attributes_set.items() if k not in slots}
@dataclass
class ModelField:
"""ModelField class for compatibility with pydantic v1"""
name: str
"""The name of the field."""
annotation: Any
"""The annotation of the field."""
field_info: FieldInfo
"""The FieldInfo of the field."""
@classmethod
def _construct(cls, name: str, annotation: Any, field_info: FieldInfo) -> Self:
return cls(name, annotation, field_info)
@classmethod
def construct(
cls, name: str, annotation: Any, field_info: Optional[FieldInfo] = None
) -> Self:
"""Construct a ModelField from given infos."""
return cls._construct(name, annotation, field_info or FieldInfo())
def _annotation_has_config(self) -> bool:
"""Check if the annotation has config.
TypeAdapter raise error when annotation has config
and given config is not None.
"""
type_is_annotated = origin_is_annotated(get_origin(self.annotation))
inner_type = (
get_args(self.annotation)[0] if type_is_annotated else self.annotation
)
try:
return (
issubclass(inner_type, BaseModel)
or is_dataclass(inner_type)
or is_typeddict(inner_type)
)
except TypeError:
return False
def get_default(self) -> Any:
"""Get the default value of the field."""
return self.field_info.get_default(call_default_factory=True)
def _type_display(self):
"""Get the display of the type of the field."""
return display_as_type(self.annotation)
def __hash__(self) -> int:
# Each ModelField is unique for our purposes,
# to allow store them in a set.
return id(self)
def extract_field_info(field_info: BaseFieldInfo) -> Dict[str, Any]:
"""Get FieldInfo init kwargs from a FieldInfo instance."""
kwargs = field_info._attributes_set.copy()
kwargs["annotation"] = field_info.rebuild_annotation()
return kwargs
def model_field_validate(
model_field: ModelField, value: Any, config: Optional[ConfigDict] = None
) -> Any:
"""Validate the value pass to the field."""
type: Any = Annotated[model_field.annotation, model_field.field_info]
return TypeAdapter(
type, config=None if model_field._annotation_has_config() else config
).validate_python(value)
def model_fields(model: Type[BaseModel]) -> List[ModelField]:
"""Get field list of a model."""
return [
ModelField._construct(
name=name,
annotation=field_info.rebuild_annotation(),
field_info=FieldInfo(**extract_field_info(field_info)),
)
for name, field_info in model.model_fields.items()
]
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)
def type_validate_python(type_: Type[T], data: Any) -> T:
"""Validate data with given type."""
return TypeAdapter(type_).validate_python(data)
def __get_pydantic_core_schema__(
cls: Type["_CustomValidationClass"],
source_type: Any,
handler: GetCoreSchemaHandler,
) -> CoreSchema:
validators = list(cls.__get_validators__())
if len(validators) == 1:
return core_schema.no_info_plain_validator_function(validators[0])
return core_schema.chain_schema(
[core_schema.no_info_plain_validator_function(func) for func in validators]
)
def custom_validation(class_: Type["CVC"]) -> Type["CVC"]:
"""Use pydantic v1 like validator generator in pydantic v2"""
setattr(
class_,
"__get_pydantic_core_schema__",
classmethod(__get_pydantic_core_schema__),
)
return class_
else: # pragma: pydantic-v1
from pydantic import Extra
from pydantic import parse_obj_as
from pydantic import BaseConfig as PydanticConfig
from pydantic.fields import FieldInfo as BaseFieldInfo
from pydantic.fields import ModelField as BaseModelField
from pydantic.schema import get_annotation_from_field_info
# isort: split
from pydantic.fields import Required as Required
# isort: split
from pydantic.fields import Undefined as PydanticUndefined
from pydantic.fields import UndefinedType as PydanticUndefinedType
class ConfigDict(PydanticConfig):
"""Config class that allow get value with default value."""
@classmethod
def get(cls, field: str, default: Any = None) -> Any:
"""Get a config value."""
return getattr(cls, field, default)
class DEFAULT_CONFIG(ConfigDict):
extra = Extra.allow
arbitrary_types_allowed = True
class FieldInfo(BaseFieldInfo):
def __init__(self, default: Any = PydanticUndefined, **kwargs: Any):
# preprocess default value to make it compatible with pydantic v2
# when default is Required, set it to PydanticUndefined
if default is Required:
default = PydanticUndefined
super().__init__(default, **kwargs)
class ModelField(BaseModelField):
@classmethod
def _construct(cls, name: str, annotation: Any, field_info: FieldInfo) -> Self:
return cls(
name=name,
type_=annotation,
class_validators=None,
model_config=DEFAULT_CONFIG,
default=field_info.default,
default_factory=field_info.default_factory,
required=(
field_info.default is PydanticUndefined
and field_info.default_factory is None
),
field_info=field_info,
)
@classmethod
def construct(
cls, name: str, annotation: Any, field_info: Optional[FieldInfo] = None
) -> Self:
"""Construct a ModelField from given infos.
Field annotation is preprocessed with field_info.
"""
if field_info is not None:
annotation = get_annotation_from_field_info(
annotation, field_info, name
)
return cls._construct(name, annotation, field_info or FieldInfo())
def extract_field_info(field_info: BaseFieldInfo) -> Dict[str, Any]:
"""Get FieldInfo init kwargs from a FieldInfo instance."""
kwargs = {
s: getattr(field_info, s) for s in field_info.__slots__ if s != "extra"
}
kwargs.update(field_info.extra)
return kwargs
def model_field_validate(
model_field: ModelField, value: Any, config: Optional[Type[ConfigDict]] = None
) -> Any:
"""Validate the value pass to the field.
Set config before validate to ensure validate correctly.
"""
if model_field.model_config is not config:
model_field.set_config(config or ConfigDict)
v, errs_ = model_field.validate(value, {}, loc=())
if errs_:
raise ValueError(value, model_field)
return v
def model_fields(model: Type[BaseModel]) -> List[ModelField]:
"""Get field list of a model."""
# construct the model field without preprocess to avoid error
return [
ModelField._construct(
name=model_field.name,
annotation=model_field.annotation,
field_info=FieldInfo(
**extract_field_info(model_field.field_info),
),
)
for model_field in model.__fields__.values()
]
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)
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"]:
"""Do nothing in pydantic v1"""
return class_

View File

@@ -12,76 +12,256 @@ FrontMatter:
"""
import os
import abc
import json
from pathlib import Path
from datetime import timedelta
from ipaddress import IPv4Address
from typing import TYPE_CHECKING, Any, Set, Dict, Tuple, Union, Mapping, Optional
from pydantic.utils import deep_update
from pydantic.fields import Undefined, UndefinedType
from pydantic import Extra, Field, BaseSettings, IPvAnyAddress
from pydantic.env_settings import (
DotenvType,
SettingsError,
EnvSettingsSource,
InitSettingsSource,
SettingsSourceCallable,
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
from pydantic.networks import IPvAnyAddress
from nonebot.log import logger
from nonebot.typing import origin_is_union
from nonebot.utils import deep_update, type_is_complex, lenient_issubclass
from nonebot.compat import (
PYDANTIC_V2,
ConfigDict,
ModelField,
PydanticUndefined,
PydanticUndefinedType,
model_config,
model_fields,
)
DOTENV_TYPE: TypeAlias = Union[
Path, str, List[Union[Path, str]], Tuple[Union[Path, str], ...]
]
ENV_FILE_SENTINEL = Path("")
class CustomEnvSettings(EnvSettingsSource):
def __call__(self, settings: BaseSettings) -> Dict[str, Any]:
class SettingsError(ValueError): ...
class BaseSettingsSource(abc.ABC):
def __init__(self, settings_cls: Type["BaseSettings"]) -> None:
self.settings_cls = settings_cls
@property
def config(self) -> "SettingsConfig":
return model_config(self.settings_cls)
@abc.abstractmethod
def __call__(self) -> Dict[str, Any]:
raise NotImplementedError
class InitSettingsSource(BaseSettingsSource):
__slots__ = ("init_kwargs",)
def __init__(
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]:
return self.init_kwargs
def __repr__(self) -> str:
return f"InitSettingsSource(init_kwargs={self.init_kwargs!r})"
class DotEnvSettingsSource(BaseSettingsSource):
def __init__(
self,
settings_cls: Type["BaseSettings"],
env_file: Optional[DOTENV_TYPE] = ENV_FILE_SENTINEL,
env_file_encoding: Optional[str] = None,
case_sensitive: Optional[bool] = None,
env_nested_delimiter: Optional[str] = None,
) -> None:
super().__init__(settings_cls)
self.env_file = (
env_file
if env_file is not ENV_FILE_SENTINEL
else self.config.get("env_file", (".env",))
)
self.env_file_encoding = (
env_file_encoding
if env_file_encoding is not None
else self.config.get("env_file_encoding", "utf-8")
)
self.case_sensitive = (
case_sensitive
if case_sensitive is not None
else self.config.get("case_sensitive", False)
)
self.env_nested_delimiter = (
env_nested_delimiter
if env_nested_delimiter is not None
else self.config.get("env_nested_delimiter", None)
)
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]:
if type_is_complex(field.annotation):
return True, False
elif origin_is_union(get_origin(field.annotation)) and any(
type_is_complex(arg) for arg in get_args(field.annotation)
):
return True, True
return False, False
def _parse_env_vars(
self, env_vars: Mapping[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]]:
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]]:
env_files = self.env_file
if env_files is None:
return {}
if isinstance(env_files, (str, os.PathLike)):
env_files = [env_files]
dotenv_vars: Dict[str, Optional[str]] = {}
for env_file in env_files:
env_path = Path(env_file).expanduser()
if env_path.is_file():
dotenv_vars.update(self._read_env_file(env_path))
return dotenv_vars
def _next_field(
self, field: Optional[ModelField], key: str
) -> Optional[ModelField]:
if not field or origin_is_union(get_origin(field.annotation)):
return None
elif field.annotation and lenient_issubclass(field.annotation, BaseModel):
for field in model_fields(field.annotation):
if field.name == key:
return field
return None
def _explode_env_vars(
self,
field: ModelField,
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] = {}
for env_name, env_val in env_vars.items():
if not env_name.startswith(prefix):
continue
# delete from file vars when used
if env_name in env_file_vars:
del env_file_vars[env_name]
_, *keys, last_key = env_name.split(self.env_nested_delimiter)
env_var = result
target_field: Optional[ModelField] = field
for key in keys:
target_field = self._next_field(target_field, key)
env_var = env_var.setdefault(key, {})
target_field = self._next_field(target_field, last_key)
if target_field and env_val:
is_complex, allow_parse_failure = self._field_is_complex(target_field)
if is_complex:
try:
env_val = json.loads(env_val)
except ValueError as e:
if not allow_parse_failure:
raise SettingsError(
f'error parsing env var "{env_name}"'
) from e
env_var[last_key] = env_val
return result
def __call__(self) -> Dict[str, Any]:
"""从环境变量和 dotenv 配置文件中读取配置项。"""
d: Dict[str, Any] = {}
if settings.__config__.case_sensitive:
env_vars: Mapping[str, Optional[str]] = os.environ # pragma: no cover
else:
env_vars = {k.lower(): v for k, v in os.environ.items()}
env_file_vars = self._read_env_files(settings.__config__.case_sensitive)
env_vars = self._parse_env_vars(os.environ)
env_file_vars = self._read_env_files()
env_vars = {**env_file_vars, **env_vars}
for field in settings.__fields__.values():
env_val: Union[str, None, UndefinedType] = Undefined
for env_name in field.field_info.extra["env_names"]:
env_val = env_vars.get(env_name, Undefined)
if env_name in env_file_vars:
del env_file_vars[env_name]
if env_val is not Undefined:
break
for field in model_fields(self.settings_cls):
field_name = field.name
env_name = self._apply_case_sensitive(field_name)
is_complex, allow_parse_failure = self.field_is_complex(field)
# try get values from env vars
env_val = env_vars.get(env_name, PydanticUndefined)
# delete from file vars when used
if env_name in env_file_vars:
del env_file_vars[env_name]
is_complex, allow_parse_failure = self._field_is_complex(field)
if is_complex:
if isinstance(env_val, UndefinedType):
if isinstance(env_val, PydanticUndefinedType):
# field is complex but no value found so far, try explode_env_vars
if env_val_built := self.explode_env_vars(field, env_vars):
d[field.alias] = env_val_built
if env_val_built := self._explode_env_vars(
field, env_vars, env_file_vars
):
d[field_name] = env_val_built
elif env_val is None:
d[field.alias] = env_val
d[field_name] = env_val
else:
# field is complex and there's a value
# decode that as JSON, then add explode_env_vars
try:
env_val = settings.__config__.parse_env_var(field.name, env_val)
env_val = json.loads(env_val)
except ValueError as e:
if not allow_parse_failure:
raise SettingsError(
f'error parsing env var "{env_name}"' # type: ignore
f'error parsing env var "{env_name}"'
) from e
if isinstance(env_val, dict):
d[field.alias] = deep_update(
env_val, self.explode_env_vars(field, env_vars)
# field value is a dict
# try explode_env_vars to find more sub-values
d[field_name] = deep_update(
env_val,
self._explode_env_vars(field, env_vars, env_file_vars),
)
else:
d[field.alias] = env_val
elif not isinstance(env_val, UndefinedType):
d[field_name] = env_val
elif env_val is not PydanticUndefined:
# simplest case, field is not complex
# we only need to add the value if it was found
d[field.alias] = env_val
d[field_name] = env_val
# remain user custom config
for env_name in env_file_vars:
@@ -89,7 +269,7 @@ class CustomEnvSettings(EnvSettingsSource):
if env_val and (val_striped := env_val.strip()):
# there's a value, decode that as JSON
try:
env_val = settings.__config__.parse_env_var(env_name, val_striped)
env_val = json.loads(val_striped)
except ValueError:
logger.trace(
"Error while parsing JSON for "
@@ -113,38 +293,80 @@ class CustomEnvSettings(EnvSettingsSource):
return d
class BaseConfig(BaseSettings):
if PYDANTIC_V2: # pragma: pydantic-v2
class SettingsConfig(ConfigDict, total=False):
env_file: Optional[DOTENV_TYPE]
env_file_encoding: str
case_sensitive: bool
env_nested_delimiter: Optional[str]
else: # pragma: pydantic-v1
class SettingsConfig(ConfigDict):
env_file: Optional[DOTENV_TYPE]
env_file_encoding: str
case_sensitive: bool
env_nested_delimiter: Optional[str]
class BaseSettings(BaseModel):
if TYPE_CHECKING:
# dummy getattr for pylance checking, actually not used
def __getattr__(self, name: str) -> Any: # pragma: no cover
return self.__dict__.get(name)
class Config:
extra = Extra.allow
env_nested_delimiter = "__"
if PYDANTIC_V2: # pragma: pydantic-v2
model_config: SettingsConfig = SettingsConfig(
extra="allow",
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
env_nested_delimiter="__",
)
else: # pragma: pydantic-v1
@classmethod
def customise_sources(
cls,
init_settings: InitSettingsSource,
env_settings: EnvSettingsSource,
file_secret_settings: SettingsSourceCallable,
) -> Tuple[SettingsSourceCallable, ...]:
common_config = init_settings.init_kwargs.pop("_common_config", {})
return (
init_settings,
CustomEnvSettings(
env_settings.env_file,
env_settings.env_file_encoding,
env_settings.env_nested_delimiter,
env_settings.env_prefix_len,
),
InitSettingsSource(common_config),
file_secret_settings,
class Config(SettingsConfig):
extra = "allow" # type: ignore
env_file = ".env"
env_file_encoding = "utf-8"
case_sensitive = False
env_nested_delimiter = "__"
def __init__(
__settings_self__, # pyright: ignore[reportSelfClsParameterName]
_env_file: Optional[DOTENV_TYPE] = ENV_FILE_SENTINEL,
_env_file_encoding: Optional[str] = None,
_env_nested_delimiter: Optional[str] = None,
**values: Any,
) -> None:
super().__init__(
**__settings_self__._settings_build_values(
values,
env_file=_env_file,
env_file_encoding=_env_file_encoding,
env_nested_delimiter=_env_nested_delimiter,
)
)
def _settings_build_values(
self,
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]:
init_settings = InitSettingsSource(self.__class__, init_kwargs=init_kwargs)
env_settings = DotEnvSettingsSource(
self.__class__,
env_file=env_file,
env_file_encoding=env_file_encoding,
env_nested_delimiter=env_nested_delimiter,
)
return deep_update(env_settings(), init_settings())
class Env(BaseConfig):
class Env(BaseSettings):
"""运行环境配置。大小写不敏感。
将会从 **环境变量** > **dotenv 配置文件** 的优先级读取环境信息。
@@ -156,11 +378,8 @@ class Env(BaseConfig):
NoneBot 将从 `.env.{environment}` 文件中加载配置。
"""
class Config:
env_file = ".env"
class Config(BaseConfig):
class Config(BaseSettings):
"""NoneBot 主要配置。大小写不敏感。
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
@@ -169,7 +388,8 @@ class Config(BaseConfig):
配置方法参考: [配置](https://nonebot.dev/docs/appendices/config)
"""
_env_file: DotenvType = ".env", ".env.prod"
if TYPE_CHECKING:
_env_file: Optional[DOTENV_TYPE] = ".env", ".env.prod"
# nonebot configs
driver: str = "~fastapi"
@@ -254,11 +474,19 @@ class Config(BaseConfig):
# custom configs can be assigned during nonebot.init
# or from env file using json loads
class Config:
env_file = ".env", ".env.prod"
if PYDANTIC_V2: # pragma: pydantic-v2
model_config = SettingsConfig(env_file=(".env", ".env.prod"))
else: # pragma: pydantic-v1
class Config(SettingsConfig):
env_file = ".env", ".env.prod"
__autodoc__ = {
"CustomEnvSettings": False,
"BaseConfig": False,
"SettingsError": False,
"BaseSettingsSource": False,
"InitSettingsSource": False,
"DotEnvSettingsSource": False,
"SettingsConfig": False,
"BaseSettings": False,
}

View File

@@ -24,14 +24,11 @@ from typing import (
cast,
)
from pydantic import BaseConfig
from pydantic.schema import get_annotation_from_field_info
from pydantic.fields import Required, FieldInfo, Undefined, ModelField
from nonebot.log import logger
from nonebot.typing import _DependentCallable
from nonebot.exception import SkippedException
from nonebot.utils import run_sync, is_coroutine_callable
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
from .utils import check_field_type, get_typed_signature
@@ -69,10 +66,6 @@ class Param(abc.ABC, FieldInfo):
return
class CustomConfig(BaseConfig):
arbitrary_types_allowed = True
@dataclass(frozen=True)
class Dependent(Generic[R]):
"""依赖注入容器
@@ -125,12 +118,8 @@ class Dependent(Generic[R]):
params = get_typed_signature(call).parameters.values()
for param in params:
default_value = Required
if param.default != param.empty:
default_value = param.default
if isinstance(default_value, Param):
field_info = default_value
if isinstance(param.default, Param):
field_info = param.default
else:
for allow_type in allow_types:
if field_info := allow_type._check_param(param, allow_types):
@@ -141,25 +130,13 @@ class Dependent(Generic[R]):
f"for function {call} with type {param.annotation}"
)
default_value = field_info.default
annotation: Any = Any
required = default_value == Required
if param.annotation != param.empty:
if param.annotation is not param.empty:
annotation = param.annotation
annotation = get_annotation_from_field_info(
annotation, field_info, param.name
)
fields.append(
ModelField(
name=param.name,
type_=annotation,
class_validators=None,
model_config=CustomConfig,
default=None if required else default_value,
required=required,
field_info=field_info,
ModelField.construct(
name=param.name, annotation=annotation, field_info=field_info
)
)
@@ -207,7 +184,7 @@ class Dependent(Generic[R]):
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 Undefined:
if value is PydanticUndefined:
value = field.get_default()
v = check_field_type(field, value)
return v if param.validate else value

View File

@@ -8,10 +8,10 @@ import inspect
from typing import Any, Dict, Callable, ForwardRef
from loguru import logger
from pydantic.fields import ModelField
from pydantic.typing import evaluate_forwardref
from nonebot.exception import TypeMisMatch
from nonebot.typing import evaluate_forwardref
from nonebot.compat import DEFAULT_CONFIG, ModelField, model_field_validate
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
@@ -50,7 +50,7 @@ def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) ->
def check_field_type(field: ModelField, value: Any) -> Any:
"""检查字段类型是否匹配"""
v, errs_ = field.validate(value, {}, loc=())
if errs_:
try:
return model_field_validate(field, value, DEFAULT_CONFIG)
except ValueError:
raise TypeMisMatch(field, value)
return v

View File

@@ -179,8 +179,7 @@ class WebSocket(BaseWebSocket):
if TYPE_CHECKING:
class Driver(Mixin, NoneDriver):
...
class Driver(Mixin, NoneDriver): ...
else:
Driver = combine_driver(NoneDriver, Mixin)

View File

@@ -15,14 +15,13 @@ FrontMatter:
description: nonebot.drivers.fastapi 模块
"""
import logging
import contextlib
from functools import wraps
from typing_extensions import override
from typing import Any, Dict, List, Tuple, Union, Optional
from pydantic import BaseSettings
from pydantic import BaseModel
from nonebot.config import Env
from nonebot.drivers import ASGIMixin
@@ -59,7 +58,7 @@ def catch_closed(func):
return decorator
class Config(BaseSettings):
class Config(BaseModel):
"""FastAPI 驱动框架设置,详情参考 FastAPI 文档"""
fastapi_openapi_url: Optional[str] = None
@@ -83,9 +82,6 @@ class Config(BaseSettings):
fastapi_extra: Dict[str, Any] = {}
"""传递给 `FastAPI` 的其他参数。"""
class Config:
extra = "ignore"
class Driver(BaseDriver, ASGIMixin):
"""FastAPI 驱动框架。"""

View File

@@ -72,8 +72,7 @@ class Mixin(HTTPClientMixin):
if TYPE_CHECKING:
class Driver(Mixin, NoneDriver):
...
class Driver(Mixin, NoneDriver): ...
else:
Driver = combine_driver(NoneDriver, Mixin)

View File

@@ -20,7 +20,7 @@ from functools import wraps
from typing_extensions import override
from typing import Any, Dict, List, Tuple, Union, Optional, cast
from pydantic import BaseSettings
from pydantic import BaseModel
from nonebot.config import Env
from nonebot.drivers import ASGIMixin
@@ -58,7 +58,7 @@ def catch_closed(func):
return decorator
class Config(BaseSettings):
class Config(BaseModel):
"""Quart 驱动框架设置"""
quart_reload: bool = False
@@ -74,9 +74,6 @@ class Config(BaseSettings):
quart_extra: Dict[str, Any] = {}
"""传递给 `Quart` 的其他参数。"""
class Config:
extra = "ignore"
class Driver(BaseDriver, ASGIMixin):
"""Quart 驱动框架"""

View File

@@ -50,10 +50,7 @@ def catch_closed(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
try:
return await func(*args, **kwargs)
except ConnectionClosed as e:
if e.rcvd_then_sent:
raise WebSocketClosed(e.rcvd.code, e.rcvd.reason) # type: ignore
else:
raise WebSocketClosed(e.sent.code, e.sent.reason) # type: ignore
raise WebSocketClosed(e.code, e.reason)
return decorator
@@ -131,8 +128,7 @@ class WebSocket(BaseWebSocket):
if TYPE_CHECKING:
class Driver(Mixin, NoneDriver):
...
class Driver(Mixin, NoneDriver): ...
else:
Driver = combine_driver(NoneDriver, Mixin)

View File

@@ -31,7 +31,7 @@ FrontMatter:
from typing import Any, Optional
from pydantic.fields import ModelField
from nonebot.compat import ModelField
class NoneBotException(Exception):

View File

@@ -14,8 +14,7 @@ if TYPE_CHECKING:
from .message import Message, MessageSegment
class _ApiCall(Protocol):
async def __call__(self, **kwargs: Any) -> Any:
...
async def __call__(self, **kwargs: Any) -> Any: ...
class Bot(abc.ABC):

View File

@@ -4,6 +4,7 @@ from typing import Any, Type, TypeVar
from pydantic import BaseModel
from nonebot.utils import DataclassEncoder
from nonebot.compat import PYDANTIC_V2, ConfigDict
from .message import Message
@@ -13,15 +14,21 @@ E = TypeVar("E", bound="Event")
class Event(abc.ABC, BaseModel):
"""Event 基类。提供获取关键信息的方法,其余信息可直接获取。"""
class Config:
extra = "allow"
json_encoders = {Message: DataclassEncoder}
if PYDANTIC_V2: # pragma: pydantic-v2
model_config = ConfigDict(extra="allow")
else: # pragma: pydantic-v1
@classmethod
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)
class Config(ConfigDict):
extra = "allow" # type: ignore
json_encoders = {Message: DataclassEncoder}
if not PYDANTIC_V2: # pragma: pydantic-v1
@classmethod
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)
@abc.abstractmethod
def get_type(self) -> str:

View File

@@ -17,7 +17,7 @@ from typing import (
overload,
)
from pydantic import parse_obj_as
from nonebot.compat import custom_validation, type_validate_python
from .template import MessageTemplate
@@ -25,6 +25,7 @@ TMS = TypeVar("TMS", bound="MessageSegment")
TM = TypeVar("TM", bound="Message")
@custom_validation
@dataclass
class MessageSegment(abc.ABC, Generic[TM]):
"""消息段基类"""
@@ -65,6 +66,8 @@ class MessageSegment(abc.ABC, Generic[TM]):
def _validate(cls, value) -> Self:
if isinstance(value, cls):
return value
if isinstance(value, MessageSegment):
raise ValueError(f"Type {type(value)} can not be converted to {cls}")
if not isinstance(value, dict):
raise ValueError(f"Expected dict for MessageSegment, got {type(value)}")
if "type" not in value:
@@ -97,6 +100,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
raise NotImplementedError
@custom_validation
class Message(List[TMS], abc.ABC):
"""消息序列
@@ -158,9 +162,9 @@ class Message(List[TMS], abc.ABC):
elif isinstance(value, str):
pass
elif isinstance(value, dict):
value = parse_obj_as(cls.get_segment_class(), value)
value = type_validate_python(cls.get_segment_class(), value)
elif isinstance(value, Iterable):
value = [parse_obj_as(cls.get_segment_class(), v) for v in value]
value = [type_validate_python(cls.get_segment_class(), v) for v in value]
else:
raise ValueError(
f"Expected str, dict or iterable for Message, got {type(value)}"

View File

@@ -20,9 +20,16 @@ from typing import (
overload,
)
from _string import formatter_field_name_split # type: ignore
if TYPE_CHECKING:
from .message import Message, MessageSegment
def formatter_field_name_split( # noqa: F811
field_name: str,
) -> Tuple[str, List[Tuple[bool, str]]]: ...
TM = TypeVar("TM", bound="Message")
TF = TypeVar("TF", str, "Message")
@@ -36,26 +43,35 @@ class MessageTemplate(Formatter, Generic[TF]):
参数:
template: 模板
factory: 消息类型工厂,默认为 `str`
private_getattr: 是否允许在模板中访问私有属性,默认为 `False`
"""
@overload
def __init__(
self: "MessageTemplate[str]", template: str, factory: Type[str] = str
) -> None:
...
self: "MessageTemplate[str]",
template: str,
factory: Type[str] = str,
private_getattr: bool = False,
) -> None: ...
@overload
def __init__(
self: "MessageTemplate[TM]", template: Union[str, TM], factory: Type[TM]
) -> None:
...
self: "MessageTemplate[TM]",
template: Union[str, TM],
factory: Type[TM],
private_getattr: bool = False,
) -> None: ...
def __init__(
self, template: Union[str, TM], factory: Union[Type[str], Type[TM]] = str
self,
template: Union[str, TM],
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.private_getattr = private_getattr
def __repr__(self) -> str:
return f"MessageTemplate({self.template!r}, factory={self.factory!r})"
@@ -167,6 +183,19 @@ class MessageTemplate(Formatter, Generic[TF]):
return functools.reduce(self._add, results), auto_arg_index
def get_field(
self, field_name: str, args: Sequence[Any], kwargs: Mapping[str, Any]
) -> Tuple[Any, Union[int, str]]:
first, rest = formatter_field_name_split(field_name)
obj = self.get_value(first, args, kwargs)
for is_attr, value in rest:
if not self.private_getattr and value.startswith("_"):
raise ValueError("Cannot access private attribute")
obj = getattr(obj, value) if is_attr else obj[value]
return obj, first
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):

View File

@@ -6,18 +6,15 @@ D = TypeVar("D", bound="Driver")
if TYPE_CHECKING:
class CombinedDriver(Driver, Mixin):
...
class CombinedDriver(Driver, Mixin): ...
@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], *mixins: Type[Mixin]) -> Type["CombinedDriver"]: ...
def combine_driver(

View File

@@ -65,12 +65,10 @@ class MatcherManager(MutableMapping[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

View File

@@ -262,16 +262,20 @@ class Matcher(metaclass=MatcherMeta):
"type": type_,
"rule": rule or Rule(),
"permission": permission or Permission(),
"handlers": [
handler
if isinstance(handler, Dependent)
else Dependent[Any].parse(
call=handler, allow_types=cls.HANDLER_PARAM_TYPES
)
for handler in handlers
]
if handlers
else [],
"handlers": (
[
(
handler
if isinstance(handler, Dependent)
else Dependent[Any].parse(
call=handler, allow_types=cls.HANDLER_PARAM_TYPES
)
)
for handler in handlers
]
if handlers
else []
),
"temp": temp,
"expire_time": (
expire_time
@@ -658,12 +662,10 @@ class Matcher(metaclass=MatcherMeta):
raise SkippedException
@overload
def get_receive(self, id: str) -> Union[Event, None]:
...
def get_receive(self, id: str) -> Union[Event, None]: ...
@overload
def get_receive(self, id: str, default: T) -> Union[Event, T]:
...
def get_receive(self, id: str, default: T) -> Union[Event, T]: ...
def get_receive(
self, id: str, default: Optional[T] = None
@@ -680,12 +682,10 @@ class Matcher(metaclass=MatcherMeta):
self.state[LAST_RECEIVE_KEY] = event
@overload
def get_last_receive(self) -> Union[Event, None]:
...
def get_last_receive(self) -> Union[Event, None]: ...
@overload
def get_last_receive(self, default: T) -> Union[Event, T]:
...
def get_last_receive(self, default: T) -> Union[Event, T]: ...
def get_last_receive(
self, default: Optional[T] = None
@@ -697,12 +697,10 @@ class Matcher(metaclass=MatcherMeta):
return self.state.get(LAST_RECEIVE_KEY, default)
@overload
def get_arg(self, key: str) -> Union[Message, None]:
...
def get_arg(self, key: str) -> Union[Message, None]: ...
@overload
def get_arg(self, key: str, default: T) -> Union[Message, T]:
...
def get_arg(self, key: str, default: T) -> Union[Message, T]: ...
def get_arg(
self, key: str, default: Optional[T] = None
@@ -724,12 +722,10 @@ class Matcher(metaclass=MatcherMeta):
self.state[REJECT_TARGET] = target
@overload
def get_target(self) -> Union[str, None]:
...
def get_target(self) -> Union[str, None]: ...
@overload
def get_target(self, default: T) -> Union[str, T]:
...
def get_target(self, default: T) -> Union[str, T]: ...
def get_target(self, default: Optional[T] = None) -> Optional[Union[str, T]]:
return self.state.get(REJECT_TARGET, default)

View File

@@ -1,7 +1,7 @@
import asyncio
import inspect
from typing_extensions import Self, Annotated, override
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
from typing_extensions import Self, Annotated, get_args, override, get_origin
from typing import (
TYPE_CHECKING,
Any,
@@ -14,12 +14,12 @@ from typing import (
cast,
)
from pydantic.typing import get_args, get_origin
from pydantic.fields import Required, FieldInfo, Undefined, ModelField
from pydantic.fields import FieldInfo as PydanticFieldInfo
from nonebot.dependencies import Param, Dependent
from nonebot.dependencies.utils import check_field_type
from nonebot.dependencies import Param, Dependent, CustomConfig
from nonebot.typing import T_State, T_Handler, T_DependencyCache
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined, extract_field_info
from nonebot.utils import (
get_name,
run_sync,
@@ -34,23 +34,6 @@ if TYPE_CHECKING:
from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Event
EXTRA_FIELD_INFO = (
"gt",
"lt",
"ge",
"le",
"multiple_of",
"allow_inf_nan",
"max_digits",
"decimal_places",
"min_items",
"max_items",
"unique_items",
"min_length",
"max_length",
"regex",
)
class DependsInner:
def __init__(
@@ -58,7 +41,7 @@ class DependsInner:
dependency: Optional[T_Handler] = None,
*,
use_cache: bool = True,
validate: Union[bool, FieldInfo] = False,
validate: Union[bool, PydanticFieldInfo] = False,
) -> None:
self.dependency = dependency
self.use_cache = use_cache
@@ -75,7 +58,7 @@ def Depends(
dependency: Optional[T_Handler] = None,
*,
use_cache: bool = True,
validate: Union[bool, FieldInfo] = False,
validate: Union[bool, PydanticFieldInfo] = False,
) -> Any:
"""子依赖装饰器
@@ -113,24 +96,32 @@ class DependParam(Param):
本注入应该具有最高优先级,因此应该在其他参数之前检查。
"""
def __init__(
self, *args, dependent: Dependent, use_cache: bool, **kwargs: Any
) -> None:
super().__init__(*args, **kwargs)
self.dependent = dependent
self.use_cache = use_cache
def __repr__(self) -> str:
return f"Depends({self.extra['dependent']})"
return f"Depends({self.dependent}, use_cache={self.use_cache})"
@classmethod
def _from_field(
cls, sub_dependent: Dependent, use_cache: bool, validate: Union[bool, FieldInfo]
cls,
sub_dependent: Dependent,
use_cache: bool,
validate: Union[bool, PydanticFieldInfo],
) -> Self:
kwargs = {}
if isinstance(validate, FieldInfo):
kwargs.update((k, getattr(validate, k)) for k in EXTRA_FIELD_INFO)
if isinstance(validate, PydanticFieldInfo):
kwargs.update(extract_field_info(validate))
return cls(
Required,
validate=bool(validate),
**kwargs,
dependent=sub_dependent,
use_cache=use_cache,
)
kwargs["validate"] = bool(validate)
kwargs["dependent"] = sub_dependent
kwargs["use_cache"] = use_cache
return cls(**kwargs)
@classmethod
@override
@@ -191,10 +182,10 @@ class DependParam(Param):
dependency_cache: Optional[T_DependencyCache] = None,
**kwargs: Any,
) -> Any:
use_cache: bool = self.extra["use_cache"]
use_cache: bool = self.use_cache
dependency_cache = {} if dependency_cache is None else dependency_cache
sub_dependent: Dependent = self.extra["dependent"]
sub_dependent: Dependent = self.dependent
call = cast(Callable[..., Any], sub_dependent.call)
# solve sub dependency with current cache
@@ -231,8 +222,7 @@ class DependParam(Param):
@override
async def _check(self, **kwargs: Any) -> None:
# run sub dependent pre-checkers
sub_dependent: Dependent = self.extra["dependent"]
await sub_dependent.check(**kwargs)
await self.dependent.check(**kwargs)
class BotParam(Param):
@@ -243,14 +233,16 @@ class BotParam(Param):
为保证兼容性,本注入还会解析名为 `bot` 且没有类型注解的参数。
"""
def __init__(
self, *args, checker: Optional[ModelField] = None, **kwargs: Any
) -> None:
super().__init__(*args, **kwargs)
self.checker = checker
def __repr__(self) -> str:
return (
"BotParam("
+ (
repr(cast(ModelField, checker).type_)
if (checker := self.extra.get("checker"))
else ""
)
+ (repr(self.checker.annotation) if self.checker is not None else "")
+ ")"
)
@@ -265,18 +257,13 @@ class BotParam(Param):
if generic_check_issubclass(param.annotation, Bot):
checker: Optional[ModelField] = None
if param.annotation is not Bot:
checker = ModelField(
name=param.name,
type_=param.annotation,
class_validators=None,
model_config=CustomConfig,
default=None,
required=True,
checker = ModelField.construct(
name=param.name, annotation=param.annotation, field_info=FieldInfo()
)
return cls(Required, checker=checker)
return cls(checker=checker)
# legacy: param is named "bot" and has no type annotation
elif param.annotation == param.empty and param.name == "bot":
return cls(Required)
return cls()
@override
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
@@ -284,8 +271,8 @@ class BotParam(Param):
@override
async def _check(self, bot: "Bot", **kwargs: Any) -> None:
if checker := self.extra.get("checker"):
check_field_type(checker, bot)
if self.checker is not None:
check_field_type(self.checker, bot)
class EventParam(Param):
@@ -296,14 +283,16 @@ class EventParam(Param):
为保证兼容性,本注入还会解析名为 `event` 且没有类型注解的参数。
"""
def __init__(
self, *args, checker: Optional[ModelField] = None, **kwargs: Any
) -> None:
super().__init__(*args, **kwargs)
self.checker = checker
def __repr__(self) -> str:
return (
"EventParam("
+ (
repr(cast(ModelField, checker).type_)
if (checker := self.extra.get("checker"))
else ""
)
+ (repr(self.checker.annotation) if self.checker is not None else "")
+ ")"
)
@@ -318,18 +307,13 @@ class EventParam(Param):
if generic_check_issubclass(param.annotation, Event):
checker: Optional[ModelField] = None
if param.annotation is not Event:
checker = ModelField(
name=param.name,
type_=param.annotation,
class_validators=None,
model_config=CustomConfig,
default=None,
required=True,
checker = ModelField.construct(
name=param.name, annotation=param.annotation, field_info=FieldInfo()
)
return cls(Required, checker=checker)
return cls(checker=checker)
# legacy: param is named "event" and has no type annotation
elif param.annotation == param.empty and param.name == "event":
return cls(Required)
return cls()
@override
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
@@ -337,8 +321,8 @@ class EventParam(Param):
@override
async def _check(self, event: "Event", **kwargs: Any) -> Any:
if checker := self.extra.get("checker", None):
check_field_type(checker, event)
if self.checker is not None:
check_field_type(self.checker, event)
class StateParam(Param):
@@ -359,10 +343,10 @@ class StateParam(Param):
) -> Optional[Self]:
# param type is T_State
if param.annotation is T_State:
return cls(Required)
return cls()
# legacy: param is named "state" and has no type annotation
elif param.annotation == param.empty and param.name == "state":
return cls(Required)
return cls()
@override
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
@@ -377,8 +361,18 @@ class MatcherParam(Param):
为保证兼容性,本注入还会解析名为 `matcher` 且没有类型注解的参数。
"""
def __init__(
self, *args, checker: Optional[ModelField] = None, **kwargs: Any
) -> None:
super().__init__(*args, **kwargs)
self.checker = checker
def __repr__(self) -> str:
return "MatcherParam()"
return (
"MatcherParam("
+ (repr(self.checker.annotation) if self.checker is not None else "")
+ ")"
)
@classmethod
@override
@@ -391,18 +385,13 @@ class MatcherParam(Param):
if generic_check_issubclass(param.annotation, Matcher):
checker: Optional[ModelField] = None
if param.annotation is not Matcher:
checker = ModelField(
name=param.name,
type_=param.annotation,
class_validators=None,
model_config=CustomConfig,
default=None,
required=True,
checker = ModelField.construct(
name=param.name, annotation=param.annotation, field_info=FieldInfo()
)
return cls(Required, checker=checker)
return cls(checker=checker)
# legacy: param is named "matcher" and has no type annotation
elif param.annotation == param.empty and param.name == "matcher":
return cls(Required)
return cls()
@override
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
@@ -410,16 +399,16 @@ class MatcherParam(Param):
@override
async def _check(self, matcher: "Matcher", **kwargs: Any) -> Any:
if checker := self.extra.get("checker", None):
check_field_type(checker, matcher)
if self.checker is not None:
check_field_type(self.checker, matcher)
class ArgInner:
def __init__(
self, key: Optional[str], type: Literal["message", "str", "plaintext"]
) -> None:
self.key = key
self.type = type
self.key: Optional[str] = key
self.type: Literal["message", "str", "plaintext"] = type
def __repr__(self) -> str:
return f"ArgInner(key={self.key!r}, type={self.type!r})"
@@ -449,8 +438,19 @@ class ArgParam(Param):
留空则会根据参数名称获取。
"""
def __init__(
self,
*args,
key: str,
type: Literal["message", "str", "plaintext"],
**kwargs: Any,
) -> None:
super().__init__(*args, **kwargs)
self.key = key
self.type = type
def __repr__(self) -> str:
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
return f"ArgParam(key={self.key!r}, type={self.type!r})"
@classmethod
@override
@@ -458,22 +458,19 @@ class ArgParam(Param):
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional[Self]:
if isinstance(param.default, ArgInner):
return cls(
Required, key=param.default.key or param.name, type=param.default.type
)
return cls(key=param.default.key or param.name, type=param.default.type)
elif get_origin(param.annotation) is Annotated:
for arg in get_args(param.annotation)[:0:-1]:
if isinstance(arg, ArgInner):
return cls(Required, key=arg.key or param.name, type=arg.type)
return cls(key=arg.key or param.name, type=arg.type)
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
key: str = self.extra["key"]
message = matcher.get_arg(key)
message = matcher.get_arg(self.key)
if message is None:
return message
if self.extra["type"] == "message":
if self.type == "message":
return message
elif self.extra["type"] == "str":
elif self.type == "str":
return str(message)
else:
return message.extract_plain_text()
@@ -497,10 +494,10 @@ class ExceptionParam(Param):
) -> Optional[Self]:
# param type is Exception(s) or subclass(es) of Exception or None
if generic_check_issubclass(param.annotation, Exception):
return cls(Required)
return cls()
# legacy: param is named "exception" and has no type annotation
elif param.annotation == param.empty and param.name == "exception":
return cls(Required)
return cls()
@override
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
@@ -524,11 +521,11 @@ class DefaultParam(Param):
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional[Self]:
if param.default != param.empty:
return cls(param.default)
return cls(default=param.default)
@override
async def _solve(self, **kwargs: Any) -> Any:
return Undefined
return PydanticUndefined
__autodoc__ = {

View File

@@ -39,10 +39,12 @@ class Permission:
def __init__(self, *checkers: Union[T_PermissionChecker, Dependent[bool]]) -> None:
self.checkers: Set[Dependent[bool]] = {
checker
if isinstance(checker, Dependent)
else Dependent[bool].parse(
call=checker, allow_types=self.HANDLER_PARAM_TYPES
(
checker
if isinstance(checker, Dependent)
else Dependent[bool].parse(
call=checker, allow_types=self.HANDLER_PARAM_TYPES
)
)
for checker in checkers
}

View File

@@ -38,10 +38,12 @@ class Rule:
def __init__(self, *checkers: Union[T_RuleChecker, Dependent[bool]]) -> None:
self.checkers: Set[Dependent[bool]] = {
checker
if isinstance(checker, Dependent)
else Dependent[bool].parse(
call=checker, allow_types=self.HANDLER_PARAM_TYPES
(
checker
if isinstance(checker, Dependent)
else Dependent[bool].parse(
call=checker, allow_types=self.HANDLER_PARAM_TYPES
)
)
for checker in checkers
}

View File

@@ -5,7 +5,18 @@ FrontMatter:
description: nonebot.params 模块
"""
from typing import Any, Dict, List, Match, Tuple, Union, Optional
from typing import (
Any,
Dict,
List,
Match,
Tuple,
Union,
Literal,
Callable,
Optional,
overload,
)
from nonebot.typing import T_State
from nonebot.matcher import Matcher
@@ -147,13 +158,34 @@ def RegexMatched() -> Match[str]:
return Depends(_regex_matched, use_cache=False)
def _regex_str(state: T_State) -> str:
return _regex_matched(state).group()
def _regex_str(
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]:
return _regex_matched(state).group(*groups)
return _regex_str_dependency
def RegexStr() -> str:
@overload
def RegexStr(__group: Literal[0] = 0) -> str: ...
@overload
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], ...]: ...
def RegexStr(*groups: Union[str, int]) -> Union[str, Tuple[Union[str, Any], ...], Any]:
"""正则匹配结果文本"""
return Depends(_regex_str, use_cache=False)
return Depends(_regex_str(groups), use_cache=False)
def _regex_group(state: T_State) -> Tuple[Any, ...]:

View File

@@ -39,7 +39,14 @@ FrontMatter:
from itertools import chain
from types import ModuleType
from contextvars import ContextVar
from typing import Set, Dict, List, Tuple, Optional
from typing import Set, Dict, List, Type, Tuple, TypeVar, Optional
from pydantic import BaseModel
from nonebot import get_driver
from nonebot.compat import model_dump, type_validate_python
C = TypeVar("C", bound=BaseModel)
_plugins: Dict[str, "Plugin"] = {}
_managers: List["PluginManager"] = []
@@ -108,6 +115,11 @@ 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:
"""从全局配置获取当前插件需要的配置项。"""
return type_validate_python(config, model_dump(get_driver().config))
from .on import on as on
from .manager import PluginManager
from .on import on_type as on_type

View File

@@ -213,8 +213,10 @@ def inherit_supported_adapters(*names: str) -> Optional[Set[str]]:
)
return final_supported and {
f"nonebot.adapters.{adapter_name[1:]}"
if adapter_name.startswith("~")
else adapter_name
(
f"nonebot.adapters.{adapter_name[1:]}"
if adapter_name.startswith("~")
else adapter_name
)
for adapter_name in final_supported
}

View File

@@ -19,4 +19,5 @@ echo = on_command("echo", to_me())
@echo.handle()
async def handle_echo(message: Message = CommandArg()):
await echo.send(message=message)
if any((not seg.is_text()) or str(seg) for seg in message):
await echo.send(message=message)

View File

@@ -460,45 +460,38 @@ 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(
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(
self,
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: None = None,
) -> Namespace:
...
) -> Namespace: ...
@overload
def parse_args(
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
) -> T:
...
) -> T: ...
@overload
def parse_args(self, *, namespace: T) -> T:
...
def parse_args(self, *, namespace: T) -> T: ...
def parse_args(
self,

View File

@@ -10,18 +10,14 @@ FrontMatter:
description: nonebot.typing 模块
"""
import sys
import types
import warnings
from typing_extensions import ParamSpec, TypeAlias, override
from typing import (
TYPE_CHECKING,
Any,
Dict,
Union,
TypeVar,
Callable,
Optional,
Awaitable,
)
import contextlib
import typing as t
import typing_extensions as t_ext
from typing import TYPE_CHECKING, TypeVar
from typing_extensions import ParamSpec, TypeAlias, get_args, override, get_origin
if TYPE_CHECKING:
from asyncio import Task
@@ -32,7 +28,7 @@ if TYPE_CHECKING:
T = TypeVar("T")
P = ParamSpec("P")
T_Wrapped: TypeAlias = Callable[P, T]
T_Wrapped: TypeAlias = t.Callable[P, T]
def overrides(InterfaceClass: object):
@@ -47,14 +43,77 @@ def overrides(InterfaceClass: object):
return override
if sys.version_info < (3, 10):
def origin_is_union(origin: t.Optional[t.Type[t.Any]]) -> bool:
"""判断是否是 Union 类型"""
return origin is t.Union
else:
def origin_is_union(origin: t.Optional[t.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:
"""判断是否是 Literal 类型"""
return origin is t.Literal or origin is t_ext.Literal
def _literal_values(type_: t.Type[t.Any]) -> t.Tuple[t.Any, ...]:
return get_args(type_)
def all_literal_values(type_: t.Type[t.Any]) -> t.List[t.Any]:
"""获取 Literal 类型包含的所有值"""
if not origin_is_literal(get_origin(type_)):
return [type_]
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:
"""判断是否是 Annotated 类型"""
with contextlib.suppress(TypeError):
return origin is not None and issubclass(origin, t_ext.Annotated)
return False
NONE_TYPES = {None, type(None), t.Literal[None], t_ext.Literal[None]}
if sys.version_info >= (3, 10):
NONE_TYPES.add(types.NoneType)
def is_none_type(type_: t.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]
) -> t.Any:
return ref._evaluate(globalns, localns, frozenset())
# state
T_State: TypeAlias = Dict[Any, Any]
T_State: TypeAlias = t.Dict[t.Any, t.Any]
"""事件处理状态 State 类型"""
_DependentCallable: TypeAlias = Union[Callable[..., T], Callable[..., Awaitable[T]]]
_DependentCallable: TypeAlias = t.Union[
t.Callable[..., T], t.Callable[..., t.Awaitable[T]]
]
# driver hooks
T_BotConnectionHook: TypeAlias = _DependentCallable[Any]
T_BotConnectionHook: TypeAlias = _DependentCallable[t.Any]
"""Bot 连接建立时钩子函数
依赖参数:
@@ -63,7 +122,7 @@ T_BotConnectionHook: TypeAlias = _DependentCallable[Any]
- BotParam: Bot 对象
- DefaultParam: 带有默认值的参数
"""
T_BotDisconnectionHook: TypeAlias = _DependentCallable[Any]
T_BotDisconnectionHook: TypeAlias = _DependentCallable[t.Any]
"""Bot 连接断开时钩子函数
依赖参数:
@@ -74,15 +133,17 @@ T_BotDisconnectionHook: TypeAlias = _DependentCallable[Any]
"""
# api hooks
T_CallingAPIHook: TypeAlias = Callable[["Bot", str, Dict[str, Any]], Awaitable[Any]]
T_CallingAPIHook: TypeAlias = t.Callable[
["Bot", str, t.Dict[str, t.Any]], t.Awaitable[t.Any]
]
"""`bot.call_api` 钩子函数"""
T_CalledAPIHook: TypeAlias = Callable[
["Bot", Optional[Exception], str, Dict[str, Any], Any], Awaitable[Any]
T_CalledAPIHook: TypeAlias = t.Callable[
["Bot", t.Optional[Exception], str, t.Dict[str, t.Any], t.Any], t.Awaitable[t.Any]
]
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
# event hooks
T_EventPreProcessor: TypeAlias = _DependentCallable[Any]
T_EventPreProcessor: TypeAlias = _DependentCallable[t.Any]
"""事件预处理函数 EventPreProcessor 类型
依赖参数:
@@ -93,7 +154,7 @@ T_EventPreProcessor: TypeAlias = _DependentCallable[Any]
- StateParam: State 对象
- DefaultParam: 带有默认值的参数
"""
T_EventPostProcessor: TypeAlias = _DependentCallable[Any]
T_EventPostProcessor: TypeAlias = _DependentCallable[t.Any]
"""事件后处理函数 EventPostProcessor 类型
依赖参数:
@@ -106,7 +167,7 @@ T_EventPostProcessor: TypeAlias = _DependentCallable[Any]
"""
# matcher run hooks
T_RunPreProcessor: TypeAlias = _DependentCallable[Any]
T_RunPreProcessor: TypeAlias = _DependentCallable[t.Any]
"""事件响应器运行前预处理函数 RunPreProcessor 类型
依赖参数:
@@ -118,7 +179,7 @@ T_RunPreProcessor: TypeAlias = _DependentCallable[Any]
- MatcherParam: Matcher 对象
- DefaultParam: 带有默认值的参数
"""
T_RunPostProcessor: TypeAlias = _DependentCallable[Any]
T_RunPostProcessor: TypeAlias = _DependentCallable[t.Any]
"""事件响应器运行后后处理函数 RunPostProcessor 类型
依赖参数:
@@ -155,7 +216,7 @@ T_PermissionChecker: TypeAlias = _DependentCallable[bool]
- DefaultParam: 带有默认值的参数
"""
T_Handler: TypeAlias = _DependentCallable[Any]
T_Handler: TypeAlias = _DependentCallable[t.Any]
"""Handler 处理函数。"""
T_TypeUpdater: TypeAlias = _DependentCallable[str]
"""TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。
@@ -183,5 +244,5 @@ T_PermissionUpdater: TypeAlias = _DependentCallable["Permission"]
- MatcherParam: Matcher 对象
- DefaultParam: 带有默认值的参数
"""
T_DependencyCache: TypeAlias = Dict[_DependentCallable[Any], "Task[Any]"]
T_DependencyCache: TypeAlias = t.Dict[_DependentCallable[t.Any], "Task[t.Any]"]
"""依赖缓存, 用于存储依赖函数的返回值"""

View File

@@ -12,28 +12,38 @@ import inspect
import importlib
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 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 pydantic.typing import is_union, is_none_type, is_literal_type, all_literal_values
from pydantic import BaseModel
from nonebot.log import logger
from nonebot.typing import (
is_none_type,
origin_is_union,
origin_is_literal,
all_literal_values,
)
P = ParamSpec("P")
R = TypeVar("R")
@@ -53,6 +63,34 @@ def escape_tag(s: str) -> str:
return re.sub(r"</?((?:[fb]g\s)?[^<>\s]*)>", r"\\\g<0>", s)
def deep_update(
mapping: Dict[K, Any], *updating_mappings: Dict[K, Any]
) -> Dict[K, Any]:
"""深度更新合并字典"""
updated_mapping = mapping.copy()
for updating_mapping in updating_mappings:
for k, v in updating_mapping.items():
if (
k in updated_mapping
and isinstance(updated_mapping[k], dict)
and isinstance(v, dict)
):
updated_mapping[k] = deep_update(updated_mapping[k], v)
else:
updated_mapping[k] = v
return updated_mapping
def lenient_issubclass(
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...]]
) -> bool:
"""检查 cls 是否是 class_or_tuple 中的一个类型子类并忽略类型错误。"""
try:
return isinstance(cls, type) and issubclass(cls, class_or_tuple)
except TypeError:
return False
def generic_check_issubclass(
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...]]
) -> bool:
@@ -62,6 +100,8 @@ def generic_check_issubclass(
- 如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
则会检查其中的所有类型是否是 class_or_tuple 中一个类型的子类或 None。
- 如果 cls 是 `typing.Literal` 类型,
则会检查其中的所有值是否是 class_or_tuple 中一个类型的实例。
- 如果 cls 是 `typing.TypeVar` 类型,
则会检查其 `__bound__` 或 `__constraints__`
是否是 class_or_tuple 中一个类型的子类或 None。
@@ -70,12 +110,12 @@ def generic_check_issubclass(
return issubclass(cls, class_or_tuple)
except TypeError:
origin = get_origin(cls)
if is_union(origin):
if origin_is_union(origin):
return all(
is_none_type(type_) or generic_check_issubclass(type_, class_or_tuple)
for type_ in get_args(cls)
)
elif is_literal_type(cls):
elif origin_is_literal(origin):
return all(
is_none_type(value) or isinstance(value, class_or_tuple)
for value in all_literal_values(cls)
@@ -99,6 +139,21 @@ def generic_check_issubclass(
return False
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:
if lenient_issubclass(type_, (str, bytes)):
return False
return lenient_issubclass(
type_, (BaseModel, Mapping, Sequence, tuple, set, frozenset, deque)
) or dataclasses.is_dataclass(type_)
def is_coroutine_callable(call: Callable[..., Any]) -> bool:
"""检查 call 是否是一个 callable 协程函数"""
if inspect.isroutine(call):
@@ -163,8 +218,7 @@ async def run_coro_with_catch(
coro: Coroutine[Any, Any, T],
exc: Tuple[Type[Exception], ...],
return_on_err: None = None,
) -> Union[T, None]:
...
) -> Union[T, None]: ...
@overload
@@ -172,8 +226,7 @@ async def run_coro_with_catch(
coro: Coroutine[Any, Any, T],
exc: Tuple[Type[Exception], ...],
return_on_err: R,
) -> Union[T, R]:
...
) -> Union[T, R]: ...
async def run_coro_with_catch(

952
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "nonebot2"
version = "2.1.3"
version = "2.2.0"
description = "An asynchronous python bot framework."
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
@@ -29,9 +29,10 @@ python = "^3.8"
yarl = "^1.7.2"
pygtrie = "^2.4.1"
loguru = ">=0.6.0,<1.0.0"
python-dotenv = ">=0.21.0,<2.0.0"
typing-extensions = ">=4.4.0,<5.0.0"
pydantic = ">=1.10.0,<3.0.0,!=2.5.0,!=2.5.1"
tomli = { version = "^2.0.1", python = "<3.11" }
pydantic = { version = "^1.10.0", extras = ["dotenv"] }
websockets = { version = ">=10.0", optional = true }
Quart = { version = ">=0.18.0,<1.0.0", optional = true }
@@ -43,19 +44,14 @@ uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
], optional = true }
[tool.poetry.group.dev.dependencies]
ruff = "^0.2.0"
isort = "^5.10.1"
black = "^23.1.0"
black = "^24.0.0"
nonemoji = "^0.1.2"
pre-commit = "^3.0.0"
ruff = ">=0.0.272,<1.0.0"
[tool.poetry.group.test.dependencies]
nonebug = "^0.3.0"
pytest-cov = "^4.0.0"
pytest-xdist = "^3.0.2"
pytest-asyncio = "^0.23.2"
werkzeug = ">=2.3.6,<4.0.0"
coverage-conditional-plugin = "^0.9.0"
nonebot-test = { path = "./envs/test/", develop = false }
[tool.poetry.group.docs.dependencies]
nb-autodoc = "^1.0.0a5"
@@ -90,19 +86,21 @@ src_paths = ["nonebot", "tests"]
extra_standard_library = ["typing_extensions"]
[tool.ruff]
select = ["E", "W", "F", "UP", "C", "T", "PYI", "PT", "Q"]
ignore = ["E402", "C901", "UP037"]
line-length = 88
target-version = "py38"
[tool.ruff.flake8-pytest-style]
[tool.ruff.lint]
select = ["E", "W", "F", "UP", "C", "T", "PYI", "PT", "Q"]
ignore = ["E402", "C901", "UP037"]
[tool.ruff.lint.flake8-pytest-style]
fixture-parentheses = false
mark-parentheses = false
[tool.pyright]
pythonVersion = "3.8"
pythonPlatform = "All"
defineConstant = { PYDANTIC_V2 = true }
executionEnvironments = [
{ root = "./tests", extraPaths = [
"./",

11
scripts/build-api-docs.sh Executable file
View File

@@ -0,0 +1,11 @@
#! /usr/bin/env bash
# cd to the root of the project
cd "$(dirname "$0")/.."
poetry run nb-autodoc nonebot \
-s nonebot.plugins \
-u nonebot.internal \
-u nonebot.internal.*
cp -r ./build/nonebot/* ./website/docs/api/
yarn prettier

7
scripts/run-tests.sh Executable file
View File

@@ -0,0 +1,7 @@
#! /usr/bin/env bash
# cd to the root of the tests
cd "$(dirname "$0")/../tests"
# Run the tests
pytest -n auto --cov-report xml $@

14
scripts/setup-envs.sh Executable file
View File

@@ -0,0 +1,14 @@
#! /usr/bin/env bash
# config poetry to install env in project
poetry config virtualenvs.in-project true
# setup dev environment
echo "Setting up dev environment"
poetry install --all-extras && poetry run pre-commit install && yarn install
# setup pydantic v2 test environment
for env in $(find ./envs/ -maxdepth 1 -mindepth 1 -type d -not -name test); do
echo "Setting up $env environment"
(cd $env && poetry install --no-root)
done

15
scripts/update-envs.sh Executable file
View File

@@ -0,0 +1,15 @@
#! /usr/bin/env bash
# update test env
echo "Updating test env..."
(cd ./envs/test/ && poetry update --lock)
# update dev env
echo "Updating dev env..."
poetry update
# update other envs
for env in $(find ./envs/ -maxdepth 1 -mindepth 1 -type d -not -name test); do
echo "Updating $env env..."
(cd $env && poetry update)
done

View File

@@ -22,5 +22,8 @@ rules =
"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
"package_version('pydantic') >= (2,)": pydantic-v1

17
tests/.env.example Normal file
View File

@@ -0,0 +1,17 @@
SIMPLE=simple
COMPLEX='
[1, 2, 3]
'
COMPLEX_NONE
COMPLEX_UNION=[1, 2, 3]
NESTED={"a": 1}
NESTED__B=2
NESTED__C__C=3
NESTED__COMPLEX=[1, 2, 3]
NESTED_INNER__A=1
NESTED_INNER__B=2
OTHER_SIMPLE=simple
OTHER_NESTED={"a": 1}
OTHER_NESTED__B=2
OTHER_NESTED_INNER__A=1
OTHER_NESTED_INNER__B=2

View File

@@ -13,3 +13,4 @@ NESTED_MISSING_DICT__A=1
NESTED_MISSING_DICT__B__C=2
NOT_NESTED=some string
NOT_NESTED__A=1
PLUGIN_CONFIG=1

View File

@@ -1,9 +1,15 @@
import json
import base64
import socket
from typing import Dict, List, Union, TypeVar
from wsproto.events import Ping
from werkzeug import Request, Response
from werkzeug.datastructures import MultiDict
from wsproto.frame_protocol import CloseReason
from wsproto.events import Request as WSRequest
from wsproto import WSConnection, ConnectionType
from wsproto.events import TextMessage, BytesMessage, CloseConnection, AcceptConnection
K = TypeVar("K")
V = TypeVar("V")
@@ -29,8 +35,7 @@ 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()}
@Request.application
def request_handler(request: Request) -> Response:
def http_echo(request: Request) -> Response:
try:
_json = json.loads(request.data.decode("utf-8"))
except (ValueError, TypeError):
@@ -67,3 +72,65 @@ def request_handler(request: Request) -> Response:
status=200,
content_type="application/json",
)
def websocket_echo(request: Request) -> Response:
stream = request.environ["werkzeug.socket"]
ws = WSConnection(ConnectionType.SERVER)
in_data = b"GET %s HTTP/1.1\r\n" % request.path.encode("utf-8")
for header, value in request.headers.items():
in_data += f"{header}: {value}\r\n".encode()
in_data += b"\r\n"
ws.receive_data(in_data)
running: bool = True
while True:
out_data = b""
for event in ws.events():
if isinstance(event, WSRequest):
out_data += ws.send(AcceptConnection())
elif isinstance(event, CloseConnection):
out_data += ws.send(event.response())
running = False
elif isinstance(event, Ping):
out_data += ws.send(event.response())
elif isinstance(event, TextMessage):
if event.data == "quit":
out_data += ws.send(
CloseConnection(CloseReason.NORMAL_CLOSURE, "bye")
)
running = False
else:
out_data += ws.send(TextMessage(data=event.data))
elif isinstance(event, BytesMessage):
if event.data == b"quit":
out_data += ws.send(
CloseConnection(CloseReason.NORMAL_CLOSURE, "bye")
)
running = False
else:
out_data += ws.send(BytesMessage(data=event.data))
if out_data:
stream.send(out_data)
if not running:
break
in_data = stream.recv(4096)
ws.receive_data(in_data)
stream.shutdown(socket.SHUT_RDWR)
return Response("", status=204)
@Request.application
def request_handler(request: Request) -> Response:
if request.headers.get("Connection") == "Upgrade":
return websocket_echo(request)
else:
return http_echo(request)

View File

@@ -78,8 +78,7 @@ async def reject_preset(a: str = ArgStr(), b: str = ArgStr()):
test_overload = on_message()
class FakeEvent(Event):
...
class FakeEvent(Event): ...
@test_overload.got("a")

View File

@@ -8,8 +8,7 @@ class Config(BaseModel):
custom: str = ""
class FakeAdapter(Adapter):
...
class FakeAdapter(Adapter): ...
__plugin_meta__ = PluginMetadata(

View File

@@ -11,20 +11,17 @@ async def legacy_bot(bot):
return bot
async def not_legacy_bot(bot: int):
...
async def not_legacy_bot(bot: int): ...
class FooBot(Bot):
...
class FooBot(Bot): ...
async def sub_bot(b: FooBot) -> FooBot:
return b
class BarBot(Bot):
...
class BarBot(Bot): ...
async def union_bot(b: Union[FooBot, BarBot]) -> Union[FooBot, BarBot]:
@@ -45,5 +42,4 @@ async def generic_bot_none(b: CB) -> CB:
return b
async def not_bot(b: Union[int, Bot]):
...
async def not_bot(b: Union[int, Bot]): ...

View File

@@ -36,8 +36,7 @@ class ClassDependency:
y: int = Depends(gen_async)
class FooBot(Bot):
...
class FooBot(Bot): ...
async def sub_bot(b: FooBot) -> FooBot:

View File

@@ -12,20 +12,17 @@ async def legacy_event(event):
return event
async def not_legacy_event(event: int):
...
async def not_legacy_event(event: int): ...
class FooEvent(Event):
...
class FooEvent(Event): ...
async def sub_event(e: FooEvent) -> FooEvent:
return e
class BarEvent(Event):
...
class BarEvent(Event): ...
async def union_event(e: Union[FooEvent, BarEvent]) -> Union[FooEvent, BarEvent]:
@@ -46,8 +43,7 @@ async def generic_event_none(e: CE) -> CE:
return e
async def not_event(e: Union[int, Event]):
...
async def not_event(e: Union[int, Event]): ...
async def event_type(t: str = EventType()) -> str:

View File

@@ -4,3 +4,7 @@ from typing import Union
async def exc(e: Exception, x: Union[ValueError, TypeError]) -> Exception:
assert e == x
return e
async def legacy_exc(exception) -> Exception:
return exception

View File

@@ -13,20 +13,17 @@ async def legacy_matcher(matcher):
return matcher
async def not_legacy_matcher(matcher: int):
...
async def not_legacy_matcher(matcher: int): ...
class FooMatcher(Matcher):
...
class FooMatcher(Matcher): ...
async def sub_matcher(m: FooMatcher) -> FooMatcher:
return m
class BarMatcher(Matcher):
...
class BarMatcher(Matcher): ...
async def union_matcher(
@@ -49,8 +46,7 @@ async def generic_matcher_none(m: CM) -> CM:
return m
async def not_matcher(m: Union[int, Matcher]):
...
async def not_matcher(m: Union[int, Matcher]): ...
async def receive(e: Event = Received("test")) -> Event:

View File

@@ -29,8 +29,7 @@ async def legacy_state(state):
return state
async def not_legacy_state(state: int):
...
async def not_legacy_state(state: int): ...
async def command(cmd: Tuple[str, ...] = Command()) -> Tuple[str, ...]:
@@ -77,8 +76,13 @@ async def regex_matched(regex_matched: Match[str] = RegexMatched()) -> Match[str
return regex_matched
async def regex_str(regex_str: str = RegexStr()) -> str:
return regex_str
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, ...]]:
return entire, type_, second, groups
async def startswith(startswith: str = Startswith()) -> str:

View File

@@ -19,5 +19,4 @@ async def complex_priority(
arg: Message = Arg(),
exception: Optional[Exception] = None,
default: int = 1,
):
...
): ...

View File

@@ -202,8 +202,7 @@ matcher_on_regex = on_regex(
)
class TestEvent(Event):
...
class TestEvent(Event): ...
matcher_on_type = on_type(

View File

@@ -99,8 +99,7 @@ async def test_adapter_server(driver: Driver):
async def handle_http(request: Request):
return Response(200, content="test")
async def handle_ws(ws: WebSocket):
...
async def handle_ws(ws: WebSocket): ...
adapter = FakeAdapter(driver)

View File

@@ -1,8 +1,9 @@
import pytest
from pydantic import ValidationError, parse_obj_as
from pydantic import ValidationError
from nonebot.adapters import Message
from nonebot.compat import type_validate_python
from utils import FakeMessage, FakeMessageSegment
from nonebot.adapters import Message, MessageSegment
def test_segment_data():
@@ -47,16 +48,21 @@ def test_segment_add():
def test_segment_validate():
assert parse_obj_as(
assert type_validate_python(
FakeMessageSegment,
{"type": "text", "data": {"text": "text"}, "extra": "should be ignored"},
) == FakeMessageSegment.text("text")
with pytest.raises(ValidationError):
type_validate_python(
type("FakeMessageSegment2", (MessageSegment,), {}),
FakeMessageSegment.text("text"),
)
with pytest.raises(ValidationError):
parse_obj_as(FakeMessageSegment, "some str")
type_validate_python(FakeMessageSegment, "some str")
with pytest.raises(ValidationError):
parse_obj_as(FakeMessageSegment, {"data": {}})
type_validate_python(FakeMessageSegment, {"data": {}})
def test_segment_join():
@@ -144,26 +150,26 @@ def test_message_getitem():
def test_message_validate():
assert parse_obj_as(FakeMessage, FakeMessage([])) == FakeMessage([])
assert type_validate_python(FakeMessage, FakeMessage([])) == FakeMessage([])
with pytest.raises(ValidationError):
parse_obj_as(type("FakeMessage2", (Message,), {}), FakeMessage([]))
type_validate_python(type("FakeMessage2", (Message,), {}), FakeMessage([]))
assert parse_obj_as(FakeMessage, "text") == FakeMessage(
assert type_validate_python(FakeMessage, "text") == FakeMessage(
[FakeMessageSegment.text("text")]
)
assert parse_obj_as(
assert type_validate_python(
FakeMessage, {"type": "text", "data": {"text": "text"}}
) == FakeMessage([FakeMessageSegment.text("text")])
assert parse_obj_as(
assert type_validate_python(
FakeMessage,
[FakeMessageSegment.text("text"), {"type": "text", "data": {"text": "text"}}],
) == FakeMessage([FakeMessageSegment.text("text"), FakeMessageSegment.text("text")])
with pytest.raises(ValidationError):
parse_obj_as(FakeMessage, object())
type_validate_python(FakeMessage, object())
def test_message_contains():

View File

@@ -1,3 +1,5 @@
import pytest
from nonebot.adapters import MessageTemplate
from utils import FakeMessage, FakeMessageSegment, escape_text
@@ -15,12 +17,8 @@ def test_template_message():
def custom(input: str) -> str:
return f"{input}-custom!"
try:
with pytest.raises(ValueError, match="already exists"):
template.add_format_spec(custom)
except ValueError:
pass
else:
raise AssertionError("Should raise ValueError")
format_args = {
"a": "custom",
@@ -57,3 +55,22 @@ def test_message_injection():
message = template.format(name="[fake:image]")
assert message.extract_plain_text() == escape_text("[fake:image]Is Bad")
def test_malformed_template():
positive_template = FakeMessage.template("{a}{b}")
message = positive_template.format(a="a", b="b")
assert message.extract_plain_text() == "ab"
malformed_template = FakeMessage.template("{a.__init__}")
with pytest.raises(ValueError, match="private attribute"):
message = malformed_template.format(a="a")
malformed_template = FakeMessage.template("{a[__builtins__]}")
with pytest.raises(ValueError, match="private attribute"):
message = malformed_template.format(a=globals())
malformed_template = MessageTemplate(
"{a[__builtins__][__import__]}{b.__init__}", private_getattr=True
)
message = malformed_template.format(a=globals(), b="b")

68
tests/test_compat.py Normal file
View File

@@ -0,0 +1,68 @@
from typing import Any
from dataclasses import dataclass
import pytest
from pydantic import BaseModel
from nonebot.compat import (
DEFAULT_CONFIG,
Required,
FieldInfo,
PydanticUndefined,
model_dump,
custom_validation,
type_validate_python,
)
@pytest.mark.asyncio
async def test_default_config():
assert DEFAULT_CONFIG.get("extra") == "allow"
assert DEFAULT_CONFIG.get("arbitrary_types_allowed") is True
@pytest.mark.asyncio
async def test_field_info():
# required should be convert to PydanticUndefined
assert FieldInfo(Required).default is PydanticUndefined
# field info should allow extra attributes
assert FieldInfo(test="test").extra["test"] == "test"
@pytest.mark.asyncio
async def test_model_dump():
class TestModel(BaseModel):
test1: int
test2: int
assert model_dump(TestModel(test1=1, test2=2), include={"test1"}) == {"test1": 1}
assert model_dump(TestModel(test1=1, test2=2), exclude={"test1"}) == {"test2": 2}
@pytest.mark.asyncio
async def test_custom_validation():
called = []
@custom_validation
@dataclass
class TestModel:
test: int
@classmethod
def __get_validators__(cls):
yield cls._validate_1
yield cls._validate_2
@classmethod
def _validate_1(cls, v: Any) -> Any:
called.append(1)
return v
@classmethod
def _validate_2(cls, v: Any) -> Any:
called.append(2)
return cls(test=v["test"])
assert type_validate_python(TestModel, {"test": 1}) == TestModel(test=1)
assert called == [1, 2]

118
tests/test_config.py Normal file
View File

@@ -0,0 +1,118 @@
from typing import List, Union, Optional
import pytest
from pydantic import BaseModel
from nonebot.config import DOTENV_TYPE, BaseSettings, SettingsError
class Simple(BaseModel):
a: int = 0
b: int = 0
c: dict = {}
complex: list = []
class Example(BaseSettings):
_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()
class Config:
env_file = ".env", ".env.example"
env_nested_delimiter = "__"
class ExampleWithoutDelimiter(Example):
class Config:
env_nested_delimiter = None
@pytest.mark.asyncio
async def test_config_no_env():
config = Example(_env_file=None)
assert config.simple == ""
with pytest.raises(AttributeError):
config.common_config
@pytest.mark.asyncio
async def test_config_with_env():
config = Example(_env_file=(".env", ".env.example"))
assert config.simple == "simple"
assert config.complex == [1, 2, 3]
assert config.complex_none is None
assert config.complex_union == [1, 2, 3]
assert config.nested.a == 1
assert config.nested.b == 2
assert config.nested.c == {"c": "3"}
assert config.nested.complex == [1, 2, 3]
with pytest.raises(AttributeError):
config.nested__b
with pytest.raises(AttributeError):
config.nested__c__c
with pytest.raises(AttributeError):
config.nested__complex
assert config.nested_inner.a == 1
assert config.nested_inner.b == 2
with pytest.raises(AttributeError):
config.nested_inner__a
with pytest.raises(AttributeError):
config.nested_inner__b
assert config.common_config == "common"
assert config.other_simple == "simple"
assert config.other_nested == {"a": 1, "b": 2}
with pytest.raises(AttributeError):
config.other_nested__b
assert config.other_nested_inner == {"a": 1, "b": 2}
with pytest.raises(AttributeError):
config.other_nested_inner__a
with pytest.raises(AttributeError):
config.other_nested_inner__b
@pytest.mark.asyncio
async def test_config_error_env():
with pytest.MonkeyPatch().context() as m:
m.setenv("COMPLEX", "not json")
with pytest.raises(SettingsError):
Example(_env_file=(".env", ".env.example"))
@pytest.mark.asyncio
async def test_config_without_delimiter():
config = ExampleWithoutDelimiter()
assert config.nested.a == 1
assert config.nested.b == 0
assert config.nested__b == 2
assert config.nested.c == {}
assert config.nested__c__c == 3
assert config.nested.complex == []
assert config.nested__complex == [1, 2, 3]
assert config.nested_inner.a == 0
assert config.nested_inner.b == 0
assert config.other_nested == {"a": 1}
assert config.other_nested__b == 2
with pytest.raises(AttributeError):
config.other_nested_inner
assert config.other_nested_inner__a == 1
assert config.other_nested_inner__b == 2

View File

@@ -131,7 +131,7 @@ async def test_websocket_server(app: App, driver: Driver):
assert data == b"ping"
await ws.send(b"pong")
with pytest.raises(WebSocketClosed):
with pytest.raises(WebSocketClosed, match=r"code=1000"):
await ws.receive()
ws_setup = WebSocketServerSetup(URL("/ws_test"), "ws_test", _handle_ws)
@@ -152,7 +152,7 @@ async def test_websocket_server(app: App, driver: Driver):
await ws.send_bytes(b"ping")
assert await ws.receive_bytes() == b"pong"
await ws.close()
await ws.close(code=1000)
await asyncio.sleep(1)
@@ -315,9 +315,29 @@ async def test_http_client(driver: Driver, server_url: URL):
],
indirect=True,
)
async def test_websocket_client(driver: Driver):
async def test_websocket_client(driver: Driver, server_url: URL):
assert isinstance(driver, WebSocketClientMixin)
request = Request("GET", server_url.with_scheme("ws"))
async with driver.websocket(request) as ws:
await ws.send("test")
assert await ws.receive() == "test"
await ws.send(b"test")
assert await ws.receive() == b"test"
await ws.send_text("test")
assert await ws.receive_text() == "test"
await ws.send_bytes(b"test")
assert await ws.receive_bytes() == b"test"
await ws.send("quit")
with pytest.raises(WebSocketClosed, match=r"code=1000"):
await ws.receive()
await asyncio.sleep(1)
@pytest.mark.asyncio
@pytest.mark.parametrize(

33
tests/test_echo.py Normal file
View File

@@ -0,0 +1,33 @@
import pytest
from nonebug import App
from utils import FakeMessage, FakeMessageSegment, make_fake_event
@pytest.mark.asyncio
async def test_echo(app: App):
from nonebot.plugins.echo import echo
async with app.test_matcher(echo) as ctx:
bot = ctx.create_bot()
message = FakeMessage("/echo 123")
event = make_fake_event(_message=message)()
ctx.receive_event(bot, event)
ctx.should_call_send(event, FakeMessage("123"), True, bot=bot)
message = FakeMessageSegment.text("/echo 123") + FakeMessageSegment.image(
"test"
)
event = make_fake_event(_message=message)()
ctx.receive_event(bot, event)
ctx.should_call_send(
event,
FakeMessageSegment.text("123") + FakeMessageSegment.image("test"),
True,
bot=bot,
)
message = FakeMessage("/echo")
event = make_fake_event(_message=message)()
ctx.receive_event(bot, event)

View File

@@ -218,7 +218,7 @@ async def test_event(app: App):
async with app.test_dependent(union_event, allow_types=[EventParam]) as ctx:
ctx.pass_params(event=fake_fooevent)
ctx.should_return(fake_event)
ctx.should_return(fake_fooevent)
async with app.test_dependent(generic_event, allow_types=[EventParam]) as ctx:
ctx.pass_params(event=fake_event)
@@ -361,7 +361,9 @@ async def test_state(app: App):
regex_str, allow_types=[StateParam, DependParam]
) as ctx:
ctx.pass_params(state=fake_state)
ctx.should_return("[cq:test,arg=value]")
ctx.should_return(
("[cq:test,arg=value]", "test", "arg=value", ("test", "arg=value"))
)
async with app.test_dependent(
regex_group, allow_types=[StateParam, DependParam]
@@ -527,13 +529,17 @@ async def test_arg(app: App):
@pytest.mark.asyncio
async def test_exception(app: App):
from plugins.param.param_exception import exc
from plugins.param.param_exception import exc, legacy_exc
exception = ValueError("test")
async with app.test_dependent(exc, allow_types=[ExceptionParam]) as ctx:
ctx.pass_params(exception=exception)
ctx.should_return(exception)
async with app.test_dependent(legacy_exc, allow_types=[ExceptionParam]) as ctx:
ctx.pass_params(exception=exception)
ctx.should_return(exception)
@pytest.mark.asyncio
async def test_default(app: App):

View File

@@ -1,4 +1,5 @@
import pytest
from pydantic import BaseModel
import nonebot
from nonebot.plugin import PluginManager, _managers
@@ -35,3 +36,14 @@ async def test_get_available_plugin():
finally:
_managers.clear()
_managers.extend(old_managers)
@pytest.mark.asyncio
async def test_get_plugin_config():
class Config(BaseModel):
plugin_config: int
# check get plugin config
config = nonebot.get_plugin_config(Config)
assert isinstance(config, Config)
assert config.plugin_config == 1

View File

@@ -34,19 +34,15 @@ def test_generic_check_issubclass():
def test_is_coroutine_callable():
async def test1():
...
async def test1(): ...
def test2():
...
def test2(): ...
class TestClass1:
async def __call__(self):
...
async def __call__(self): ...
class TestClass2:
def __call__(self):
...
def __call__(self): ...
assert is_coroutine_callable(test1)
assert not is_coroutine_callable(test2)
@@ -62,8 +58,7 @@ def test_is_gen_callable():
async def test2():
yield
def test3():
...
def test3(): ...
class TestClass1:
def __call__(self):
@@ -74,8 +69,7 @@ def test_is_gen_callable():
yield
class TestClass3:
def __call__(self):
...
def __call__(self): ...
assert is_gen_callable(test1)
assert not is_gen_callable(test2)
@@ -92,8 +86,7 @@ def test_is_async_gen_callable():
def test2():
yield
async def test3():
...
async def test3(): ...
class TestClass1:
async def __call__(self):
@@ -104,8 +97,7 @@ def test_is_async_gen_callable():
yield
class TestClass3:
async def __call__(self):
...
async def __call__(self): ...
assert is_async_gen_callable(test1)
assert not is_async_gen_callable(test2)

View File

@@ -19,6 +19,25 @@ NoneBot 使用 [`pydantic`](https://docs.pydantic.dev/) 以及 [`python-dotenv`]
NoneBot 内置的配置项列表及含义可以在[内置配置项](#内置配置项)中查看。
:::caution 注意
NoneBot 自 2.2.0 起兼容了 Pydantic v1 与 v2 版本,以下文档中 Pydantic 相关示例均采用 v2 版本用法。
如果在使用商店或其他第三方插件的过程中遇到 Pydantic 相关警告或报错,例如:
```python
pydantic_core._pydantic_core.ValidationError: 1 validation error for Config
Input should be a valid dictionary or instance of Config [type=model_type, input_value=Config(...), input_type=Config]
```
请考虑降级 Pydantic 至 v1 版本:
```bash
pip install --force-reinstall 'pydantic~=1.10'
```
:::
## 配置项的加载
在 NoneBot 中,我们可以把配置途径分为 **直接传入**、**系统环境变量**、**dotenv 配置文件** 三种,其加载优先级依次由高到低。
@@ -182,18 +201,19 @@ superusers = config.superusers
在 NoneBot 中,我们使用强大高效的 `pydantic` 来定义配置模型,这个模型可以被用于配置的读取和类型检查等。例如在 `weather` 插件目录中新建 `config.py` 来定义一个模型:
```python title=weather/config.py
from pydantic import BaseModel, validator
from pydantic import BaseModel, field_validator
class Config(BaseModel):
weather_api_key: str
weather_command_priority: int = 10
weather_plugin_enabled: bool = True
@validator("weather_command_priority")
def check_priority(cls, v):
if isinstance(v, int) and v >= 1:
@field_validator("weather_command_priority")
@classmethod
def check_priority(cls, v: int) -> int:
if v >= 1:
return v
raise ValueError("weather command priority must be an integer and greater than 1")
raise ValueError("weather command priority must greater than 1")
```
在 `config.py` 中,我们定义了一个 `Config` 类,它继承自 `pydantic.BaseModel`,并定义了一些配置项。在 `Config` 类中,我们还定义了一个 `check_priority` 方法,它用于检查 `weather_command_priority` 配置项的合法性。更多关于 `pydantic` 的编写方式,可以参考 [pydantic 官方文档](https://docs.pydantic.dev/)。
@@ -201,11 +221,11 @@ class Config(BaseModel):
在定义好配置模型后,我们可以在插件加载时获取全局配置,导入插件自身的配置模型并使用:
```python {5,11} title=weather/__init__.py
from nonebot import get_driver
from nonebot import get_plugin_config
from .config import Config
plugin_config = Config.parse_obj(get_driver().config)
plugin_config = get_plugin_config(Config)
weather = on_command(
"天气",
@@ -239,11 +259,11 @@ class Config(BaseModel):
```
```python title=weather/__init__.py
from nonebot import get_driver
from nonebot import get_plugin_config
from .config import Config
plugin_config = Config.parse_obj(get_driver().config).weather
plugin_config = get_plugin_config(Config).weather
```
这样我们就可以省略插件配置项名称中的前缀 `weather_` 了。但需要注意的是,如果我们使用了 scope 配置,那么在配置文件中也需要使用 [`env_nested_delimiter` 格式](#配置项解析),例如:

View File

@@ -3,114 +3,42 @@ sidebar_position: 2
description: Alconna 基本介绍
---
# Alconna 命令解析
# Alconna 本体
[Alconna](https://github.com/ArcletProject/Alconna) 作为命令解析器,
是一个简单、灵活、高效的命令参数解析器,并且不局限于解析命令式字符串。
[`Alconna`](https://github.com/ArcletProject/Alconna) 隶属于 `ArcletProject`,是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
特点包括
- 高效
- 直观的命令组件创建方式
- 强大的类型解析与类型转换功能
- 自定义的帮助信息格式
- 多语言支持
- 易用的快捷命令创建与使用
- 可创建命令补全会话,以实现多轮连续的补全提示
- 可嵌套的多级子命令
- 正则匹配支持
## 命令示范
我们通过一个例子来讲解 **Alconna** 的核心 —— `Args`, `Subcommand`, `Option`
```python
import sys
from io import StringIO
from arclet.alconna import Alconna, Args, Subcommand, Option
from arclet.alconna import Alconna, Args, Field, Option, CommandMeta, MultiVar, Arparma
from nepattern import AnyString
alc = Alconna(
"exec",
Args["code", MultiVar(AnyString), Field(completion=lambda: "print(1+1)")] / "\n",
Option("纯文本"),
Option("无输出"),
Option("目标", Args["name", str, "res"]),
meta=CommandMeta("exec python code", example="exec\\nprint(1+1)"),
)
alc.shortcut(
"echo",
{"command": "exec 纯文本\nprint(\\'{*}\\')"},
)
alc.shortcut(
"sin(\d+)",
{"command": "exec 纯文本\nimport math\nprint(math.sin({0}*math.pi/180))"},
)
def exec_code(result: Arparma):
if result.find("纯文本"):
codes = list(result.code)
else:
codes = str(result.origin).split("\n")[1:]
output = result.query[str]("目标.name", "res")
if not codes:
return ""
lcs = {}
_stdout = StringIO()
_to = sys.stdout
sys.stdout = _stdout
try:
exec(
"def rc(__out: str):\n "
+ " ".join(_code + "\n" for _code in codes)
+ " return locals().get(__out)",
{**globals(), **locals()},
lcs,
)
code_res = lcs["rc"](output)
sys.stdout = _to
if result.find("无输出"):
return ""
if code_res is not None:
return f"{output}: {code_res}"
_out = _stdout.getvalue()
return f"输出: {_out}"
except Exception as e:
sys.stdout = _to
return str(e)
finally:
sys.stdout = _to
print(exec_code(alc.parse("echo 1234")))
print(exec_code(alc.parse("sin30")))
print(
exec_code(
alc.parse(
"""\
exec
print(
exec_code(
alc.parse(
"exec\\n"
"import sys;print(sys.version)"
)
)
)
"""
)
"pip",
Subcommand(
"install",
Args["package", str],
Option("-r|--requirement", Args["file", str]),
Option("-i|--index-url", Args["url", str]),
)
)
res = alc.parse("pip install nonebot2 -i URL")
print(res)
# matched=True, header_match=(origin='pip' result='pip' matched=True groups={}), subcommands={'install': (value=Ellipsis args={'package': 'nonebot2'} options={'index-url': (value=None args={'url': 'URL'})} subcommands={})}, other_args={'package': 'nonebot2', 'url': 'URL'}
print(res.all_matched_args)
# {'package': 'nonebot2', 'url': 'URL'}
```
## 命令编写
这段代码通过`Alconna`创捷了一个接受主命令名为`pip`, 子命令为`install`且子命令接受一个 **Args** 参数`package`和二个 **Option** 参数`-r``-i`的命令参数解析器, 通过`parse`方法返回解析结果 **Arparma** 的实例。
## 组成
### 命令头
命令头是指命令的前缀 (Prefix) 与命令名 (Command) 的组合,例如 `!help` 中的 `!``help`
在 Alconna 中,你可以传入多种类型的命令头,例如:
命令头是指命令的前缀 (Prefix) 与命令名 (Command) 的组合,例如 !help 中的 ! 与 help。
| 前缀 | 命令名 | 匹配内容 | 说明 |
| :--------------------------: | :--------: | :---------------------------------------------------------: | :--------------: |
@@ -127,23 +55,20 @@ print(
| [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`
- 元素头:只会匹配对应的值,例如 `[123, 456]` 只会匹配 `123``456`,不会匹配 `789`
- 纯文字头:只会匹配对应的字符串,例如 `["foo", "bar"]` 只会匹配 `"foo"``"bar"`,不会匹配 `"baz"`
- 正则头:`re:xxx` 会将 `xxx` 转为正则表达式,然后匹配对应的字符串,例如 `re:\d{2}` 只会匹配 `"12"``"34"`,不会匹配 `"foo"`
**正则只在命令名上生效,命令前缀中的正则会被转义**
- 类型头:只会匹配对应的类型,例如 `[int, bool]` 只会匹配 `123``True`,不会匹配 `"foo"`
- 无前缀的类型头:此时会将传入的值尝试转为 BasePattern例如 `int` 会转为 `nepattern.INTEGER`。此时命令头会匹配对应的类型,
例如 `int` 会匹配 `123``"456"`,但不会匹配 `"foo"`。同时Alconna 会将命令头匹配到的值转为对应的类型,例如 `int` 会将 `"123"` 转为 `123`
- 表达式头:只会匹配对应的表达式,例如 `[nepattern.NUMBER]` 只会匹配 `123``123.456`,不会匹配 `"foo"`
- 混合头:
:::tip
除了通过传入 `re:xxx` 来使用正则表达式外Alconna 还提供了一种更加简洁的方式来使用正则表达式,那就是 Bracket Header。
**正则只在命令名上生效,命令前缀中的正则会被转义**
:::
除了通过传入 `re:xxx` 来使用正则表达式外Alconna 还提供了一种更加简洁的方式来使用正则表达式,那就是 Bracket Header
```python
from alconna import Alconna
alc = Alconna(".rd{roll:int}")
assert alc.parse(".rd123").header["roll"] == 123
```
@@ -152,362 +77,185 @@ Bracket Header 类似 python 里的 f-string 写法,通过 "{}" 声明匹配
"{}" 中的内容为 "name:type or pat"
- "{}", "{:}": 占位符,等价于 "(.+)"
- "{foo}": 等价于 "(?P&lt;foo&gt;.+)"
- "{:\d+}": 等价于 "(\d+)"
- "{foo:int}": 等价于 "(?P&lt;foo&gt;\d+)",其中 "int" 部分若能转为 `BasePattern` 则读取里面的表达式
- "{}", "{:}" "(.+)", 占位符
- "{foo}" "(?P&lt;foo&gt;.+)"
- "{:\d+}" "(\d+)"
- "{foo:int}" "(?P&lt;foo&gt;\d+)",其中 "int" 部分若能转为 `BasePattern` 则读取里面的表达式
### 组件
### 参数声明(Args)
我们可以看到主要的两大组件:`Option``Subcommand`
`Args` 是用于声明命令参数的组件, 可以通过以下几种方式构造 **Args**
`Option` 可以传入一组 `alias`,如 `Option("--foo|-F|FOO|f")``Option("--foo", alias=["-F"])`
- `Args[key, var, default][key1, var1, default1][...]`
- `Args[(key, var, default)]`
- `Args.key[var, default]`
传入别名后Option 会选择其中长度最长的作为选项名称。若传入为 "--foo|-f",则命令名称为 "--foo"。
其中key **一定**是字符串,而 var 一般为参数的类型default 为具体的值或者 **arclet.alconna.args.Field**
:::tip 特别提醒!!!
其与函数签名类似,但是允许含有默认值的参数在前;同时支持 keyword-only 参数不依照构造顺序传入 (但是仍需要在非 keyword-only 参数之后)。
在 Alconna 中 Option 的名字或别名**没有要求**必须在前面写上 `-`
#### key
:::
`key` 的作用是用以标记解析出来的参数并存放于 **Arparma** 中,以方便用户调用。
`Subcommand` 则可以传入自己的 **Option****Subcommand**
其有三种为 Args 注解的标识符:  `?``/`、 `!`, 标识符与 key 之间建议以 `;` 分隔:
```python
from arclet.alconna import Alconna, Option, Subcommand
- `!` 标识符表示该处传入的参数应**不是**规定的类型,或**不在**指定的值中。
- `?` 标识符表示该参数为**可选**参数,会在无参数匹配时跳过。
- `/` 标识符表示该参数的类型注解需要隐藏。
alc = Alconna(
"command_name",
Option("opt1"),
Option("--opt2"),
Subcommand(
"sub1",
Option("sub1_opt1"),
Option("SO2"),
Subcommand(
"sub1_sub1"
)
),
Subcommand(
"sub2"
)
)
```
他们拥有如下共同参数:
- `help_text`: 传入该组件的帮助信息
- `dest`: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
- `requires`: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
对于命令 `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` 可以设置包括参数字典在内的默认值:
```python
from arclet.alconna import Option, OptionResult
opt1 = Option("--foo", default=False)
opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
```
### 选项操作
`Option` 可以特别设置传入一类 `Action`,作为解析操作
`Action` 分为三类:
- `store`: 无 Args 时, 仅存储一个值, 默认为 Ellipsis 有 Args 时, 后续的解析结果会覆盖之前的值
- `append`: 无 Args 时, 将多个值存为列表, 默认为 Ellipsis 有 Args 时, 每个解析结果会追加到列表中
当存在默认值并且不为列表时, 会自动将默认值变成列表, 以保证追加的正确性
- `count`: 无 Args 时, 计数器加一; 有 Args 时, 表现与 STORE 相同
当存在默认值并且不为数字时, 会自动将默认值变成 1 以保证计数器的正确性。
`Alconna` 提供了预制的几类 `action`
- `store``store_value``store_true``store_false`
- `append``append_value`
- `count`
### 参数声明
`Args` 是用于声明命令参数的组件。
`Args` 是参数解析的基础组件,构造方法形如 `Args["foo", str]["bar", int]["baz", bool, False]`
与函数签名类似,但是允许含有默认值的参数在前;同时支持 keyword-only 参数不依照构造顺序传入 (但是仍需要在非 keyword-only 参数之后)。
`Args` 中的 `name` 是用以标记解析出来的参数并存放于 **Arparma** 中,以方便用户调用。
其有三种为 Args 注解的标识符: `?`、`/` 与 `!`。标识符与 key 之间建议以 `;` 分隔:
- `!` 标识符表示该处传入的参数应不是规定的类型,或不在指定的值中。
- `?` 标识符表示该参数为可选参数,会在无参数匹配时跳过。
- `/` 标识符表示该参数的类型注解需要隐藏。
另外,对于参数的注释也可以标记在 `name` 中,其与 name 或者标识符 以 `#` 分割:
`foo#这是注释;?` 或 `foo?#这是注释`
另外,对于参数的注释也可以标记在 `key` 中,其与 key 或者标识符 以 `#` 分割:
`foo#这是注释;?` 或 `foo?#这是注释`
:::tip
`Args` 中的 `name` 在实际命令中并不需要传入keyword 参数除外):
`Args` 中的 `key` 在实际命令中并不需要传入keyword 参数除外):
```python
from arclet.alconna import Alconna, Args
alc = Alconna("test", Args["foo", str])
alc.parse("test --foo abc") # 错误
alc.parse("test abc") # 正确
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
alc = Alconna("test", Option("--foo", Args["foo", str]))
```
:::
`Args` 的参数类型表面上看需要传入一个 `type`,但实际上它需要的是一个 `nepattern.BasePattern` 的实例。
#### var
var 负责命令参数的**类型检查**与**类型转化**
`Args` 的`var`表面上看需要传入一个 `type`,但实际上它需要的是一个 `nepattern.BasePattern` 的实例:
```python
from arclet.alconna import Args
from nepattern import BasePattern
# 表示 foo 参数需要匹配一个 @number 样式的字符串
args = Args["foo", BasePattern("@\d+")]
```
示例中传入的 `str` 是因为 `str` 已经注册在了 `nepattern.global_patterns` 中,因此会替换为 `nepattern.global_patterns[str]`。
示例中可以传入 `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}`: 匹配字典中的任意一个键, 并返回对应的值 (特殊的键 ... 会匹配任意的值)
- ...
:::
`MultiVar` 则是一个特殊的标注,用于告知解析器该参数可以接受多个值,其构造方法形如 `MultiVar(str)`。
同样的还有 `KeyWordVar`,其构造方法形如 `KeyWordVar(str)`,用于告知解析器该参数为一个 keyword-only 参数。
`MultiVar` 则是一个特殊的标注,用于告知解析器该参数可以接受多个值,其构造方法形如 `MultiVar(str)`。 同样的还有 `KeyWordVar`,其构造方法形如 `KeyWordVar(str)`,用于告知解析器该参数为一个 keyword-only 参数
:::tip
`MultiVar` 与 `KeyWordVar` 组合时,代表该参数为一个可接受多个 key-value 的参数,其构造方法形如 `MultiVar(KeyWordVar(str))`
`MultiVar` 与 `KeyWordVar` 组合时,代表该参数为一个可接受多个 key-value 的参数,其构造方法形如 `MultiVar(KeyWordVar(str))`
`MultiVar` 与 `KeyWordVar` 也可以传入 `default` 参数,用于指定默认值
`MultiVar` 与 `KeyWordVar` 也可以传入 `default` 参数,用于指定默认值
`MultiVar` 不能在 `KeyWordVar` 之后传入
`MultiVar` 不能在 `KeyWordVar` 之后传入
:::
### 紧凑命令
### Option 和 Subcommand
`Alconna``Option` 可以设置 `compact=True` 使得解析命令时允许名称与后随参数之间没有分隔:
`Option` 可以传入一组 `alias`,如 `Option("--foo|-F|--FOO|-f")` 或 `Option("--foo", alias=["-F"]`
传入别名后,`option` 会选择其中长度最长的作为选项名称。若传入为 "--foo|-f",则命令名称为 "--foo"
:::tip 特别提醒!!!
在 Alconna 中 Option 的名字或别名**没有要求**必须在前面写上 `-`
:::
`Subcommand` 可以传入自己的 **Option** 与 **Subcommand**
他们拥有如下共同参数:
- `help_text`: 传入该组件的帮助信息
- `dest`: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
- `requires`: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
对于命令 `test foo bar baz qux <a:int>` 来讲,因为`foo bar baz` 仅需要判断是否相等, 所以可以这么编写:
```python
from arclet.alconna import Alconna, Option, CommandMeta, Args
alc = Alconna("test", Args["foo", int], Option("BAR", Args["baz", str], compact=True), meta=CommandMeta(compact=True))
assert alc.parse("test123 BARabc").matched
Alconna("test", Option("qux", Args.a[int], requires=["foo", "bar", "baz"]))
```
这使得我们可以实现如下命令:
- `default`: 默认值,在该组件未被解析时使用使用该值替换。
特别的,使用 `OptionResult` 或 `SubcomanndResult` 可以设置包括参数字典在内的默认值:
```python
>>> from arclet.alconna import Alconna, Option, Args, append
>>> alc = Alconna("gcc", Option("--flag|-F", Args["content", str], action=append, compact=True))
>>> alc.parse("gcc -Fabc -Fdef -Fxyz").query[list[str]]("flag.content")
['abc', 'def', 'xyz']
from arclet.alconna import Option, OptionResult
opt1 = Option("--foo", default=False)
opt2 = Option("--foo", default=OptionResult(value=False, args={"bar": 1}))
```
当 `Option` 的 `action` 为 `count` 时,其自动支持 `compact` 特性:
`Option` 可以特别设置传入一类 `Action`,作为解析操作
```python
>>> from arclet.alconna import Alconna, Option, Args, count
>>> alc = Alconna("pp", Option("--verbose|-v", action=count, default=0))
>>> alc.parse("pp -vvv").query[int]("verbose.value")
3
```
`Action` 分为三类:
## 命令特性
- `store`: 无 Args 时, 仅存储一个值, 默认为 Ellipsis 有 Args 时, 后续的解析结果会覆盖之前的值
- `append`: 无 Args 时, 将多个值存为列表, 默认为 Ellipsis 有 Args 时, 每个解析结果会追加到列表中, 当存在默认值并且不为列表时, 会自动将默认值变成列表, 以保证追加的正确性
- `count`: 无 Args 时, 计数器加一; 有 Args 时, 表现与 STORE 相同, 当存在默认值并且不为数字时, 会自动将默认值变成 1 以保证计数器的正确性。
### 配置
`Alconna` 提供了预制的几类 `Action`
`arclet.alconna.Namespace` 表示某一命名空间下的默认配置:
- `store`(默认)`store_value``store_true``store_false`
- `append``append_value`
- `count`
```python
from arclet.alconna import config, namespace, Namespace
from arclet.alconna.tools import ShellTextFormatter
### Arparma
`Alconna.parse` 会返回由 **Arparma** 承载的解析结果
np = Namespace("foo", prefixes=["/"]) # 创建 Namespace 对象,并进行初始配置
with namespace("bar") as np1:
np1.prefixes = ["!"] # 以上下文管理器方式配置命名空间,此时配置会自动注入上下文内创建的命令
np1.formatter_type = ShellTextFormatter # 设置此命名空间下的命令的 formatter 默认为 ShellTextFormatter
np1.builtin_option_name["help"] = {"帮助", "-h"} # 设置此命名空间下的命令的帮助选项名称
config.namespaces["foo"] = np # 将命名空间挂载到 config 上
```
同时也提供了默认命名空间配置与修改方法:
```python
from arclet.alconna import config, namespace, Namespace
config.default_namespace.prefixes = [...] # 直接修改默认配置
np = Namespace("xxx", prefixes=[...])
config.default_namespace = np # 更换默认的命名空间
with namespace(config.default_namespace.name) as np:
np.prefixes = [...]
```
### 半自动补全
半自动补全为用户提供了推荐后续输入的功能。
补全默认通过 `--comp` 或 `-cp` 或 `?` 触发:(命名空间配置可修改名称)
```python
from arclet.alconna import Alconna, Args, Option
alc = Alconna("test", Args["abc", int]) + Option("foo") + Option("bar")
alc.parse("test ?")
'''
output
以下是建议的输入:
* <abc: int>
* --help
* -h
* -sct
* --shortcut
* foo
* bar
'''
```
### 快捷指令
快捷指令顾名思义,可以为基础指令创建便捷的触发方式
一般情况下你可以通过 `Alconna.shortcut` 进行快捷指令操作 (创建,删除)
```python
>>> from arclet.alconna import Alconna, Args
>>> alc = Alconna("setu", Args["count", int])
>>> alc.shortcut("涩图(\d+)张", {"args": ["{0}"]})
'Alconna::setu 的快捷指令: "涩图(\\d+)张" 添加成功'
>>> alc.parse("涩图3张").query("count")
3
```
`shortcut` 的第一个参数为快捷指令名称,第二个参数为 `ShortcutArgs`,作为快捷指令的配置
```python
class ShortcutArgs(TypedDict):
"""快捷指令参数"""
command: NotRequired[DataCollection[Any]]
"""快捷指令的命令"""
args: NotRequired[list[Any]]
"""快捷指令的附带参数"""
fuzzy: NotRequired[bool]
"""是否允许命令后随参数"""
prefix: NotRequired[bool]
"""是否调用时保留指令前缀"""
```
当 `fuzzy` 为 False 时,传入 `"涩图1张 abc"` 之类的快捷指令将视为解析失败
快捷指令允许三类特殊的 placeholder:
- `{%X}`: 如 `setu {%0}`,表示此处必须填入快捷指令后随的第 X 个参数。
例如,若快捷指令为 `涩图`,配置为 `{"command": "setu {%0}"}`,则指令 `涩图 1` 相当于 `setu 1`
- `{*}`: 表示此处填入所有后随参数,并且可以通过 `{*X}` 的方式指定组合参数之间的分隔符。
- `{X}`: 表示此处填入可能的正则匹配的组:
- 若 `command` 中存在匹配组 `(xxx)`,则 `{X}` 表示第 X 个匹配组的内容
- 若 `command` 中存储匹配组 `(?P<xxx>...)`,则 `{X}` 表示名字为 X 的匹配结果
除此之外,通过内置选项 `--shortcut` 可以动态操作快捷指令。
例如:
- `cmd --shortcut <key> <cmd>` 来增加一个快捷指令
- `cmd --shortcut list` 来列出当前指令的所有快捷指令
- `cmd --shortcut delete key` 来删除一个快捷指令
### 使用模糊匹配
模糊匹配通过在 Alconna 中设置其 CommandMeta 开启。
模糊匹配会应用在任意需要进行名称判断的地方,如**命令名称****选项名称**和**参数名称**(如指定需要传入参数名称)。
```python
from arclet.alconna import Alconna, CommandMeta
alc = Alconna("test_fuzzy", meta=CommandMeta(fuzzy_match=True))
alc.parse("test_fuzy")
# output: test_fuzy is not matched. Do you mean "test_fuzzy"?
```
## 解析结果
`Alconna.parse` 会返回由 **Arparma** 承载的解析结果。
`Arpamar` 会有如下参数:
`Arparma` 会有如下参数:
- 调试类
@@ -524,47 +272,286 @@ alc.parse("test_fuzy")
- 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` 键对应的值 ...
同样,`Arparma["foo.bar"]` 的表现与 `query()` 一致
## 命名空间配置
命名空间配置 (以下简称命名空间) 相当于`Alconna`的设置,`Alconna`默认使用“Alconna”命名空间命名空间有以下几个属性
- name: 命名空间名称
- prefixes: 默认前缀配置
- separators: 默认分隔符配置
- formatter_type: 默认格式化器类型
- fuzzy_match: 默认是否开启模糊匹配
- raise_exception: 默认是否抛出异常
- builtin_option_name: 默认的内置选项名称(--help, --shortcut, --comp)
- enable_message_cache: 默认是否启用消息缓存
- compact: 默认是否开启紧凑模式
- strict: 命令是否严格匹配
- ...
### 新建命名空间并替换
```python
from arclet.alconna import Alconna, namespace, Namespace, Subcommand, Args, config
ns = Namespace("foo", prefixes=["/"])  # 创建 "foo"命名空间配置, 它要求创建的Alconna的主命令前缀必须是/
alc = Alconna("pip", Subcommand("install", Args["package", str]), namespace=ns) # 在创建Alconna时候传入命名空间以替换默认命名空间
# 可以通过with方式创建命名空间
with namespace("bar") as np1:
np1.prefixes = ["!"] # 以上下文管理器方式配置命名空间,此时配置会自动注入上下文内创建的命令
np1.formatter_type = ShellTextFormatter # 设置此命名空间下的命令的 formatter 默认为 ShellTextFormatter
np1.builtin_option_name["help"] = {"帮助", "-h"} # 设置此命名空间下的命令的帮助选项名称
# 你还可以使用config来管理所有命名空间并切换至任意命名空间
config.namespaces["foo"] = ns # 将命名空间挂载到 config 上
alc = Alconna("pip", Subcommand("install", Args["package", str]), namespace=config.namespaces["foo"]) # 也是同样可以切换到"foo"命名空间
```
### 修改默认的命名空间
```python
from arclet.alconna import config, namespace, Namespace
config.default_namespace.prefixes = [...] # 直接修改默认配置
np = Namespace("xxx", prefixes=[...])
config.default_namespace = np # 更换默认的命名空间
with namespace(config.default_namespace.name) as np:
np.prefixes = [...]
```
## 快捷指令
快捷命令可以做到标识一段命令, 并且传递参数给原命令
一般情况下你可以通过 `Alconna.shortcut` 进行快捷指令操作 (创建,删除)
`shortcut` 的第一个参数为快捷指令名称,第二个参数为 `ShortcutArgs`,作为快捷指令的配置:
```python
class ShortcutArgs(TypedDict):
"""快捷指令参数"""
command: NotRequired[DataCollection[Any]]
"""快捷指令的命令"""
args: NotRequired[list[Any]]
"""快捷指令的附带参数"""
fuzzy: NotRequired[bool]
"""是否允许命令后随参数"""
prefix: NotRequired[bool]
"""是否调用时保留指令前缀"""
```
### args的使用
```python
from arclet.alconna import Alconna, Args
alc = Alconna("setu", Args["count", int])
alc.shortcut("涩图(\d+)张", {"args": ["{0}"]})
# 'Alconna::setu 的快捷指令: "涩图(\\d+)张" 添加成功'
alc.parse("涩图3张").query("count")
# 3
```
### command的使用
```python
from arclet.alconna import Alconna, Args
alc = Alconna("eval", Args["content", str])
alc.shortcut("echo", {"command": "eval print(\\'{*}\\')"})
# 'Alconna::eval 的快捷指令: "echo" 添加成功'
alc.shortcut("echo", delete=True) # 删除快捷指令
# 'Alconna::eval 的快捷指令: "echo" 删除成功'
@alc.bind() # 绑定一个命令执行器, 若匹配成功则会传入参数, 自动执行命令执行器
def cb(content: str):
eval(content, {}, {})
alc.parse('eval print(\\"hello world\\")')
# hello world
alc.parse("echo hello world!")
# hello world!
```
当 `fuzzy` 为 False 时,第一个例子中传入 `"涩图1张 abc"` 之类的快捷指令将视为解析失败
快捷指令允许三类特殊的 placeholder
- `{%X}`: 如 `setu {%0}`,表示此处填入快捷指令后随的第 X 个参数。
例如,若快捷指令为 `涩图`, 配置为 `{"command": "setu {%0}"}`, 则指令 `涩图 1` 相当于 `setu 1`
- `{*}`: 表示此处填入所有后随参数,并且可以通过 `{*X}` 的方式指定组合参数之间的分隔符。
- `{X}`: 表示此处填入可能的正则匹配的组:
- 若 `command` 中存在匹配组 `(xxx)`,则 `{X}` 表示第 X 个匹配组的内容
- 若 `command` 中存储匹配组 `(?P<xxx>...)`, 则 `{X}` 表示 **名字** 为 X 的匹配结果
除此之外, 通过 **Alconna** 内置选项 `--shortcut` 可以动态操作快捷指令
例如:
- `cmd --shortcut <key> <cmd>` 来增加一个快捷指令
- `cmd --shortcut list` 来列出当前指令的所有快捷指令
- `cmd --shortcut delete key` 来删除一个快捷指令
```python
from arclet.alconna import Alconna, Args
alc = Alconna("eval", Args["content", str])
alc.shortcut("echo", {"command": "eval print(\\'{*}\\')"})
alc.parse("eval --shortcut list")
# 'echo'
```
## 紧凑命令
`Alconna`,  `Option` 与 `Subcommand` 可以设置 `compact=True` 使得解析命令时允许名称与后随参数之间没有分隔:
```python
from arclet.alconna import Alconna, Option, CommandMeta, Args
alc = Alconna("test", Args["foo", int], Option("BAR", Args["baz", str], compact=True), meta=CommandMeta(compact=True))
assert alc.parse("test123 BARabc").matched
```
这使得我们可以实现如下命令:
```python
from arclet.alconna import Alconna, Option, Args, append
alc = Alconna("gcc", Option("--flag|-F", Args["content", str], action=append, compact=True))
print(alc.parse("gcc -Fabc -Fdef -Fxyz").query[list]("flag.content"))
# ['abc', 'def', 'xyz']
```
当 `Option` 的 `action` 为 `count` 时,其自动支持 `compact` 特性:
```python
from arclet.alconna import Alconna, Option, count
alc = Alconna("pp", Option("--verbose|-v", action=count, default=0))
print(alc.parse("pp -vvv").query[int]("verbose.value"))
# 3
```
## 模糊匹配
模糊匹配通过在 Alconna 中设置其 CommandMeta 开启
模糊匹配会应用在任意需要进行名称判断的地方,如 **命令名称****选项名称** 和 **参数名称** (如指定需要传入参数名称)。
```python
from arclet.alconna import Alconna, CommandMeta
alc = Alconna("test_fuzzy", meta=CommandMeta(fuzzy_match=True))
alc.parse("test_fuzy")
# test_fuzy is not matched. Do you mean "test_fuzzy"?
```
## 半自动补全
半自动补全为用户提供了推荐后续输入的功能
补全默认通过 `--comp` 或 `-cp` 或 `?` 触发:(命名空间配置可修改名称)
```python
from arclet.alconna import Alconna, Args, Option
alc = Alconna("test", Args["abc", int]) + Option("foo") + Option("bar")
alc.parse("test --comp")
'''
output
以下是建议的输入:
* <abc: int>
* --help
* -h
* -sct
* --shortcut
* foo
* bar
'''
```
## Duplication
**Duplication** 用来提供更好的自动补全,类似于 **ArgParse****Namespace**,经测试表现良好(好耶)。
**Duplication** 用来提供更好的自动补全,类似于 **ArgParse** 的 **Namespace**
普通情况下使用,需要利用到 **ArgsStub**、**OptionStub****SubcommandStub** 三个部分
普通情况下使用,需要利用到 **ArgsStub**、**OptionStub** 和 **SubcommandStub** 三个部分
pip 为例,其对应的 Duplication 应如下构造
pip为例其对应的 Duplication 应如下构造:
```python
from arclet.alconna import OptionResult, Duplication, SubcommandStub
from arclet.alconna import Alconna, Args, Option, OptionResult, Duplication, SubcommandStub, Subcommand, count
class MyDup(Duplication):
verbose: OptionResult
install: SubcommandStub # 选项与子命令对应的stub的变量名必须与其名字相同
```
    verbose: OptionResult
    install: SubcommandStub
并在解析时传入 Duplication
```python
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),
)
res = alc.parse("pip -v install ...") # 不使用duplication获得的提示较少
print(res.query("install"))
# (value=Ellipsis args={'package': '...'} options={} subcommands={})
result = alc.parse("pip -v install ...", duplication=MyDup)
>>> type(result)
<class MyDup>
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

View File

@@ -10,7 +10,7 @@ description: 配置项
- **类型**: `bool`
- **默认值**: `False`
是否全局启用输出信息自动发送,不启用则会在触特殊内置选项后仍然将解析结果传递至响应器。
是否全局启用输出信息自动发送,不启用则会在触特殊内置选项后仍然将解析结果传递至响应器。
## alconna_use_command_start
@@ -38,11 +38,11 @@ description: 配置项
- **类型**: `bool`
- **默认值**: `False`
是否读取 Nonebot 的配置项 `COMMAND_SEP` 来作为全局的 Alconna 命令分隔符
是否读取 Nonebot 的配置项 `COMMAND_SEP` 来作为全局的 Alconna 命令分隔符
## alconna_global_extensions
- **类型**: `List[str]`
- **默认值**: `[]`
全局加载的扩展,路径以 . 分隔,如 `foo.bar.baz:DemoExtension`
全局加载的扩展,路径以 . 分隔,如 `foo.bar.baz:DemoExtension`

View File

@@ -3,15 +3,16 @@ sidebar_position: 3
description: 响应规则的使用
---
# Alconna 响应规则
# Alconna 插件
以下为一个使用示例
展示
```python
from nonebot_plugin_alconna.adapters.onebot12 import Image
from nonebot_plugin_alconna import At, on_alconna
from arclet.alconna import Args, Option, Alconna, Arparma, MultiVar, Subcommand
alc = Alconna(
["/", "!"],
"role-group",
@@ -41,64 +42,103 @@ async def _(result: Arparma):
## 响应器使用
`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 作为命令分隔符
`on_alconna` 返回的是 `Matcher` 的子类 `AlconnaMatcher`,其拓展了如下方法:
- `.assign(path, value, or_not)`: 用于对包含多个选项/子命令的命令的分派处理
- `.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
用例:
本插件基于 **Alconna**,为 **Nonebot** 提供了一类新的事件响应器辅助函数 `on_alconna`
```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)`: 用于对包含多个选项/子命令的命令的分派处理(具体请看[条件控制](./matcher.md#条件控制)
- `.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
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("已退出")
    await login.finish("已退出")
# /login xxx 触发
@login.assign("password")
async def login_handle(pw: Match[str] = AlconnaMatch("password")):
if pw.available:
login.set_path_arg("password", pw.result)
    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
await login.send("登录成功")
assert password
await login.send("登录成功")
```
## 依赖注入
`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 +147,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 +164,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 +186,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#通用消息段)
本插件为以下适配器提供了专门的适配器标注:
@@ -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,9 +467,9 @@ matcher = on_alconna(
...
```
那么使用了 `LLMExtension` 的响应器便能接受任何能通过 llm 翻译为具体命令的自然语言消息,同时可以在响应器中为所有 `llm` 参数注入模型变量
那么添加了 `LLMExtension` 的响应器便能接受任何能通过 llm 翻译为具体命令的自然语言消息,同时可以在响应器中为所有 `llm` 参数注入模型变量
目前 `Extension` 的功能有:
目前 `Extension` 的功能有:
- `validate`: 对于事件的来源适配器或 bot 选择是否接受响应
- `output_converter`: 输出信息的自定义转换方法
@@ -360,15 +480,15 @@ matcher = on_alconna(
- `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.adapters.discord import DiscordSlashExtension
alc = Alconna(
["/"],
"permission",

View File

@@ -12,6 +12,9 @@ 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), 以达到**跨平台接收消息**的作用。
`nonebot-plugin-alconna.uniseg` 提供了类似 `MessageSegment` 的通用消息段,并可在 `Alconna` 下直接标注使用:
```python
@@ -80,13 +83,13 @@ class Other(Segment):
"""其他 Segment"""
```
来自各自适配器的消息序列都会经过这些通用消息段对应的标注转换,以达到跨平台接收消息的作用
此类消息段通过 `UniMessage.export` 可以转为特定的 `MessageSegment`
## 通用消息序列
`nonebot-plugin-alconna.uniseg` 同时提供了一个类似于 `Message` 的 `UniMessage` 类型,其元素为经过通用标注转换后的通用消息段。
你可以用如下方式获取 `UniMessage`
你可以用如下方式获取 `UniMessage`
<Tabs groupId="get_unimsg">
<TabItem value="depend" label="使用依赖注入">
@@ -96,6 +99,7 @@ class Other(Segment):
```python
from nonebot_plugin_alconna.uniseg import UniMsg, At, Reply
matcher = on_xxx(...)
@matcher.handle()
@@ -117,6 +121,7 @@ async def _(msg: UniMsg):
from nonebot import Message, EventMessage
from nonebot_plugin_alconna.uniseg import UniMessage
matcher = on_xxx(...)
@matcher.handle()
@@ -129,12 +134,13 @@ async def _(message: Message = EventMessage()):
不仅如此,你还可以通过 `UniMessage` 的 `export` 与 `send` 方法来**跨平台发送消息**。
`UniMessage.export` 会通过传入的 `bot: Bot` 参数,或上下文中的 `Bot` 对象读取适配器信息,并使用对应的生成方法把通用消息转为适配器对应的消息序列
`UniMessage.export` 会通过传入的 `bot: Bot` 参数,或上下文中的 `Bot` 对象读取适配器信息,并使用对应的生成方法把通用消息转为适配器对应的消息序列
```python
from nonebot import Bot, on_command
from nonebot_plugin_alconna.uniseg import Image, UniMessage
test = on_command("test")
@test.handle()
@@ -149,6 +155,7 @@ from arclet.alconna import Alconna, Args
from nonebot_plugin_alconna import Match, AlconnaMatcher, on_alconna
from nonebot_plugin_alconna.uniseg import At, UniMessage
test_cmd = on_alconna(Alconna("test", Args["target?", At]))
@test_cmd.handle()
@@ -167,6 +174,7 @@ async def tt(target: At):
from nonebot import Bot, on_command
from nonebot_plugin_alconna.uniseg import UniMessage
test = on_command("test")
@test.handle()
@@ -188,6 +196,7 @@ async def handle():
```python
from nonebot_plugin_alconna.uniseg import UniMessage, At
msg = UniMessage("Hello")
msg1 = UniMessage(At("user", "124"))
msg2 = UniMessage(["Hello", At("user", "124")])
@@ -198,33 +207,96 @@ msg2 = UniMessage(["Hello", At("user", "124")])
```python
from nonebot_plugin_alconna.uniseg import UniMessage, At, Image
msg = UniMessage.text("Hello").at("124").image(path="/path/to/img")
assert msg == UniMessage(
["Hello", At("user", "124"), Image(path="/path/to/img")]
)
```
### 获取消息纯文本
### 拼接消息
类似于 `Message.extract_plain_text()`,用于获取通用消息的纯文本。
`str`、`UniMessage`、`Segment` 对象之间可以直接相加,相加均会返回一个新的 `UniMessage` 对象:
```python
from nonebot_plugin_alconna.uniseg import UniMessage, At
# 提取消息纯文本字符串
assert UniMessage(
[At("user", "1234"), "text"]
).extract_plain_text() == "text"
# 消息序列与消息段相加
UniMessage("text") + Text("text")
# 消息序列与字符串相加
UniMessage([Text("text")]) + "text"
# 消息序列与消息序列相加
UniMessage("text") + UniMessage([Text("text")])
# 字符串与消息序列相加
"text" + UniMessage([Text("text")])
# 消息段与消息段相加
Text("text") + Text("text")
# 消息段与字符串相加
Text("text") + "text"
# 消息段与消息序列相加
Text("text") + UniMessage([Text("text")])
# 字符串与消息段相加
"text" + Text("text")
```
### 遍历
通用消息序列继承自 `List[Segment]` ,因此可以使用 `for` 循环遍历消息段。
如果需要在当前消息序列后直接拼接新的消息段,可以使用 `Message.append`、`Message.extend` 方法,或者使用自加:
```python
for segment in message: # type: Segment
...
msg = UniMessage([Text("text")])
# 自加
msg += "text"
msg += Text("text")
msg += UniMessage([Text("text")])
# 附加
msg.append(Text("text"))
# 扩展
msg.extend([Text("text")])
```
### 使用消息模板
`UniMessage.template` 同样类似于 `Message.template`,可以用于格式化消息,大体用法参考 [消息模板](../../tutorial/message#使用消息模板)。
这里额外说明 `UniMessage.template` 的拓展控制符
相比 `Message`UniMessage 对于 {:XXX} 做了另一类拓展。其能够识别例如 At(xxx, yyy) 或 Emoji(aaa, bbb)的字符串并执行
以 At(...) 为例:
```python title=使用通用消息段的拓展控制符
>>> from nonebot_plugin_alconna.uniseg import UniMessage
>>> UniMessage.template("{:At(user, target)}").format(target="123")
UniMessage(At("user", "123"))
>>> UniMessage.template("{:At(type=user, target=id)}").format(id="123")
UniMessage(At("user", "123"))
>>> UniMessage.template("{:At(type=user, target=123)}").format()
UniMessage(At("user", "123"))
```
而在 `AlconnaMatcher` 中,{:XXX} 更进一步地提供了获取 `event` 和 `bot` 中的属性的功能:
```python title=在AlconnaMatcher中使用通用消息段的拓展控制符
from arclet.alconna import Alconna, Args
from nonebot_plugin_alconna import At, Match, UniMessage, AlconnaMatcher, on_alconna
test_cmd = on_alconna(Alconna("test", Args["target?", At]))
@test_cmd.handle()
async def tt_h(matcher: AlconnaMatcher, target: Match[At]):
if target.available:
matcher.set_path_arg("target", target.result)
@test_cmd.got_path(
"target",
prompt=UniMessage.template("{:At(user, $event.get_user_id())} 请确认目标")
)
async def tt():
await test_cmd.send(
UniMessage.template("{:At(user, $event.get_user_id())} 已确认目标为 {target}")
)
```
另外也有 `$message_id` 与 `$target` 两个特殊值。
### 检查消息段
我们可以通过 `in` 运算符或消息序列的 `has` 方法来:
@@ -236,7 +308,7 @@ At("user", "1234") in message
At in message
```
我们还可以使用 `only` 方法来检查消息中是否仅包含指定的消息段
我们还可以使用 `only` 方法来检查消息中是否仅包含指定的消息段
```python
# 是否都为 "test"
@@ -245,13 +317,37 @@ message.only("test")
message.only(Text)
```
### 获取消息纯文本
类似于 `Message.extract_plain_text()`,用于获取通用消息的纯文本:
```python
from nonebot_plugin_alconna.uniseg import UniMessage, At
# 提取消息纯文本字符串
assert UniMessage(
[At("user", "1234"), "text"]
).extract_plain_text() == "text"
```
### 遍历
通用消息序列继承自 `List[Segment]` ,因此可以使用 `for` 循环遍历消息段:
```python
for segment in message: # type: Segment
...
```
### 过滤、索引与切片
消息序列对列表的索引与切片进行了增强,在原有列表 `int` 索引与 `slice` 切片的基础上,支持 `type` 过滤索引与切片
消息序列对列表的索引与切片进行了增强,在原有列表 `int` 索引与 `slice` 切片的基础上,支持 `type` 过滤索引与切片
```python
from nonebot_plugin_alconna.uniseg import UniMessage, At, Text, Reply
message = UniMessage(
[
Reply(...),
@@ -272,14 +368,14 @@ message[At, 0] == At("user", "1234")
message[Text, 0:2] == UniMessage([Text("text1"), Text("text2")])
```
我们也可以通过消息序列的 `include`、`exclude` 方法进行类型过滤
我们也可以通过消息序列的 `include`、`exclude` 方法进行类型过滤
```python
message.include(Text, At)
message.exclude(Reply)
```
同样的,消息序列对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段
同样的,消息序列对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段
```python
# 指定类型首个消息段索引
@@ -288,96 +384,14 @@ message.index(Text) == 1
message.count(Text) == 2
```
此外,消息序列添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段
此外,消息序列添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段
```python
# 获取指定类型指定个数的消息段
message.get(Text, 1) == UniMessage([Text("test1")])
```
### 拼接消息
`str`、`UniMessage`、`Segment` 对象之间可以直接相加,相加均会返回一个新的 `UniMessage` 对象。
```python
# 消息序列与消息段相加
UniMessage("text") + Text("text")
# 消息序列与字符串相加
UniMessage([Text("text")]) + "text"
# 消息序列与消息序列相加
UniMessage("text") + UniMessage([Text("text")])
# 字符串与消息序列相加
"text" + UniMessage([Text("text")])
# 消息段与消息段相加
Text("text") + Text("text")
# 消息段与字符串相加
Text("text") + "text"
# 消息段与消息序列相加
Text("text") + UniMessage([Text("text")])
# 字符串与消息段相加
"text" + Text("text")
```
如果需要在当前消息序列后直接拼接新的消息段,可以使用 `Message.append`、`Message.extend` 方法,或者使用自加。
```python
msg = UniMessage([Text("text")])
# 自加
msg += "text"
msg += Text("text")
msg += UniMessage([Text("text")])
# 附加
msg.append(Text("text"))
# 扩展
msg.extend([Text("text")])
```
### 使用消息模板
`UniMessage.template` 同样类似于 `Message.template`,可以用于格式化消息。大体用法参考 [消息模板](../../tutorial/message#使用消息模板)。
这里额外说明 `UniMessage.template` 的拓展控制符
相比 `Message`UniMessage 对于 {:XXX} 做了另一类拓展。其能够识别例如 At(xxx, yyy) 或 Emoji(aaa, bbb)的字符串并执行
以 At(...) 为例:
```python title=使用通用消息段的拓展控制符
>>> from nonebot_plugin_alconna.uniseg import UniMessage
>>> UniMessage.template("{:At(user, target)}").format(target="123")
UniMessage(At("user", "123"))
>>> UniMessage.template("{:At(type=user, target=id)}").format(id="123")
UniMessage(At("user", "123"))
>>> UniMessage.template("{:At(type=user, target=123)}").format()
UniMessage(At("user", "123"))
```
而在 `AlconnaMatcher` 中,{:XXX} 更进一步地提供了获取 `event` 和 `bot` 中的属性的功能
```python title=在AlconnaMatcher中使用通用消息段的拓展控制符
from arclet.alconna import Alconna, Args
from nonebot_plugin_alconna import At, Match, UniMessage, AlconnaMatcher, on_alconna
test_cmd = on_alconna(Alconna("test", Args["target?", At]))
@test_cmd.handle()
async def tt_h(matcher: AlconnaMatcher, target: Match[At]):
if target.available:
matcher.set_path_arg("target", target.result)
@test_cmd.got_path(
"target",
prompt=UniMessage.template("{:At(user, $event.get_user_id())} 请确认目标")
)
async def tt():
await test_cmd.send(
UniMessage.template("{:At(user, $event.get_user_id())} 已确认目标为 {target}")
)
```
另外也有 `$message_id` 与 `$target` 两个特殊值。
## 消息发送
### 消息发送
前面提到,通用消息可用 `UniMessage.send` 发送自身:
@@ -398,6 +412,7 @@ async def send(
from nonebot import Event, Bot
from nonebot_plugin_alconna.uniseg import UniMessage, Target
matcher = on_xxx(...)
@matcher.handle()

View File

@@ -1,88 +0,0 @@
---
sidebar_position: 6
description: 杂项
---
# 杂项
## 特殊装饰器
`nonebot_plugin_alconna` 提供 了一个 `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)
```
## 特殊构造器
`nonebot_plugin_alconna` 提供了一个 `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 {1, 9}
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` 对象,并提取图片的二进制数据返回。

View File

@@ -79,9 +79,9 @@ except Exception as e:
通常适配器需要一些配置项,例如平台连接密钥等。适配器的配置方法与[插件配置](../appendices/config#%E6%8F%92%E4%BB%B6%E9%85%8D%E7%BD%AE)类似,例如:
```python title=config.py
from pydantic import BaseModel, Extra
from pydantic import BaseModel
class Config(BaseModel, extra=Extra.ignore):
class Config(BaseModel):
xxx_id: str
xxx_token: str
```

View File

@@ -56,7 +56,7 @@ options:
## 创建配置文件
配置文件用于存放 NoneBot 运行所需要的配置项,使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。配置项需符合 dotenv 格式,复杂类型数据需使用 JSON 格式填写。具体可选配置方式以及配置项详情参考[配置](../appendices/config.mdx)。
配置文件用于存放 NoneBot 运行所需要的配置项,使用 [`pydantic`](https://docs.pydantic.dev/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。配置项需符合 dotenv 格式,复杂类型数据需使用 JSON 格式填写。具体可选配置方式以及配置项详情参考[配置](../appendices/config.mdx)。
在**项目文件夹**中创建一个 `.env` 文本文件,并写入以下内容:

View File

@@ -82,20 +82,19 @@ Message([MessageSegment.text("Hello, world!")])
#### 从字典数组构造
`Message` 对象支持 Pydantic 自定义类型构造,可以使用 Pydantic 的 `parse_obj_as` 方法进行构造。
`Message` 对象支持 Pydantic 自定义类型构造,可以使用 Pydantic 的 `TypeAdapter` 方法进行构造。
```python
from pydantic import parse_obj_as
from pydantic import TypeAdapter
from nonebot.adapters.console import Message, MessageSegment
# 由字典构造消息段
parse_obj_as(
MessageSegment, {"type": "text", "data": {"text": "text"}}
TypeAdapter(MessageSegment).validate_python(
{"type": "text", "data": {"text": "text"}}
) == MessageSegment.text("text")
# 由字典数组构造消息序列
parse_obj_as(
Message,
TypeAdapter(Message).validate_python(
[MessageSegment.text("text"), {"type": "text", "data": {"text": "text"}}],
) == Message([MessageSegment.text("text"), MessageSegment.text("text")])
```

View File

@@ -39,13 +39,15 @@ export default function TagFormItem({
}
if (validateTag()) {
const tag: TagType = { label, color };
setTags([...tags, tag]);
onTagUpdate(tags);
const newTags = [...tags, tag];
setTags(newTags);
onTagUpdate(newTags);
}
};
const delTag = (index: number) => {
setTags(tags.filter((_, i) => i !== index));
onTagUpdate(tags);
const newTags = tags.filter((_, i) => i !== index);
setTags(newTags);
onTagUpdate(newTags);
};
const onChangeColor = (color: ColorResult) => {
setColor(color.hex as TagType["color"]);

View File

@@ -5,6 +5,57 @@ toc_max_heading_level: 2
# 更新日志
## v2.2.0
### 🚀 新功能
- Feature: 添加插件 Pydantic 相关使用方法 [@yanyongyu](https://github.com/yanyongyu) ([#2563](https://github.com/nonebot/nonebot2/pull/2563))
- Feature: 兼容 Pydantic v2 [@yanyongyu](https://github.com/yanyongyu) ([#2544](https://github.com/nonebot/nonebot2/pull/2544))
- Feature: 使用自定义配置加载替代 `pydantic-settings` [@yanyongyu](https://github.com/yanyongyu) ([#2521](https://github.com/nonebot/nonebot2/pull/2521))
- Feature: 带参数的 `RegexStr()` [@ProgramRipper](https://github.com/ProgramRipper) ([#2499](https://github.com/nonebot/nonebot2/pull/2499))
### 🐛 Bug 修复
- Fix: websockets 驱动器连接关闭 code 获取错误 [@yanyongyu](https://github.com/yanyongyu) ([#2537](https://github.com/nonebot/nonebot2/pull/2537))
- Fix: 修复 `echo` 发送空消息 [@yanyongyu](https://github.com/yanyongyu) ([#2525](https://github.com/nonebot/nonebot2/pull/2525))
- Fix: `MessageTemplate` 禁止访问私有属性 [@mnixry](https://github.com/mnixry) ([#2509](https://github.com/nonebot/nonebot2/pull/2509))
### 📝 文档
- Docs: 更新 Alconna 文档 [@lengmianzz](https://github.com/lengmianzz) ([#2568](https://github.com/nonebot/nonebot2/pull/2568))
- Docs: 添加产品赞助列表 [@yanyongyu](https://github.com/yanyongyu) ([#2566](https://github.com/nonebot/nonebot2/pull/2566))
- Docs: 修复表单标签状态更新 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#2558](https://github.com/nonebot/nonebot2/pull/2558))
- Docs: 添加 CITATION 文件 [@yanyongyu](https://github.com/yanyongyu) ([#2520](https://github.com/nonebot/nonebot2/pull/2520))
### 💫 杂项
- Plugin: 移除不再维护的几款插件 [@mnixry](https://github.com/mnixry) ([#2561](https://github.com/nonebot/nonebot2/pull/2561))
- CI: 更新 prettier 配置 [@StarHeartHunt](https://github.com/StarHeartHunt) ([#2546](https://github.com/nonebot/nonebot2/pull/2546))
- Plugin: 恢复删除的插件 `nonebot-plugin-eitherchoice` [@lgc2333](https://github.com/lgc2333) ([#2502](https://github.com/nonebot/nonebot2/pull/2502))
### 🍻 插件发布
- Plugin: 定时提醒 [@noneflow](https://github.com/noneflow) ([#2559](https://github.com/nonebot/nonebot2/pull/2559))
- Plugin: 黑名单插件 [@noneflow](https://github.com/noneflow) ([#2554](https://github.com/nonebot/nonebot2/pull/2554))
- Plugin: ChatGPT 聊天 [@noneflow](https://github.com/noneflow) ([#2556](https://github.com/nonebot/nonebot2/pull/2556))
- Plugin: BA模拟抽卡 [@noneflow](https://github.com/noneflow) ([#2550](https://github.com/nonebot/nonebot2/pull/2550))
- Plugin: 随机发送图片 [@noneflow](https://github.com/noneflow) ([#2548](https://github.com/nonebot/nonebot2/pull/2548))
- Plugin: 哪吒监控插件 [@noneflow](https://github.com/noneflow) ([#2552](https://github.com/nonebot/nonebot2/pull/2552))
- Plugin: SakuraFrp [@noneflow](https://github.com/noneflow) ([#2543](https://github.com/nonebot/nonebot2/pull/2543))
- Plugin: haruka_bot_red [@noneflow](https://github.com/noneflow) ([#2541](https://github.com/nonebot/nonebot2/pull/2541))
- Plugin: nonebot-plugin-gemini [@noneflow](https://github.com/noneflow) ([#2527](https://github.com/nonebot/nonebot2/pull/2527))
- Plugin: 最终台词 [@noneflow](https://github.com/noneflow) ([#2523](https://github.com/nonebot/nonebot2/pull/2523))
- Plugin: nonebot-plugin-nekoimage [@noneflow](https://github.com/noneflow) ([#2534](https://github.com/nonebot/nonebot2/pull/2534))
- Plugin: 谷歌Bard聊天 [@noneflow](https://github.com/noneflow) ([#2529](https://github.com/nonebot/nonebot2/pull/2529))
- Plugin: nonebot-plugin-mypower [@noneflow](https://github.com/noneflow) ([#2533](https://github.com/nonebot/nonebot2/pull/2533))
- Plugin: 文心一言4适配 [@noneflow](https://github.com/noneflow) ([#2516](https://github.com/nonebot/nonebot2/pull/2516))
- Plugin: 最佳平替 [@noneflow](https://github.com/noneflow) ([#2519](https://github.com/nonebot/nonebot2/pull/2519))
- Plugin: 随机MC图 [@noneflow](https://github.com/noneflow) ([#2512](https://github.com/nonebot/nonebot2/pull/2512))
- Plugin: nonebot_plugin_nikke [@noneflow](https://github.com/noneflow) ([#2508](https://github.com/nonebot/nonebot2/pull/2508))
- Plugin: nonebot-plugin-imagemaster [@noneflow](https://github.com/noneflow) ([#2504](https://github.com/nonebot/nonebot2/pull/2504))
- Plugin: Waiter 插件 [@noneflow](https://github.com/noneflow) ([#2506](https://github.com/nonebot/nonebot2/pull/2506))
- Plugin: AntiMonkey [@noneflow](https://github.com/noneflow) ([#2501](https://github.com/nonebot/nonebot2/pull/2501))
## v2.1.3
### 🐛 Bug 修复

View File

@@ -1,104 +0,0 @@
---
sidebar_position: 2
description: 填写与获取插件相关的信息
options:
menu:
- category: advanced
weight: 30
---
# 插件信息
NoneBot 是一个插件化的框架,可以通过加载插件来扩展功能。同时,我们也可以通过 NoneBot 的插件系统来获取相关信息,例如插件的名称、使用方法,用于收集帮助信息等。下面我们将介绍如何为插件添加元数据,以及如何获取插件信息。
## 插件元数据
在 NoneBot 中,插件 [`Plugin`](../api/plugin/plugin.md#Plugin) 对象中存储了插件系统所需要的一系列信息。包括插件的索引名称、插件模块、插件中的事件响应器、插件父子关系等。通常,只有插件开发者才需要关心这些信息,而插件使用者或者机器人用户想要看到的是插件使用方法等帮助信息。因此,我们可以为插件添加插件元数据 `PluginMetadata`,它允许插件开发者为插件添加一些额外的信息。这些信息编写于插件模块的顶层,可以直接通过源码查看,或者通过 NoneBot 插件系统获取收集到的信息,通过其他方式发送给机器人用户等。
现在,假设我们有一个插件 `example`, 它的模块结构如下:
```tree {4-6} title=Project
📦 awesome-bot
├── 📂 awesome_bot
│ └── 📂 plugins
| └── 📂 example
| ├── 📜 __init__.py
| └── 📜 config.py
├── 📜 pyproject.toml
└── 📜 README.md
```
我们需要在插件顶层模块 `example/__init__.py` 中添加插件元数据,如下所示:
```python {1,5-12} title=example/__init__.py
from nonebot.plugin import PluginMetadata
from .config import Config
__plugin_meta__ = PluginMetadata(
name="示例插件",
description="这是一个示例插件",
usage="没什么用",
type="application",
config=Config,
extra={},
)
```
我们可以看到,插件元数据 `PluginMetadata` 有三个基本属性:插件名称、插件描述、插件使用方法。除此之外,还有几个可选的属性(具体填写见[发布插件](../developer/plugin-publishing.mdx#填写插件元数据)章节):
- `type`:插件类别,发布插件必填。当前有效类别有:`library`(为其他插件编写提供功能),`application`(向机器人用户提供功能);
- `homepage`:插件项目主页,发布插件必填;
- `config`:插件的[配置类](../appendices/config.mdx#插件配置),如无配置类可不填;
- `supported_adapters`:支持的适配器模块名集合,若插件可以保证兼容所有适配器(即仅使用基本适配器功能)可不填写;
- `extra`:一个字典,可以用于存储任意信息。其他插件可以通过约定 `extra` 字典的键名来达成收集某些特殊信息的目的。
请注意,这里的**插件名称**是供使用者或机器人用户查看的,与插件索引名称无关。**插件索引名称(插件模块名称)**仅用于 NoneBot 插件系统**内部索引**。
## 获取插件信息
NoneBot 提供了多种获取插件对象的方法,例如获取当前所有已导入的插件:
```python
import nonebot
plugins: set[Plugin] = nonebot.get_loaded_plugins()
```
也可以通过插件索引名称获取插件对象:
```python
import nonebot
plugin: Plugin | None = nonebot.get_plugin("example")
```
或者通过模块路径获取插件对象:
```python
import nonebot
plugin: Plugin | None = nonebot.get_plugin_by_module_name("awesome_bot.plugins.example")
```
如果需要获取所有当前声明的插件名称(可能还未加载),可以使用 `get_available_plugin_names` 函数:
```python
import nonebot
plugin_names: set[str] = nonebot.get_available_plugin_names()
```
插件对象 `Plugin` 中包含了多个属性:
- `name`:插件索引名称
- `module`:插件模块
- `module_name`:插件模块路径
- `manager`:插件管理器
- `matcher`:插件中定义的事件响应器
- `parent_plugin`:插件的父插件
- `sub_plugins`:插件的子插件集合
- `metadata`:插件元数据
通过这些属性以及插件元数据,我们就可以收集所需要的插件信息了。

View File

@@ -1,134 +0,0 @@
---
sidebar_position: 9
description: 添加服务端路由规则
options:
menu:
- category: advanced
weight: 100
---
# 添加路由
在[驱动器](./driver.md)一节中,我们了解了驱动器的两种类型。既然驱动器可以作为服务端运行,那么我们就可以向驱动器添加路由规则,从而实现自定义的 API 接口等功能。在添加路由规则时,我们需要注意驱动器的类型,详情可以参考[选择驱动器](./driver.md#配置驱动器)。
NoneBot 中,我们可以通过两种途径向 ASGI 驱动器添加路由规则:
1. 通过 NoneBot 的兼容层建立路由规则。
2. 直接向 ASGI 应用添加路由规则。
这两种途径各有优劣,前者可以在各种服务端型驱动器下运行,但并不能直接使用 ASGI 应用框架提供的特性与功能;后者直接使用 ASGI 应用,更自由、功能完整,但只能在特定类型驱动器下运行。
在向驱动器添加路由规则时,我们需要注意驱动器是否为服务端类型,我们可以通过以下方式判断:
```python {3}
from nonebot import get_driver
from nonebot.drivers import ASGIMixin
can_use = isinstance(get_driver(), ASGIMixin)
```
## 通过兼容层添加路由
NoneBot 兼容层定义了两个数据类 `HTTPServerSetup` 和 `WebSocketServerSetup`,分别用于定义 HTTP 服务端和 WebSocket 服务端的路由规则。
### HTTP 路由
`HTTPServerSetup` 具有四个属性:
- `path`:路由路径,不支持特殊占位表达式。类型为 `URL`。
- `method`:请求方法。类型为 `str`。
- `name`:路由名称,不可重复。类型为 `str`。
- `handle_func`:路由处理函数。类型为 `Callable[[Request], Awaitable[Response]]`。
例如,我们添加一个 `/hello` 的路由,当请求方法为 `GET` 时,返回 `200 OK` 以及返回体信息:
```python
from nonebot import get_driver
from nonebot.drivers import URL, Request, Response, ASGIMixin, HTTPServerSetup
async def hello(request: Request) -> Response:
return Response(200, content="Hello, world!")
if isinstance((driver := get_driver()), ASGIMixin):
driver.setup_http_server(
HTTPServerSetup(
path=URL("/hello"),
method="GET",
name="hello",
handle_func=hello,
)
)
```
对于 `Request` 和 `Response` 的详细信息,可以参考 [API 文档](../api/drivers/index.md)。
### WebSocket 路由
`WebSocketServerSetup` 具有三个属性:
- `path`:路由路径,不支持特殊占位表达式。类型为 `URL`。
- `name`:路由名称,不可重复。类型为 `str`。
- `handle_func`:路由处理函数。类型为 `Callable[[WebSocket], Awaitable[Any]]`。
例如,我们添加一个 `/ws` 的路由,发送所有接收到的数据:
```python
from nonebot import get_driver
from nonebot.drivers import URL, ASGIMixin, WebSocket, WebSocketServerSetup
async def ws_handler(ws: WebSocket):
await ws.accept()
try:
while True:
data = await ws.receive()
await ws.send(data)
except WebSocketClosed as e:
# handle closed
...
finally:
with contextlib.suppress(Exception):
await websocket.close()
# do some cleanup
if isinstance((driver := get_driver()), ASGIMixin):
driver.setup_websocket_server(
WebSocketServerSetup(
path=URL("/ws"),
name="ws",
handle_func=ws_handler,
)
)
```
对于 `WebSocket` 的详细信息,可以参考 [API 文档](../api/drivers/index.md)。
## 使用 ASGI 应用添加路由
### 获取 ASGI 应用
NoneBot 服务端类型的驱动器具有两个属性 `server_app` 和 `asgi`,分别对应驱动框架应用和 ASGI 应用。通常情况下,这两个应用是同一个对象。我们可以通过 `get_app()` 方法快速获取:
```python
import nonebot
app = nonebot.get_app()
asgi = nonebot.get_asgi()
```
### 添加路由规则
在获取到了 ASGI 应用后,我们就可以直接使用 ASGI 应用框架提供的功能来添加路由规则了。这里我们以 [FastAPI](./driver.md#fastapi默认) 为例,演示如何添加路由规则。
在下面的代码中,我们添加了一个 `GET` 类型的 `/api` 路由,具体方法参考 [FastAPI 文档](https://fastapi.tiangolo.com/)。
```python
import nonebot
from fastapi import FastAPI
app: FastAPI = nonebot.get_app()
@app.get("/api")
async def custom_api():
return {"message": "Hello, world!"}
```

View File

@@ -1,862 +0,0 @@
---
sidebar_position: 0
description: nonebot.adapters 模块
---
# nonebot.adapters
本模块定义了协议适配基类,各协议请继承以下基类。
使用 [Driver.register_adapter](../drivers/index.md#Driver-register-adapter) 注册适配器。
## _abstract class_ `Bot(adapter, self_id)` {#Bot}
- **说明**
Bot 基类。
用于处理上报消息,并提供 API 调用接口。
- **参数**
- `adapter` ([Adapter](#Adapter)): 协议适配器实例
- `self_id` (str): 机器人 ID
### _instance-var_ `adapter` {#Bot-adapter}
- **类型:** [Adapter](#Adapter)
- **说明:** 协议适配器实例
### _instance-var_ `self_id` {#Bot-self-id}
- **类型:** str
- **说明:** 机器人 ID
### _property_ `type` {#Bot-type}
- **类型:** str
- **说明:** 协议适配器名称
### _property_ `config` {#Bot-config}
- **类型:** [Config](../config.md#Config)
- **说明:** 全局 NoneBot 配置
### _async method_ `call_api(api, **data)` {#Bot-call-api}
- **说明:** 调用机器人 API 接口,可以通过该函数或直接通过 bot 属性进行调用
- **参数**
- `api` (str): API 名称
- `**data` (Any): API 数据
- **返回**
- Any
- **用法**
```python
await bot.call_api("send_msg", message="hello world")
await bot.send_msg(message="hello world")
```
### _abstract async method_ `send(event, message, **kwargs)` {#Bot-send}
- **说明:** 调用机器人基础发送消息接口
- **参数**
- `event` ([Event](#Event)): 上报事件
- `message` (str | [Message](#Message) | [MessageSegment](#MessageSegment)): 要发送的消息
- `**kwargs` (Any): 任意额外参数
- **返回**
- Any
### _classmethod_ `on_calling_api(func)` {#Bot-on-calling-api}
- **说明**
调用 api 预处理。
钩子函数参数:
- bot: 当前 bot 对象
- api: 调用的 api 名称
- data: api 调用的参数字典
- **参数**
- `func` ([T_CallingAPIHook](../typing.md#T-CallingAPIHook))
- **返回**
- [T_CallingAPIHook](../typing.md#T-CallingAPIHook)
### _classmethod_ `on_called_api(func)` {#Bot-on-called-api}
- **说明**
调用 api 后处理。
钩子函数参数:
- bot: 当前 bot 对象
- exception: 调用 api 时发生的错误
- api: 调用的 api 名称
- data: api 调用的参数字典
- result: api 调用的返回
- **参数**
- `func` ([T_CalledAPIHook](../typing.md#T-CalledAPIHook))
- **返回**
- [T_CalledAPIHook](../typing.md#T-CalledAPIHook)
## _abstract class_ `Event(<auto>)` {#Event}
- **说明:** Event 基类。提供获取关键信息的方法,其余信息可直接获取。
- **参数**
auto
### _classmethod_ `validate(value)` {#Event-validate}
- **参数**
- `value` (Any)
- **返回**
- E
### _abstract method_ `get_type()` {#Event-get-type}
- **说明:** 获取事件类型的方法,类型通常为 NoneBot 内置的四种类型。
- **参数**
empty
- **返回**
- str
### _abstract method_ `get_event_name()` {#Event-get-event-name}
- **说明:** 获取事件名称的方法。
- **参数**
empty
- **返回**
- str
### _abstract method_ `get_event_description()` {#Event-get-event-description}
- **说明:** 获取事件描述的方法,通常为事件具体内容。
- **参数**
empty
- **返回**
- str
### _method_ `get_log_string()` {#Event-get-log-string}
- **说明**
获取事件日志信息的方法。
通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,
可以抛出 `NoLogException` 异常。
- **参数**
empty
- **返回**
- str
- **异常**
- NoLogException: 希望 NoneBot 隐藏该事件日志
### _abstract method_ `get_user_id()` {#Event-get-user-id}
- **说明:** 获取事件主体 id 的方法,通常是用户 id 。
- **参数**
empty
- **返回**
- str
### _abstract method_ `get_session_id()` {#Event-get-session-id}
- **说明:** 获取会话 id 的方法,用于判断当前事件属于哪一个会话, 通常是用户 id、群组 id 组合。
- **参数**
empty
- **返回**
- str
### _abstract method_ `get_message()` {#Event-get-message}
- **说明:** 获取事件消息内容的方法。
- **参数**
empty
- **返回**
- [Message](#Message)
### _method_ `get_plaintext()` {#Event-get-plaintext}
- **说明**
获取消息纯文本的方法。
通常不需要修改,默认通过 `get_message().extract_plain_text` 获取。
- **参数**
empty
- **返回**
- str
### _abstract method_ `is_tome()` {#Event-is-tome}
- **说明:** 获取事件是否与机器人有关的方法。
- **参数**
empty
- **返回**
- bool
## _abstract class_ `Adapter(driver, **kwargs)` {#Adapter}
- **说明**
协议适配器基类。
通常,在 Adapter 中编写协议通信相关代码,如: 建立通信连接、处理接收与发送 data 等。
- **参数**
- `driver` ([Driver](../drivers/index.md#Driver)): [Driver](../drivers/index.md#Driver) 实例
- `**kwargs` (Any): 其他由 [Driver.register_adapter](../drivers/index.md#Driver-register-adapter) 传入的额外参数
### _instance-var_ `driver` {#Adapter-driver}
- **类型:** [Driver](../drivers/index.md#Driver)
- **说明:** 实例
### _instance-var_ `bots` {#Adapter-bots}
- **类型:** dict[str, [Bot](#Bot)]
- **说明:** 本协议适配器已建立连接的 [Bot](#Bot) 实例
### _abstract classmethod_ `get_name()` {#Adapter-get-name}
- **说明:** 当前协议适配器的名称
- **参数**
empty
- **返回**
- str
### _property_ `config` {#Adapter-config}
- **类型:** [Config](../config.md#Config)
- **说明:** 全局 NoneBot 配置
### _method_ `bot_connect(bot)` {#Adapter-bot-connect}
- **说明**
告知 NoneBot 建立了一个新的 [Bot](#Bot) 连接。
当有新的 [Bot](#Bot) 实例连接建立成功时调用。
- **参数**
- `bot` ([Bot](#Bot)): [Bot](#Bot) 实例
- **返回**
- None
### _method_ `bot_disconnect(bot)` {#Adapter-bot-disconnect}
- **说明**
告知 NoneBot [Bot](#Bot) 连接已断开。
当有 [Bot](#Bot) 实例连接断开时调用。
- **参数**
- `bot` ([Bot](#Bot)): [Bot](#Bot) 实例
- **返回**
- None
### _method_ `setup_http_server(setup)` {#Adapter-setup-http-server}
- **说明:** 设置一个 HTTP 服务器路由配置
- **参数**
- `setup` ([HTTPServerSetup](../drivers/index.md#HTTPServerSetup))
- **返回**
- untyped
### _method_ `setup_websocket_server(setup)` {#Adapter-setup-websocket-server}
- **说明:** 设置一个 WebSocket 服务器路由配置
- **参数**
- `setup` ([WebSocketServerSetup](../drivers/index.md#WebSocketServerSetup))
- **返回**
- untyped
### _async method_ `request(setup)` {#Adapter-request}
- **说明:** 进行一个 HTTP 客户端请求
- **参数**
- `setup` ([Request](../drivers/index.md#Request))
- **返回**
- [Response](../drivers/index.md#Response)
### _method_ `websocket(setup)` {#Adapter-websocket}
- **说明:** 建立一个 WebSocket 客户端连接请求
- **参数**
- `setup` ([Request](../drivers/index.md#Request))
- **返回**
- AsyncGenerator[[WebSocket](../drivers/index.md#WebSocket), None]
## _abstract class_ `Message(<auto>)` {#Message}
- **说明:** 消息序列
- **参数**
- `message`: 消息内容
### _classmethod_ `template(format_string)` {#Message-template}
- **说明**
创建消息模板。
用法和 `str.format` 大致相同,支持以 `Message` 对象作为消息模板并输出消息对象。
并且提供了拓展的格式化控制符,
可以通过该消息类型的 `MessageSegment` 工厂方法创建消息。
- **参数**
- `format_string` (str | TM): 格式化模板
- **返回**
- [MessageTemplate](#MessageTemplate)[Self]: 消息格式化器
### _abstract classmethod_ `get_segment_class()` {#Message-get-segment-class}
- **说明:** 获取消息段类型
- **参数**
empty
- **返回**
- type[TMS]
### _abstract staticmethod_ `_construct(msg)` {#Message--construct}
- **说明:** 构造消息数组
- **参数**
- `msg` (str)
- **返回**
- Iterable[TMS]
### _method_ `__getitem__(args)` {#Message---getitem--}
- **重载**
**1.** `(args) -> Self`
- **参数**
- `args` (str): 消息段类型
- **返回**
- Self: 所有类型为 `args` 的消息段
**2.** `(args) -> TMS`
- **参数**
- `args` (tuple[str, int]): 消息段类型和索引
- **返回**
- TMS: 类型为 `args[0]` 的消息段第 `args[1]` 个
**3.** `(args) -> Self`
- **参数**
- `args` (tuple[str, slice]): 消息段类型和切片
- **返回**
- Self: 类型为 `args[0]` 的消息段切片 `args[1]`
**4.** `(args) -> TMS`
- **参数**
- `args` (int): 索引
- **返回**
- TMS: 第 `args` 个消息段
**5.** `(args) -> Self`
- **参数**
- `args` (slice): 切片
- **返回**
- Self: 消息切片 `args`
### _method_ `__contains__(value)` {#Message---contains--}
- **说明:** 检查消息段是否存在
- **参数**
- `value` (TMS | str): 消息段或消息段类型
- **返回**
- bool: 消息内是否存在给定消息段或给定类型的消息段
### _method_ `has(value)` {#Message-has}
- **说明:** 与 [`__contains__`](#Message---contains--) 相同
- **参数**
- `value` (TMS | str)
- **返回**
- bool
### _method_ `index(value, *args)` {#Message-index}
- **说明:** 索引消息段
- **参数**
- `value` (TMS | str): 消息段或者消息段类型
- `*args` (SupportsIndex)
- `arg`: start 与 end
- **返回**
- int: 索引 index
- **异常**
- ValueError: 消息段不存在
### _method_ `get(type_, count=None)` {#Message-get}
- **说明:** 获取指定类型的消息段
- **参数**
- `type_` (str): 消息段类型
- `count` (int | None): 获取个数
- **返回**
- Self: 构建的新消息
### _method_ `count(value)` {#Message-count}
- **说明:** 计算指定消息段的个数
- **参数**
- `value` (TMS | str): 消息段或消息段类型
- **返回**
- int: 个数
### _method_ `only(value)` {#Message-only}
- **说明:** 检查消息中是否仅包含指定消息段
- **参数**
- `value` (TMS | str): 指定消息段或消息段类型
- **返回**
- bool: 是否仅包含指定消息段
### _method_ `append(obj)` {#Message-append}
- **说明:** 添加一个消息段到消息数组末尾。
- **参数**
- `obj` (str | TMS): 要添加的消息段
- **返回**
- Self
### _method_ `extend(obj)` {#Message-extend}
- **说明:** 拼接一个消息数组或多个消息段到消息数组末尾。
- **参数**
- `obj` (Self | Iterable[TMS]): 要添加的消息数组
- **返回**
- Self
### _method_ `join(iterable)` {#Message-join}
- **说明:** 将多个消息连接并将自身作为分割
- **参数**
- `iterable` (Iterable[TMS | Self]): 要连接的消息
- **返回**
- Self: 连接后的消息
### _method_ `copy()` {#Message-copy}
- **说明:** 深拷贝消息
- **参数**
empty
- **返回**
- Self
### _method_ `include(*types)` {#Message-include}
- **说明:** 过滤消息
- **参数**
- `*types` (str): 包含的消息段类型
- **返回**
- Self: 新构造的消息
### _method_ `exclude(*types)` {#Message-exclude}
- **说明:** 过滤消息
- **参数**
- `*types` (str): 不包含的消息段类型
- **返回**
- Self: 新构造的消息
### _method_ `extract_plain_text()` {#Message-extract-plain-text}
- **说明:** 提取消息内纯文本消息
- **参数**
empty
- **返回**
- str
## _abstract class_ `MessageSegment(<auto>)` {#MessageSegment}
- **说明:** 消息段基类
- **参数**
auto
### _instance-var_ `type` {#MessageSegment-type}
- **类型:** str
- **说明:** 消息段类型
### _class-var_ `data` {#MessageSegment-data}
- **类型:** dict[str, Any]
- **说明:** 消息段数据
### _abstract classmethod_ `get_message_class()` {#MessageSegment-get-message-class}
- **说明:** 获取消息数组类型
- **参数**
empty
- **返回**
- type[TM]
### _abstract method_ `__str__()` {#MessageSegment---str--}
- **说明:** 该消息段所代表的 str在命令匹配部分使用
- **参数**
empty
- **返回**
- str
### _method_ `__add__(other)` {#MessageSegment---add--}
- **参数**
- `other` (str | TMS | Iterable[TMS])
- **返回**
- TM
### _method_ `get(key, default=None)` {#MessageSegment-get}
- **参数**
- `key` (str)
- `default` (Any)
- **返回**
- untyped
### _method_ `keys()` {#MessageSegment-keys}
- **参数**
empty
- **返回**
- untyped
### _method_ `values()` {#MessageSegment-values}
- **参数**
empty
- **返回**
- untyped
### _method_ `items()` {#MessageSegment-items}
- **参数**
empty
- **返回**
- untyped
### _method_ `join(iterable)` {#MessageSegment-join}
- **参数**
- `iterable` (Iterable[TMS | TM])
- **返回**
- TM
### _method_ `copy()` {#MessageSegment-copy}
- **参数**
empty
- **返回**
- Self
### _abstract method_ `is_text()` {#MessageSegment-is-text}
- **说明:** 当前消息段是否为纯文本
- **参数**
empty
- **返回**
- bool
## _class_ `MessageTemplate(template, factory=str)` {#MessageTemplate}
- **说明:** 消息模板格式化实现类。
- **参数**
- `template` (str | TM): 模板
- `factory` (type[str] | type[TM]): 消息类型工厂,默认为 `str`
### _method_ `add_format_spec(spec, name=None)` {#MessageTemplate-add-format-spec}
- **参数**
- `spec` (FormatSpecFunc_T)
- `name` (str | None)
- **返回**
- FormatSpecFunc_T
### _method_ `format(*args, **kwargs)` {#MessageTemplate-format}
- **说明:** 根据传入参数和模板生成消息对象
- **参数**
- `*args`
- `**kwargs`
- **返回**
- untyped
### _method_ `format_map(mapping)` {#MessageTemplate-format-map}
- **说明:** 根据传入字典和模板生成消息对象, 在传入字段名不是有效标识符时有用
- **参数**
- `mapping` (Mapping[str, Any])
- **返回**
- TF
### _method_ `vformat(format_string, args, kwargs)` {#MessageTemplate-vformat}
- **参数**
- `format_string` (str)
- `args` (Sequence[Any])
- `kwargs` (Mapping[str, Any])
- **返回**
- TF
### _method_ `format_field(value, format_spec)` {#MessageTemplate-format-field}
- **参数**
- `value` (Any)
- `format_spec` (str)
- **返回**
- Any

View File

@@ -1,168 +0,0 @@
---
sidebar_position: 1
description: nonebot.config 模块
---
# nonebot.config
本模块定义了 NoneBot 本身运行所需的配置项。
NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及
[`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。
配置项需符合特殊格式或 json 序列化格式
详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
## _class_ `Env(<auto>)` {#Env}
- **说明**
运行环境配置。大小写不敏感。
将会从 **环境变量** > **dotenv 配置文件** 的优先级读取环境信息。
- **参数**
auto
### _class-var_ `environment` {#Env-environment}
- **类型:** str
- **说明**
当前环境名。
NoneBot 将从 `.env.{environment}` 文件中加载配置。
## _class_ `Config(<auto>)` {#Config}
- **说明**
NoneBot 主要配置。大小写不敏感。
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
这些配置将会在 json 反序列化后一起带入 `Config` 类中。
配置方法参考: [配置](https://nonebot.dev/docs/appendices/config)
- **参数**
auto
### _class-var_ `driver` {#Config-driver}
- **类型:** str
- **说明**
NoneBot 运行所使用的 `Driver` 。继承自 [Driver](drivers/index.md#Driver) 。
配置格式为 `<module>[:<Driver>][+<module>[:<Mixin>]]*`
`~``nonebot.drivers.` 的缩写。
配置方法参考: [配置驱动器](https://nonebot.dev/docs/advanced/driver#%E9%85%8D%E7%BD%AE%E9%A9%B1%E5%8A%A8%E5%99%A8)
### _class-var_ `host` {#Config-host}
- **类型:** IPvAnyAddress
- **说明:** NoneBot [ReverseDriver](drivers/index.md#ReverseDriver) 服务端监听的 IP/主机名。
### _class-var_ `port` {#Config-port}
- **类型:** int
- **说明:** NoneBot [ReverseDriver](drivers/index.md#ReverseDriver) 服务端监听的端口。
### _class-var_ `log_level` {#Config-log-level}
- **类型:** int | str
- **说明**
NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称。
参考 [记录日志](https://nonebot.dev/docs/appendices/log)[loguru 日志等级](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
:::tip 提示
日志等级名称应为大写,如 `INFO`
:::
- **用法**
```conf
LOG_LEVEL=25
LOG_LEVEL=INFO
```
### _class-var_ `api_timeout` {#Config-api-timeout}
- **类型:** float | None
- **说明:** API 请求超时时间,单位: 秒。
### _class-var_ `superusers` {#Config-superusers}
- **类型:** set[str]
- **说明:** 机器人超级用户。
- **用法**
```conf
SUPERUSERS=["12345789"]
```
### _class-var_ `nickname` {#Config-nickname}
- **类型:** set[str]
- **说明:** 机器人昵称。
### _class-var_ `command_start` {#Config-command-start}
- **类型:** set[str]
- **说明**
命令的起始标记,用于判断一条消息是不是命令。
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
- **用法**
```conf
COMMAND_START=["/", ""]
```
### _class-var_ `command_sep` {#Config-command-sep}
- **类型:** set[str]
- **说明**
命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
- **用法**
```conf
COMMAND_SEP=["."]
```
### _class-var_ `session_expire_timeout` {#Config-session-expire-timeout}
- **类型:** timedelta
- **说明:** 等待用户回复的超时时间。
- **用法**
```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
```

View File

@@ -1,98 +0,0 @@
---
sidebar_position: 0
description: nonebot.dependencies 模块
---
# nonebot.dependencies
本模块模块实现了依赖注入的定义与处理。
## _abstract class_ `Param(*args, validate=False, **kwargs)` {#Param}
- **说明**
依赖注入的基本单元 —— 参数。
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
- **参数**
- `*args`
- `validate` (bool)
- `**kwargs` (Any)
## _class_ `Dependent(<auto>)` {#Dependent}
- **说明:** 依赖注入容器
- **参数**
- `call`: 依赖注入的可调用对象,可以是任何 Callable 对象
- `pre_checkers`: 依赖注入解析前的参数检查
- `params`: 具名参数列表
- `parameterless`: 匿名参数列表
- `allow_types`: 允许的参数类型
### _staticmethod_ `parse_params(call, allow_types)` {#Dependent-parse-params}
- **参数**
- `call` (\_DependentCallable[R])
- `allow_types` (tuple[type[Param], ...])
- **返回**
- tuple[ModelField, ...]
### _staticmethod_ `parse_parameterless(parameterless, allow_types)` {#Dependent-parse-parameterless}
- **参数**
- `parameterless` (tuple[Any, ...])
- `allow_types` (tuple[type[Param], ...])
- **返回**
- tuple[Param, ...]
### _classmethod_ `parse(*, call, parameterless=None, allow_types)` {#Dependent-parse}
- **参数**
- `call` (\_DependentCallable[R])
- `parameterless` (Iterable[Any] | None)
- `allow_types` (Iterable[type[Param]])
- **返回**
- Dependent[R]
### _async method_ `check(**params)` {#Dependent-check}
- **参数**
- `**params` (Any)
- **返回**
- None
### _async method_ `solve(**params)` {#Dependent-solve}
- **参数**
- `**params` (Any)
- **返回**
- dict[str, Any]

View File

@@ -1,46 +0,0 @@
---
sidebar_position: 1
description: nonebot.dependencies.utils 模块
---
# nonebot.dependencies.utils
## _def_ `get_typed_signature(call)` {#get-typed-signature}
- **说明:** 获取可调用对象签名
- **参数**
- `call` ((...) -> Any)
- **返回**
- inspect.Signature
## _def_ `get_typed_annotation(param, globalns)` {#get-typed-annotation}
- **说明:** 获取参数的类型注解
- **参数**
- `param` (inspect.Parameter)
- `globalns` (dict[str, Any])
- **返回**
- Any
## _def_ `check_field_type(field, value)` {#check-field-type}
- **说明:** 检查字段类型是否匹配
- **参数**
- `field` (ModelField)
- `value` (Any)
- **返回**
- Any

View File

@@ -1,47 +0,0 @@
# nonebot.drivers.\_lifespan
## _class_ `Lifespan()` {#Lifespan}
- **参数**
empty
### _method_ `on_startup(func)` {#Lifespan-on-startup}
- **参数**
- `func` (LIFESPAN_FUNC)
- **返回**
- LIFESPAN_FUNC
### _method_ `on_shutdown(func)` {#Lifespan-on-shutdown}
- **参数**
- `func` (LIFESPAN_FUNC)
- **返回**
- LIFESPAN_FUNC
### _async method_ `startup()` {#Lifespan-startup}
- **参数**
empty
- **返回**
- None
### _async method_ `shutdown()` {#Lifespan-shutdown}
- **参数**
empty
- **返回**
- None

View File

@@ -1,572 +0,0 @@
---
sidebar_position: 0
description: nonebot.drivers 模块
---
# nonebot.drivers
本模块定义了驱动适配器基类。
各驱动请继承以下基类。
## _abstract class_ `Mixin(<auto>)` {#Mixin}
- **说明:** 可与其他驱动器共用的混入基类。
- **参数**
auto
### _abstract property_ `type` {#Mixin-type}
- **类型:** str
- **说明:** 混入驱动类型名称
## _abstract class_ `Driver(env, config)` {#Driver}
- **说明**
驱动器基类。
驱动器控制框架的启动和停止,适配器的注册,以及机器人生命周期管理。
- **参数**
- `env` ([Env](../config.md#Env)): 包含环境信息的 Env 对象
- `config` ([Config](../config.md#Config)): 包含配置信息的 Config 对象
### _instance-var_ `env` {#Driver-env}
- **类型:** str
- **说明:** 环境名称
### _instance-var_ `config` {#Driver-config}
- **类型:** [Config](../config.md#Config)
- **说明:** 全局配置对象
### _property_ `bots` {#Driver-bots}
- **类型:** dict[str, [Bot](../adapters/index.md#Bot)]
- **说明:** 获取当前所有已连接的 Bot
### _method_ `register_adapter(adapter, **kwargs)` {#Driver-register-adapter}
- **说明:** 注册一个协议适配器
- **参数**
- `adapter` (type[[Adapter](../adapters/index.md#Adapter)]): 适配器类
- `**kwargs`: 其他传递给适配器的参数
- **返回**
- None
### _abstract property_ `type` {#Driver-type}
- **类型:** str
- **说明:** 驱动类型名称
### _abstract property_ `logger` {#Driver-logger}
- **类型:** untyped
- **说明:** 驱动专属 logger 日志记录器
### _abstract method_ `run(*args, **kwargs)` {#Driver-run}
- **说明:** 启动驱动框架
- **参数**
- `*args`
- `**kwargs`
- **返回**
- untyped
### _abstract method_ `on_startup(func)` {#Driver-on-startup}
- **说明:** 注册一个在驱动器启动时执行的函数
- **参数**
- `func` (Callable)
- **返回**
- Callable
### _abstract method_ `on_shutdown(func)` {#Driver-on-shutdown}
- **说明:** 注册一个在驱动器停止时执行的函数
- **参数**
- `func` (Callable)
- **返回**
- Callable
### _classmethod_ `on_bot_connect(func)` {#Driver-on-bot-connect}
- **说明**
装饰一个函数使他在 bot 连接成功时执行。
钩子函数参数:
- bot: 当前连接上的 Bot 对象
- **参数**
- `func` ([T_BotConnectionHook](../typing.md#T-BotConnectionHook))
- **返回**
- [T_BotConnectionHook](../typing.md#T-BotConnectionHook)
### _classmethod_ `on_bot_disconnect(func)` {#Driver-on-bot-disconnect}
- **说明**
装饰一个函数使他在 bot 连接断开时执行。
钩子函数参数:
- bot: 当前连接上的 Bot 对象
- **参数**
- `func` ([T_BotDisconnectionHook](../typing.md#T-BotDisconnectionHook))
- **返回**
- [T_BotDisconnectionHook](../typing.md#T-BotDisconnectionHook)
## _class_ `Cookies(cookies=None)` {#Cookies}
- **参数**
- `cookies` (CookieTypes)
### _method_ `set(name, value, domain="", path="/")` {#Cookies-set}
- **参数**
- `name` (str)
- `value` (str)
- `domain` (str)
- `path` (str)
- **返回**
- None
### _method_ `get(name, default=None, domain=None, path=None)` {#Cookies-get}
- **参数**
- `name` (str)
- `default` (str | None)
- `domain` (str | None)
- `path` (str | None)
- **返回**
- str | None
### _method_ `delete(name, domain=None, path=None)` {#Cookies-delete}
- **参数**
- `name` (str)
- `domain` (str | None)
- `path` (str | None)
- **返回**
- None
### _method_ `clear(domain=None, path=None)` {#Cookies-clear}
- **参数**
- `domain` (str | None)
- `path` (str | None)
- **返回**
- None
### _method_ `update(cookies=None)` {#Cookies-update}
- **参数**
- `cookies` (CookieTypes)
- **返回**
- None
### _method_ `as_header(request)` {#Cookies-as-header}
- **参数**
- `request` (Request)
- **返回**
- dict[str, str]
## _class_ `Request(method, url, *, params=None, headers=None, cookies=None, content=None, data=None, json=None, files=None, version=HTTPVersion.H11, timeout=None, proxy=None)` {#Request}
- **参数**
- `method` (str | bytes)
- `url` (URL | str | RawURL)
- `params` (QueryTypes)
- `headers` (HeaderTypes)
- `cookies` (CookieTypes)
- `content` (ContentTypes)
- `data` (DataTypes)
- `json` (Any)
- `files` (FilesTypes)
- `version` (str | HTTPVersion)
- `timeout` (float | None)
- `proxy` (str | None)
## _class_ `Response(status_code, *, headers=None, content=None, request=None)` {#Response}
- **参数**
- `status_code` (int)
- `headers` (HeaderTypes)
- `content` (ContentTypes)
- `request` (Request | None)
## _abstract class_ `ASGIMixin(<auto>)` {#ASGIMixin}
- **说明**
ASGI 服务端基类。
将后端框架封装,以满足适配器使用。
- **参数**
auto
### _abstract property_ `server_app` {#ASGIMixin-server-app}
- **类型:** Any
- **说明:** 驱动 APP 对象
### _abstract property_ `asgi` {#ASGIMixin-asgi}
- **类型:** Any
- **说明:** 驱动 ASGI 对象
### _abstract method_ `setup_http_server(setup)` {#ASGIMixin-setup-http-server}
- **说明:** 设置一个 HTTP 服务器路由配置
- **参数**
- `setup` ([HTTPServerSetup](#HTTPServerSetup))
- **返回**
- None
### _abstract method_ `setup_websocket_server(setup)` {#ASGIMixin-setup-websocket-server}
- **说明:** 设置一个 WebSocket 服务器路由配置
- **参数**
- `setup` ([WebSocketServerSetup](#WebSocketServerSetup))
- **返回**
- None
## _abstract class_ `WebSocket(*, request)` {#WebSocket}
- **参数**
- `request` (Request)
### _abstract property_ `closed` {#WebSocket-closed}
- **类型:** bool
- **说明:** 连接是否已经关闭
### _abstract async method_ `accept()` {#WebSocket-accept}
- **说明:** 接受 WebSocket 连接请求
- **参数**
empty
- **返回**
- None
### _abstract async method_ `close(code=1000, reason="")` {#WebSocket-close}
- **说明:** 关闭 WebSocket 连接请求
- **参数**
- `code` (int)
- `reason` (str)
- **返回**
- None
### _abstract async method_ `receive()` {#WebSocket-receive}
- **说明:** 接收一条 WebSocket text/bytes 信息
- **参数**
empty
- **返回**
- str | bytes
### _abstract async method_ `receive_text()` {#WebSocket-receive-text}
- **说明:** 接收一条 WebSocket text 信息
- **参数**
empty
- **返回**
- str
### _abstract async method_ `receive_bytes()` {#WebSocket-receive-bytes}
- **说明:** 接收一条 WebSocket binary 信息
- **参数**
empty
- **返回**
- bytes
### _async method_ `send(data)` {#WebSocket-send}
- **说明:** 发送一条 WebSocket text/bytes 信息
- **参数**
- `data` (str | bytes)
- **返回**
- None
### _abstract async method_ `send_text(data)` {#WebSocket-send-text}
- **说明:** 发送一条 WebSocket text 信息
- **参数**
- `data` (str)
- **返回**
- None
### _abstract async method_ `send_bytes(data)` {#WebSocket-send-bytes}
- **说明:** 发送一条 WebSocket binary 信息
- **参数**
- `data` (bytes)
- **返回**
- None
## _enum_ `HTTPVersion` {#HTTPVersion}
- **说明:** An enumeration.
- **参数**
auto
- `H10: '1.0'`
- `H11: '1.1'`
- `H2: '2'`
## _abstract class_ `ForwardMixin(<auto>)` {#ForwardMixin}
- **说明:** 客户端混入基类。
- **参数**
auto
## _abstract class_ `ReverseMixin(<auto>)` {#ReverseMixin}
- **说明:** 服务端混入基类。
- **参数**
auto
## _var_ `ForwardDriver` {#ForwardDriver}
- **类型:** ForwardMixin
- **说明**
支持客户端请求的驱动器。
**Deprecated**,请使用 [ForwardMixin](#ForwardMixin) 或其子类代替。
## _var_ `ReverseDriver` {#ReverseDriver}
- **类型:** ReverseMixin
- **说明**
支持服务端请求的驱动器。
**Deprecated**,请使用 [ReverseMixin](#ReverseMixin) 或其子类代替。
## _def_ `combine_driver(driver, *mixins)` {#combine-driver}
- **说明:** 将一个驱动器和多个混入类合并。
- **重载**
**1.** `(driver) -> type[D]`
- **参数**
- `driver` (type[D])
- **返回**
- type[D]
**2.** `(driver, *mixins) -> type[CombinedDriver]`
- **参数**
- `driver` (type[D])
- `*mixins` (type[Mixin])
- **返回**
- type[CombinedDriver]
## _abstract class_ `HTTPClientMixin(<auto>)` {#HTTPClientMixin}
- **说明:** HTTP 客户端混入基类。
- **参数**
auto
### _abstract async method_ `request(setup)` {#HTTPClientMixin-request}
- **说明:** 发送一个 HTTP 请求
- **参数**
- `setup` ([Request](#Request))
- **返回**
- [Response](#Response)
## _class_ `HTTPServerSetup(<auto>)` {#HTTPServerSetup}
- **说明:** HTTP 服务器路由配置。
- **参数**
auto
## _abstract class_ `WebSocketClientMixin(<auto>)` {#WebSocketClientMixin}
- **说明:** WebSocket 客户端混入基类。
- **参数**
auto
### _abstract method_ `websocket(setup)` {#WebSocketClientMixin-websocket}
- **说明:** 发起一个 WebSocket 连接
- **参数**
- `setup` ([Request](#Request))
- **返回**
- AsyncGenerator[[WebSocket](#WebSocket), None]
## _class_ `WebSocketServerSetup(<auto>)` {#WebSocketServerSetup}
- **说明:** WebSocket 服务器路由配置。
- **参数**
auto

View File

@@ -1,84 +0,0 @@
---
sidebar_position: 6
description: nonebot.drivers.none 模块
---
# nonebot.drivers.none
None 驱动适配
:::tip 提示
本驱动不支持任何服务器或客户端连接
:::
## _class_ `Driver(env, config)` {#Driver}
- **说明:** None 驱动框架
- **参数**
- `env` ([Env](../config.md#Env))
- `config` ([Config](../config.md#Config))
### _property_ `type` {#Driver-type}
- **类型:** str
- **说明:** 驱动名称: `none`
### _property_ `logger` {#Driver-logger}
- **类型:** untyped
- **说明:** none driver 使用的 logger
### _method_ `on_startup(func)` {#Driver-on-startup}
- **说明:** 注册一个启动时执行的函数
- **参数**
- `func` (LIFESPAN_FUNC)
- **返回**
- LIFESPAN_FUNC
### _method_ `on_shutdown(func)` {#Driver-on-shutdown}
- **说明:** 注册一个停止时执行的函数
- **参数**
- `func` (LIFESPAN_FUNC)
- **返回**
- LIFESPAN_FUNC
### _method_ `run(*args, **kwargs)` {#Driver-run}
- **说明:** 启动 none driver
- **参数**
- `*args`
- `**kwargs`
- **返回**
- untyped
### _method_ `exit(force=False)` {#Driver-exit}
- **说明:** 退出 none driver
- **参数**
- `force` (bool): 强制退出
- **返回**
- untyped

View File

@@ -1,283 +0,0 @@
---
sidebar_position: 0
description: nonebot 模块
---
# nonebot
本模块主要定义了 NoneBot 启动所需函数,供 bot 入口文件调用。
## 快捷导入
为方便使用,本模块从子模块导入了部分内容,以下内容可以直接通过本模块导入:
- `on` => [`on`](plugin/on.md#on)
- `on_metaevent` => [`on_metaevent`](plugin/on.md#on-metaevent)
- `on_message` => [`on_message`](plugin/on.md#on-message)
- `on_notice` => [`on_notice`](plugin/on.md#on-notice)
- `on_request` => [`on_request`](plugin/on.md#on-request)
- `on_startswith` => [`on_startswith`](plugin/on.md#on-startswith)
- `on_endswith` => [`on_endswith`](plugin/on.md#on-endswith)
- `on_fullmatch` => [`on_fullmatch`](plugin/on.md#on-fullmatch)
- `on_keyword` => [`on_keyword`](plugin/on.md#on-keyword)
- `on_command` => [`on_command`](plugin/on.md#on-command)
- `on_shell_command` => [`on_shell_command`](plugin/on.md#on-shell-command)
- `on_regex` => [`on_regex`](plugin/on.md#on-regex)
- `on_type` => [`on_type`](plugin/on.md#on-type)
- `CommandGroup` => [`CommandGroup`](plugin/on.md#CommandGroup)
- `Matchergroup` => [`MatcherGroup`](plugin/on.md#MatcherGroup)
- `load_plugin` => [`load_plugin`](plugin/load.md#load-plugin)
- `load_plugins` => [`load_plugins`](plugin/load.md#load-plugins)
- `load_all_plugins` => [`load_all_plugins`](plugin/load.md#load-all-plugins)
- `load_from_json` => [`load_from_json`](plugin/load.md#load-from-json)
- `load_from_toml` => [`load_from_toml`](plugin/load.md#load-from-toml)
- `load_builtin_plugin` =>
[`load_builtin_plugin`](plugin/load.md#load-builtin-plugin)
- `load_builtin_plugins` =>
[`load_builtin_plugins`](plugin/load.md#load-builtin-plugins)
- `get_plugin` => [`get_plugin`](plugin/index.md#get-plugin)
- `get_plugin_by_module_name` =>
[`get_plugin_by_module_name`](plugin/index.md#get-plugin-by-module-name)
- `get_loaded_plugins` =>
[`get_loaded_plugins`](plugin/index.md#get-loaded-plugins)
- `get_available_plugin_names` =>
[`get_available_plugin_names`](plugin/index.md#get-available-plugin-names)
- `require` => [`require`](plugin/load.md#require)
## _def_ `get_driver()` {#get-driver}
- **说明**
获取全局 [Driver](drivers/index.md#Driver) 实例。
可用于在计划任务的回调等情形中获取当前 [Driver](drivers/index.md#Driver) 实例。
- **参数**
empty
- **返回**
- [Driver](drivers/index.md#Driver): 全局 [Driver](drivers/index.md#Driver) 对象
- **异常**
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
- **用法**
```python
driver = nonebot.get_driver()
```
## _def_ `get_adapter(name)` {#get-adapter}
- **说明:** 获取已注册的 [Adapter](adapters/index.md#Adapter) 实例。
- **重载**
**1.** `(name) -> Adapter`
- **参数**
- `name` (str): 适配器名称
- **返回**
- [Adapter](adapters/index.md#Adapter): 指定名称的 [Adapter](adapters/index.md#Adapter) 对象
**2.** `(name) -> A`
- **参数**
- `name` (type[A]): 适配器类型
- **返回**
- A: 指定类型的 [Adapter](adapters/index.md#Adapter) 对象
- **异常**
- ValueError: 指定的 [Adapter](adapters/index.md#Adapter) 未注册
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
- **用法**
```python
from nonebot.adapters.console import Adapter
adapter = nonebot.get_adapter(Adapter)
```
## _def_ `get_adapters()` {#get-adapters}
- **说明:** 获取所有已注册的 [Adapter](adapters/index.md#Adapter) 实例。
- **参数**
empty
- **返回**
- dict[str, [Adapter](adapters/index.md#Adapter)]: 所有 [Adapter](adapters/index.md#Adapter) 实例字典
- **异常**
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
- **用法**
```python
adapters = nonebot.get_adapters()
```
## _def_ `get_app()` {#get-app}
- **说明:** 获取全局 [ASGIMixin](drivers/index.md#ASGIMixin) 对应的 Server App 对象。
- **参数**
empty
- **返回**
- Any: Server App 对象
- **异常**
- AssertionError: 全局 Driver 对象不是 [ASGIMixin](drivers/index.md#ASGIMixin) 类型
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
- **用法**
```python
app = nonebot.get_app()
```
## _def_ `get_asgi()` {#get-asgi}
- **说明:** 获取全局 [ASGIMixin](drivers/index.md#ASGIMixin) 对应 [ASGI](https://asgi.readthedocs.io/) 对象。
- **参数**
empty
- **返回**
- Any: ASGI 对象
- **异常**
- AssertionError: 全局 Driver 对象不是 [ASGIMixin](drivers/index.md#ASGIMixin) 类型
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
- **用法**
```python
asgi = nonebot.get_asgi()
```
## _def_ `get_bot(self_id=None)` {#get-bot}
- **说明**
获取一个连接到 NoneBot 的 [Bot](adapters/index.md#Bot) 对象。
当提供 `self_id` 时,此函数是 `get_bots()[self_id]` 的简写;
当不提供时,返回一个 [Bot](adapters/index.md#Bot)。
- **参数**
- `self_id` (str | None): 用来识别 [Bot](adapters/index.md#Bot) 的 [Bot.self_id](adapters/index.md#Bot-self-id) 属性
- **返回**
- [Bot](adapters/index.md#Bot): [Bot](adapters/index.md#Bot) 对象
- **异常**
- KeyError: 对应 self_id 的 Bot 不存在
- ValueError: 没有传入 self_id 且没有 Bot 可用
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
- **用法**
```python
assert nonebot.get_bot("12345") == nonebot.get_bots()["12345"]
another_unspecified_bot = nonebot.get_bot()
```
## _def_ `get_bots()` {#get-bots}
- **说明:** 获取所有连接到 NoneBot 的 [Bot](adapters/index.md#Bot) 对象。
- **参数**
empty
- **返回**
- dict[str, [Bot](adapters/index.md#Bot)]: 一个以 [Bot.self_id](adapters/index.md#Bot-self-id) 为键
[Bot](adapters/index.md#Bot) 对象为值的字典
- **异常**
- ValueError: 全局 [Driver](drivers/index.md#Driver) 对象尚未初始化 ([nonebot.init](#init) 尚未调用)
- **用法**
```python
bots = nonebot.get_bots()
```
## _def_ `init(*, _env_file=None, **kwargs)` {#init}
- **说明**
初始化 NoneBot 以及 全局 [Driver](drivers/index.md#Driver) 对象。
NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。
也可以传入自定义的 `_env_file` 来指定 NoneBot 从该文件读取配置。
- **参数**
- `_env_file` (DotenvType | None): 配置文件名,默认从 `.env.{env_name}` 中读取配置
- `**kwargs` (Any): 任意变量,将会存储到 [Driver.config](drivers/index.md#Driver-config) 对象里
- **返回**
- None
- **用法**
```python
nonebot.init(database=Database(...))
```
## _def_ `run(*args, **kwargs)` {#run}
- **说明:** 启动 NoneBot即运行全局 [Driver](drivers/index.md#Driver) 对象。
- **参数**
- `*args` (Any): 传入 [Driver.run](drivers/index.md#Driver-run) 的位置参数
- `**kwargs` (Any): 传入 [Driver.run](drivers/index.md#Driver-run) 的命名参数
- **返回**
- None
- **用法**
```python
nonebot.run(host="127.0.0.1", port=8080)
```

View File

@@ -1,749 +0,0 @@
---
sidebar_position: 3
description: nonebot.matcher 模块
---
# nonebot.matcher
本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。
## _class_ `Matcher()` {#Matcher}
- **说明:** 事件响应器类
- **参数**
empty
### _instance-var_ `handlers` {#Matcher-handlers}
- **类型:** list[[Dependent](dependencies/index.md#Dependent)[Any]]
- **说明:** 事件响应器拥有的事件处理函数列表
### _class-var_ `type` {#Matcher-type}
- **类型:** ClassVar[str]
- **说明:** 事件响应器类型
### _class-var_ `rule` {#Matcher-rule}
- **类型:** ClassVar[[Rule](rule.md#Rule)]
- **说明:** 事件响应器匹配规则
### _class-var_ `permission` {#Matcher-permission}
- **类型:** ClassVar[[Permission](permission.md#Permission)]
- **说明:** 事件响应器触发权限
### _class-var_ `priority` {#Matcher-priority}
- **类型:** ClassVar[int]
- **说明:** 事件响应器优先级
### _class-var_ `block` {#Matcher-block}
- **类型:** bool
- **说明:** 事件响应器是否阻止事件传播
### _class-var_ `temp` {#Matcher-temp}
- **类型:** ClassVar[bool]
- **说明:** 事件响应器是否为临时
### _class-var_ `expire_time` {#Matcher-expire-time}
- **类型:** ClassVar[datetime | None]
- **说明:** 事件响应器过期时间点
### _classmethod_ `new(type_="", rule=None, permission=None, handlers=None, temp=False, priority=1, block=False, *, plugin=None, module=None, source=None, expire_time=None, default_state=None, default_type_updater=None, default_permission_updater=None)` {#Matcher-new}
- **说明:** 创建一个新的事件响应器,并存储至 `matchers <#matchers>`\_
- **参数**
- `type_` (str): 事件响应器类型,与 `event.get_type()` 一致时触发,空字符串表示任意
- `rule` ([Rule](rule.md#Rule) | None): 匹配规则
- `permission` ([Permission](permission.md#Permission) | None): 权限
- `handlers` (list[[T\_Handler](typing.md#T-Handler) | [Dependent](dependencies/index.md#Dependent)[Any]] | None): 事件处理函数列表
- `temp` (bool): 是否为临时事件响应器,即触发一次后删除
- `priority` (int): 响应优先级
- `block` (bool): 是否阻止事件向更低优先级的响应器传播
- `plugin` ([Plugin](plugin/plugin.md#Plugin) | None): **Deprecated.** 事件响应器所在插件
- `module` (ModuleType | None): **Deprecated.** 事件响应器所在模块
- `source` (MatcherSource | None): 事件响应器源代码上下文信息
- `expire_time` (datetime | timedelta | None): 事件响应器最终有效时间点,过时即被删除
- `default_state` ([T_State](typing.md#T-State) | None): 默认状态 `state`
- `default_type_updater` ([T_TypeUpdater](typing.md#T-TypeUpdater) | [Dependent](dependencies/index.md#Dependent)[str] | None): 默认事件类型更新函数
- `default_permission_updater` ([T_PermissionUpdater](typing.md#T-PermissionUpdater) | [Dependent](dependencies/index.md#Dependent)[[Permission](permission.md#Permission)] | None): 默认会话权限更新函数
- **返回**
- type[Matcher]: 新的事件响应器类
### _classmethod_ `destroy()` {#Matcher-destroy}
- **说明:** 销毁当前的事件响应器
- **参数**
empty
- **返回**
- None
### _classmethod_ `check_perm(bot, event, stack=None, dependency_cache=None)` {#Matcher-check-perm}
- **说明:** 检查是否满足触发权限
- **参数**
- `bot` ([Bot](adapters/index.md#Bot)): Bot 对象
- `event` ([Event](adapters/index.md#Event)): 上报事件
- `stack` (AsyncExitStack | None): 异步上下文栈
- `dependency_cache` ([T_DependencyCache](typing.md#T-DependencyCache) | None): 依赖缓存
- **返回**
- bool: 是否满足权限
### _classmethod_ `check_rule(bot, event, state, stack=None, dependency_cache=None)` {#Matcher-check-rule}
- **说明:** 检查是否满足匹配规则
- **参数**
- `bot` ([Bot](adapters/index.md#Bot)): Bot 对象
- `event` ([Event](adapters/index.md#Event)): 上报事件
- `state` ([T_State](typing.md#T-State)): 当前状态
- `stack` (AsyncExitStack | None): 异步上下文栈
- `dependency_cache` ([T_DependencyCache](typing.md#T-DependencyCache) | None): 依赖缓存
- **返回**
- bool: 是否满足匹配规则
### _classmethod_ `type_updater(func)` {#Matcher-type-updater}
- **说明:** 装饰一个函数来更改当前事件响应器的默认响应事件类型更新函数
- **参数**
- `func` ([T_TypeUpdater](typing.md#T-TypeUpdater)): 响应事件类型更新函数
- **返回**
- [T_TypeUpdater](typing.md#T-TypeUpdater)
### _classmethod_ `permission_updater(func)` {#Matcher-permission-updater}
- **说明:** 装饰一个函数来更改当前事件响应器的默认会话权限更新函数
- **参数**
- `func` ([T_PermissionUpdater](typing.md#T-PermissionUpdater)): 会话权限更新函数
- **返回**
- [T_PermissionUpdater](typing.md#T-PermissionUpdater)
### _classmethod_ `append_handler(handler, parameterless=None)` {#Matcher-append-handler}
- **参数**
- `handler` ([T_Handler](typing.md#T-Handler))
- `parameterless` (Iterable[Any] | None)
- **返回**
- [Dependent](dependencies/index.md#Dependent)[Any]
### _classmethod_ `handle(parameterless=None)` {#Matcher-handle}
- **说明:** 装饰一个函数来向事件响应器直接添加一个处理函数
- **参数**
- `parameterless` (Iterable[Any] | None): 非参数类型依赖列表
- **返回**
- ([T_Handler](typing.md#T-Handler)) -> [T_Handler](typing.md#T-Handler)
### _classmethod_ `receive(id="", parameterless=None)` {#Matcher-receive}
- **说明:** 装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
- **参数**
- `id` (str): 消息 ID
- `parameterless` (Iterable[Any] | None): 非参数类型依赖列表
- **返回**
- ([T_Handler](typing.md#T-Handler)) -> [T_Handler](typing.md#T-Handler)
### _classmethod_ `got(key, prompt=None, parameterless=None)` {#Matcher-got}
- **说明**
装饰一个函数来指示 NoneBot 获取一个参数 `key`
当要获取的 `key` 不存在时接收用户新的一条消息再运行该函数,
如果 `key` 已存在则直接继续运行
- **参数**
- `key` (str): 参数名
- `prompt` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate) | None): 在参数不存在时向用户发送的消息
- `parameterless` (Iterable[Any] | None): 非参数类型依赖列表
- **返回**
- ([T_Handler](typing.md#T-Handler)) -> [T_Handler](typing.md#T-Handler)
### _classmethod_ `send(message, **kwargs)` {#Matcher-send}
- **说明:** 发送一条消息给当前交互用户
- **参数**
- `message` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate)): 消息内容
- `**kwargs` (Any): [Bot.send](adapters/index.md#Bot-send) 的参数, 请参考对应 adapter 的 bot 对象 api
- **返回**
- Any
### _classmethod_ `finish(message=None, **kwargs)` {#Matcher-finish}
- **说明:** 发送一条消息给当前交互用户并结束当前事件响应器
- **参数**
- `message` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate) | None): 消息内容
- `**kwargs`: [Bot.send](adapters/index.md#Bot-send) 的参数, 请参考对应 adapter 的 bot 对象 api
- **返回**
- NoReturn
### _classmethod_ `pause(prompt=None, **kwargs)` {#Matcher-pause}
- **说明:** 发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
- **参数**
- `prompt` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate) | None): 消息内容
- `**kwargs`: [Bot.send](adapters/index.md#Bot-send) 的参数, 请参考对应 adapter 的 bot 对象 api
- **返回**
- NoReturn
### _classmethod_ `reject(prompt=None, **kwargs)` {#Matcher-reject}
- **说明:** 最近使用 `got` / `receive` 接收的消息不符合预期, 发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一个事件后从头开始执行当前处理函数
- **参数**
- `prompt` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate) | None): 消息内容
- `**kwargs`: [Bot.send](adapters/index.md#Bot-send) 的参数, 请参考对应 adapter 的 bot 对象 api
- **返回**
- NoReturn
### _classmethod_ `reject_arg(key, prompt=None, **kwargs)` {#Matcher-reject-arg}
- **说明:** 最近使用 `got` 接收的消息不符合预期, 发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一条消息后从头开始执行当前处理函数
- **参数**
- `key` (str): 参数名
- `prompt` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate) | None): 消息内容
- `**kwargs`: [Bot.send](adapters/index.md#Bot-send) 的参数, 请参考对应 adapter 的 bot 对象 api
- **返回**
- NoReturn
### _classmethod_ `reject_receive(id="", prompt=None, **kwargs)` {#Matcher-reject-receive}
- **说明:** 最近使用 `receive` 接收的消息不符合预期, 发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一个事件后从头开始执行当前处理函数
- **参数**
- `id` (str): 消息 id
- `prompt` (str | [Message](adapters/index.md#Message) | [MessageSegment](adapters/index.md#MessageSegment) | [MessageTemplate](adapters/index.md#MessageTemplate) | None): 消息内容
- `**kwargs`: [Bot.send](adapters/index.md#Bot-send) 的参数, 请参考对应 adapter 的 bot 对象 api
- **返回**
- NoReturn
### _classmethod_ `skip()` {#Matcher-skip}
- **说明**
跳过当前事件处理函数,继续下一个处理函数
通常在事件处理函数的依赖中使用。
- **参数**
empty
- **返回**
- NoReturn
### _method_ `get_receive(id, default=None)` {#Matcher-get-receive}
- **说明**
获取一个 `receive` 事件
如果没有找到对应的事件,返回 `default`
- **重载**
**1.** `(id) -> Event | None`
- **参数**
- `id` (str)
- **返回**
- [Event](adapters/index.md#Event) | None
**2.** `(id, default) -> Event | T`
- **参数**
- `id` (str)
- `default` (T)
- **返回**
- [Event](adapters/index.md#Event) | T
### _method_ `set_receive(id, event)` {#Matcher-set-receive}
- **说明:** 设置一个 `receive` 事件
- **参数**
- `id` (str)
- `event` ([Event](adapters/index.md#Event))
- **返回**
- None
### _method_ `get_last_receive(default=None)` {#Matcher-get-last-receive}
- **说明**
获取最近一次 `receive` 事件
如果没有事件,返回 `default`
- **重载**
**1.** `() -> Event | None`
- **参数**
empty
- **返回**
- [Event](adapters/index.md#Event) | None
**2.** `(default) -> Event | T`
- **参数**
- `default` (T)
- **返回**
- [Event](adapters/index.md#Event) | T
### _method_ `get_arg(key, default=None)` {#Matcher-get-arg}
- **说明**
获取一个 `got` 消息
如果没有找到对应的消息,返回 `default`
- **重载**
**1.** `(key) -> Message | None`
- **参数**
- `key` (str)
- **返回**
- [Message](adapters/index.md#Message) | None
**2.** `(key, default) -> Message | T`
- **参数**
- `key` (str)
- `default` (T)
- **返回**
- [Message](adapters/index.md#Message) | T
### _method_ `set_arg(key, message)` {#Matcher-set-arg}
- **说明:** 设置一个 `got` 消息
- **参数**
- `key` (str)
- `message` ([Message](adapters/index.md#Message))
- **返回**
- None
### _method_ `set_target(target, cache=True)` {#Matcher-set-target}
- **参数**
- `target` (str)
- `cache` (bool)
- **返回**
- None
### _method_ `get_target(default=None)` {#Matcher-get-target}
- **重载**
**1.** `() -> str | None`
- **参数**
empty
- **返回**
- str | None
**2.** `(default) -> str | T`
- **参数**
- `default` (T)
- **返回**
- str | T
### _method_ `stop_propagation()` {#Matcher-stop-propagation}
- **说明:** 阻止事件传播
- **参数**
empty
- **返回**
- untyped
### _async method_ `update_type(bot, event, stack=None, dependency_cache=None)` {#Matcher-update-type}
- **参数**
- `bot` ([Bot](adapters/index.md#Bot))
- `event` ([Event](adapters/index.md#Event))
- `stack` (AsyncExitStack | None)
- `dependency_cache` ([T_DependencyCache](typing.md#T-DependencyCache) | None)
- **返回**
- str
### _async method_ `update_permission(bot, event, stack=None, dependency_cache=None)` {#Matcher-update-permission}
- **参数**
- `bot` ([Bot](adapters/index.md#Bot))
- `event` ([Event](adapters/index.md#Event))
- `stack` (AsyncExitStack | None)
- `dependency_cache` ([T_DependencyCache](typing.md#T-DependencyCache) | None)
- **返回**
- [Permission](permission.md#Permission)
### _async method_ `resolve_reject()` {#Matcher-resolve-reject}
- **参数**
empty
- **返回**
- untyped
### _method_ `ensure_context(bot, event)` {#Matcher-ensure-context}
- **参数**
- `bot` ([Bot](adapters/index.md#Bot))
- `event` ([Event](adapters/index.md#Event))
- **返回**
- untyped
### _async method_ `simple_run(bot, event, state, stack=None, dependency_cache=None)` {#Matcher-simple-run}
- **参数**
- `bot` ([Bot](adapters/index.md#Bot))
- `event` ([Event](adapters/index.md#Event))
- `state` ([T_State](typing.md#T-State))
- `stack` (AsyncExitStack | None)
- `dependency_cache` ([T_DependencyCache](typing.md#T-DependencyCache) | None)
- **返回**
- untyped
### _async method_ `run(bot, event, state, stack=None, dependency_cache=None)` {#Matcher-run}
- **参数**
- `bot` ([Bot](adapters/index.md#Bot))
- `event` ([Event](adapters/index.md#Event))
- `state` ([T_State](typing.md#T-State))
- `stack` (AsyncExitStack | None)
- `dependency_cache` ([T_DependencyCache](typing.md#T-DependencyCache) | None)
- **返回**
- untyped
## _var_ `matchers` {#matchers}
- **类型:** untyped
## _class_ `MatcherManager()` {#MatcherManager}
- **说明**
事件响应器管理器
实现了常用字典操作,用于管理事件响应器。
- **参数**
empty
### _method_ `keys()` {#MatcherManager-keys}
- **参数**
empty
- **返回**
- KeysView[int]
### _method_ `values()` {#MatcherManager-values}
- **参数**
empty
- **返回**
- ValuesView[list[type[[Matcher](#Matcher)]]]
### _method_ `items()` {#MatcherManager-items}
- **参数**
empty
- **返回**
- ItemsView[int, list[type[[Matcher](#Matcher)]]]
### _method_ `get(key, default=None)` {#MatcherManager-get}
- **重载**
**1.** `(key) -> list[type[Matcher]] | None`
- **参数**
- `key` (int)
- **返回**
- list[type[[Matcher](#Matcher)]] | None
**2.** `(key, default) -> list[type[Matcher]] | T`
- **参数**
- `key` (int)
- `default` (T)
- **返回**
- list[type[[Matcher](#Matcher)]] | T
### _method_ `pop(key)` {#MatcherManager-pop}
- **参数**
- `key` (int)
- **返回**
- list[type[[Matcher](#Matcher)]]
### _method_ `popitem()` {#MatcherManager-popitem}
- **参数**
empty
- **返回**
- tuple[int, list[type[[Matcher](#Matcher)]]]
### _method_ `clear()` {#MatcherManager-clear}
- **参数**
empty
- **返回**
- None
### _method_ `update(__m)` {#MatcherManager-update}
- **参数**
- `__m` (MutableMapping[int, list[type[[Matcher](#Matcher)]]])
- **返回**
- None
### _method_ `setdefault(key, default)` {#MatcherManager-setdefault}
- **参数**
- `key` (int)
- `default` (list[type[[Matcher](#Matcher)]])
- **返回**
- list[type[[Matcher](#Matcher)]]
### _method_ `set_provider(provider_class)` {#MatcherManager-set-provider}
- **说明:** 设置事件响应器存储器
- **参数**
- `provider_class` (type[[MatcherProvider](#MatcherProvider)]): 事件响应器存储器类
- **返回**
- None
## _abstract class_ `MatcherProvider(matchers)` {#MatcherProvider}
- **说明:** 事件响应器存储器基类
- **参数**
- `matchers` (Mapping[int, list[type[[Matcher](#Matcher)]]]): 当前存储器中已有的事件响应器
## _var_ `DEFAULT_PROVIDER_CLASS` {#DEFAULT-PROVIDER-CLASS}
- **类型:** untyped
- **说明:** 默认存储器类型

View File

@@ -1,95 +0,0 @@
---
sidebar_position: 0
description: nonebot.plugin 模块
---
# nonebot.plugin
本模块为 NoneBot 插件开发提供便携的定义函数。
## 快捷导入
为方便使用,本模块从子模块导入了部分内容,以下内容可以直接通过本模块导入:
- `on` => [`on`](on.md#on)
- `on_metaevent` => [`on_metaevent`](on.md#on-metaevent)
- `on_message` => [`on_message`](on.md#on-message)
- `on_notice` => [`on_notice`](on.md#on-notice)
- `on_request` => [`on_request`](on.md#on-request)
- `on_startswith` => [`on_startswith`](on.md#on-startswith)
- `on_endswith` => [`on_endswith`](on.md#on-endswith)
- `on_fullmatch` => [`on_fullmatch`](on.md#on-fullmatch)
- `on_keyword` => [`on_keyword`](on.md#on-keyword)
- `on_command` => [`on_command`](on.md#on-command)
- `on_shell_command` => [`on_shell_command`](on.md#on-shell-command)
- `on_regex` => [`on_regex`](on.md#on-regex)
- `on_type` => [`on_type`](on.md#on-type)
- `CommandGroup` => [`CommandGroup`](on.md#CommandGroup)
- `Matchergroup` => [`MatcherGroup`](on.md#MatcherGroup)
- `load_plugin` => [`load_plugin`](load.md#load-plugin)
- `load_plugins` => [`load_plugins`](load.md#load-plugins)
- `load_all_plugins` => [`load_all_plugins`](load.md#load-all-plugins)
- `load_from_json` => [`load_from_json`](load.md#load-from-json)
- `load_from_toml` => [`load_from_toml`](load.md#load-from-toml)
- `load_builtin_plugin` =>
[`load_builtin_plugin`](load.md#load-builtin-plugin)
- `load_builtin_plugins` =>
[`load_builtin_plugins`](load.md#load-builtin-plugins)
- `require` => [`require`](load.md#require)
- `PluginMetadata` => [`PluginMetadata`](plugin.md#PluginMetadata)
## _def_ `get_plugin(name)` {#get-plugin}
- **说明**
获取已经导入的某个插件。
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
- **参数**
- `name` (str): 插件名,即 [Plugin.name](plugin.md#Plugin-name)。
- **返回**
- [Plugin](plugin.md#Plugin) | None
## _def_ `get_plugin_by_module_name(module_name)` {#get-plugin-by-module-name}
- **说明**
通过模块名获取已经导入的某个插件。
如果提供的模块名为某个插件的子模块,同样会返回该插件。
- **参数**
- `module_name` (str): 模块名,即 [Plugin.module_name](plugin.md#Plugin-module-name)。
- **返回**
- [Plugin](plugin.md#Plugin) | None
## _def_ `get_loaded_plugins()` {#get-loaded-plugins}
- **说明:** 获取当前已导入的所有插件。
- **参数**
empty
- **返回**
- set[[Plugin](plugin.md#Plugin)]
## _def_ `get_available_plugin_names()` {#get-available-plugin-names}
- **说明:** 获取当前所有可用的插件名(包含尚未加载的插件)。
- **参数**
empty
- **返回**
- set[str]

Some files were not shown because too many files have changed in this diff Show More