mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-10-07 19:26:44 +00:00
Compare commits
328 Commits
v2.2.0
...
publish/is
Author | SHA1 | Date | |
---|---|---|---|
|
d8c36e8eff | ||
|
1cc5d1af33 | ||
|
88074cf5c3 | ||
|
5d637eed95 | ||
|
362c43ce5f | ||
|
622b8eb51e | ||
|
c369dcf781 | ||
|
53d1e1dee9 | ||
|
75f5825cff | ||
|
d05c90787c | ||
|
e07ba36a4a | ||
|
f7c05d9a08 | ||
|
59c5a1a35d | ||
|
3eb653821e | ||
|
214bc838c2 | ||
|
79c7ea5bab | ||
|
b59b1be6ff | ||
|
aeb75a6ce3 | ||
|
847325a119 | ||
|
26eabfaf6f | ||
|
40a7b97220 | ||
|
91b40748c4 | ||
|
013a2f94d6 | ||
|
74d280ed75 | ||
|
b7d46de10e | ||
|
c37b5bbbca | ||
|
5e08e73698 | ||
|
b27bb92d03 | ||
|
6bf8858cc6 | ||
|
c97a780645 | ||
|
976c1cd8e0 | ||
|
26fd6f8a6c | ||
|
0020ad28ba | ||
|
ba9ca63f10 | ||
|
28b5b732c2 | ||
|
b944da8445 | ||
|
5cab166d6b | ||
|
546cdb4229 | ||
|
77790fad1f | ||
|
bcf849c98f | ||
|
f7b3c8af02 | ||
|
cced60589c | ||
|
62adb32c94 | ||
|
6ab752dcdb | ||
|
4d6f071739 | ||
|
bd140c2ceb | ||
|
59d9991aa4 | ||
|
55e7f59e40 | ||
|
bb83483020 | ||
|
5300ef5119 | ||
|
5a50d4203c | ||
|
01a96f3086 | ||
|
0570d779ee | ||
|
18d0bc2c81 | ||
|
87e0d8148f | ||
|
53d8989145 | ||
|
5433b4ebdf | ||
|
f10cecf16a | ||
|
60a3f6f4cc | ||
|
f70ae89098 | ||
|
2f60c5e9b4 | ||
|
015ddd9517 | ||
|
f1539d9ec4 | ||
|
2d0444ba75 | ||
|
ed2c222e83 | ||
|
ed048913a4 | ||
|
121ba17698 | ||
|
d0f5a76c47 | ||
|
f809f1d089 | ||
|
070ad18781 | ||
|
56119ef1cc | ||
|
30195a35dc | ||
|
0500b7baab | ||
|
08473a5c25 | ||
|
37ad14c277 | ||
|
3e8c6ce541 | ||
|
3dd5539dc7 | ||
|
559a0320a8 | ||
|
8646d885f0 | ||
|
84c008cdce | ||
|
2671cb5b72 | ||
|
379440708f | ||
|
4d070f5b48 | ||
|
82138454bc | ||
|
d98fe53d56 | ||
|
278b9e92c2 | ||
|
45418ccfae | ||
|
2ad2922565 | ||
|
84ebcb4ce6 | ||
|
6a0caacfd6 | ||
|
a8f3940cbc | ||
|
15d3910462 | ||
|
edfd0eb887 | ||
|
fe63717848 | ||
|
63424bc3ac | ||
|
99b1d0ed96 | ||
|
90c7fd4747 | ||
|
c1a9758a18 | ||
|
17e7a0c029 | ||
|
df6a948c08 | ||
|
9f19eb7a96 | ||
|
2b68428526 | ||
|
d62c6561c2 | ||
|
fc3bb5ff1f | ||
|
76b1bbb443 | ||
|
7b724925ba | ||
|
62dc2574c7 | ||
|
ea40ae3a18 | ||
|
f94e7d9b5b | ||
|
c8ba973280 | ||
|
35e062c588 | ||
|
53724487d3 | ||
|
a3003b0ff6 | ||
|
96ecd415cd | ||
|
e8ef4735ea | ||
|
b78b08ed81 | ||
|
e11ea52276 | ||
|
819e7334b2 | ||
|
1ebafaa9a5 | ||
|
3554292d5f | ||
|
ec9ef9a760 | ||
|
74663c7c5e | ||
|
cbc99be031 | ||
|
81e9bdd7ec | ||
|
323038ecc6 | ||
|
7091beb809 | ||
|
010c48d30f | ||
|
a5b2dd38d5 | ||
|
fa5f295fe7 | ||
|
7f7b23bd2f | ||
|
0434e12b8a | ||
|
425d140161 | ||
|
64d8f7843a | ||
|
a0a6427540 | ||
|
31fe8e6582 | ||
|
38e42919b7 | ||
|
c769f95688 | ||
|
d642897a5b | ||
|
d7931f8ec2 | ||
|
8a0b989718 | ||
|
4fbbb646c3 | ||
|
75856e63f6 | ||
|
98213f50db | ||
|
5bce1db24e | ||
|
380ace5780 | ||
|
6e5b01a3d4 | ||
|
622e8e8af3 | ||
|
2bbb83d3f2 | ||
|
54756134d4 | ||
|
932b212e04 | ||
|
3b40e5b20c | ||
|
f594db207d | ||
|
70e23427e8 | ||
|
c1a303fd3d | ||
|
a62b9a5e1a | ||
|
36eece311a | ||
|
29ea5f5787 | ||
|
c00e3aacfc | ||
|
cf9f78528c | ||
|
68d4795de6 | ||
|
e689d7f7d2 | ||
|
a607f868c2 | ||
|
84ac1c4bad | ||
|
e11ff528e2 | ||
|
047f4d1878 | ||
|
0294c33baf | ||
|
11a8b6e40b | ||
|
cade86b62a | ||
|
df836ec1c6 | ||
|
12cc00a3d3 | ||
|
24aa81f0be | ||
|
339706a3a6 | ||
|
b43c9adb7a | ||
|
c2783039d4 | ||
|
c4706e4123 | ||
|
8a997540b3 | ||
|
045022b22a | ||
|
723fa4b3d8 | ||
|
41b59cff06 | ||
|
bed1b46527 | ||
|
ad695ca6e8 | ||
|
33e997708c | ||
|
56b6ee1d38 | ||
|
27b2cf52a5 | ||
|
b532130f6e | ||
|
d16b8594ad | ||
|
ad8442c6de | ||
|
4edf7e2c2c | ||
|
ea49318809 | ||
|
a9a86aba61 | ||
|
6e95d5366c | ||
|
445711e1cb | ||
|
dfd2096fe5 | ||
|
d469c6f287 | ||
|
9655b941f3 | ||
|
4254fdfd8c | ||
|
1b3cd7e2e2 | ||
|
897498b7f5 | ||
|
34770e4463 | ||
|
9d14f72249 | ||
|
87f6e81ffc | ||
|
c3373e141a | ||
|
a5f2d97b04 | ||
|
80ac6a5ae9 | ||
|
496475e0ca | ||
|
982dbbccdf | ||
|
3f9c20c60b | ||
|
cabb3c6c45 | ||
|
03bf1fdcfe | ||
|
f36f8d1bcc | ||
|
5c2c1770a2 | ||
|
6810af1e1d | ||
|
78ba6ce973 | ||
|
15bcb7e374 | ||
|
7dd7ccbff5 | ||
|
5b17c8de71 | ||
|
5cf4ff66a3 | ||
|
b6be8a178e | ||
|
b77c3b2d0c | ||
|
e4a210b47c | ||
|
6bf10aafb7 | ||
|
e15d544341 | ||
|
acdb5787db | ||
|
18f0c9b500 | ||
|
b36e721274 | ||
|
9fdc50cd0e | ||
|
41abf077bc | ||
|
27a4e5a55b | ||
|
65f6a104e9 | ||
|
415bd07c0d | ||
|
3fd26dd937 | ||
|
f5f5d93b64 | ||
|
b497bb8c83 | ||
|
b0d554eacb | ||
|
cbecc7b930 | ||
|
5e0921aca9 | ||
|
7e8015e828 | ||
|
bef5bdf0bf | ||
|
c04cd5e83e | ||
|
30d3c1bbce | ||
|
5e72461391 | ||
|
54fdf71d91 | ||
|
420d0cfdc4 | ||
|
84bfba7a82 | ||
|
9fd89a6822 | ||
|
4a02dde83f | ||
|
e93ee1ffec | ||
|
e2b6fb12c7 | ||
|
7836073c7e | ||
|
3119626d89 | ||
|
19bebdd923 | ||
|
0b0dd8b552 | ||
|
47ce7a633f | ||
|
ca32f68787 | ||
|
0b972ad302 | ||
|
9b4b1526b1 | ||
|
7a232c7a4a | ||
|
983351f0b7 | ||
|
16fb5ac121 | ||
|
bb1fbca4a7 | ||
|
b7c0b6b8e0 | ||
|
485aa62755 | ||
|
53e2a86dd9 | ||
|
312095d1df | ||
|
b498be1092 | ||
|
211ea8427f | ||
|
407eb69568 | ||
|
8a44b4d6ee | ||
|
bc58fbb741 | ||
|
0c977f5fd7 | ||
|
7eeccbcb14 | ||
|
020d2a5687 | ||
|
83d61fcffd | ||
|
c0b222a5fa | ||
|
236e4ea9aa | ||
|
0622e16d18 | ||
|
159ca84e46 | ||
|
142a61ce5c | ||
|
7f226af541 | ||
|
8bf912499a | ||
|
a55b10cfa3 | ||
|
1a4f889b40 | ||
|
f9bc2de4e4 | ||
|
5a1c635083 | ||
|
76e8567f1e | ||
|
9bd349d933 | ||
|
5e8a67b605 | ||
|
e16799d500 | ||
|
a189846194 | ||
|
fcd536aada | ||
|
c89bafc2c9 | ||
|
f5855a9f9a | ||
|
a49b4bccc6 | ||
|
b434da29b1 | ||
|
514b3a5afe | ||
|
0d30f81ddb | ||
|
9a86c00f62 | ||
|
7648138902 | ||
|
2055f092f2 | ||
|
9ff7f4baba | ||
|
92ba99c34c | ||
|
876cff4daf | ||
|
1ac6a612b0 | ||
|
15ecad9f87 | ||
|
587d3f7c7e | ||
|
10e4ea6743 | ||
|
d1601bf2fe | ||
|
2994945c64 | ||
|
c9e3cad738 | ||
|
7c36964812 | ||
|
0e02d13c67 | ||
|
f7aeea2f3d | ||
|
b2da7d4cae | ||
|
239f9769c2 | ||
|
f5947518b1 | ||
|
1a4afa406b | ||
|
412b879f39 | ||
|
a830346545 | ||
|
fbb8320a25 | ||
|
14f4a0f701 | ||
|
e82e2817d5 | ||
|
ffbd1f9aeb | ||
|
5ab418a3cf | ||
|
a58e00b206 | ||
|
a74682bbf6 | ||
|
11142253fb | ||
|
ef7782167f | ||
|
f4a2682e6c |
1
.github/actions/setup-python/action.yml
vendored
1
.github/actions/setup-python/action.yml
vendored
@@ -25,7 +25,6 @@ runs:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
architecture: "x64"
|
||||
cache: "poetry"
|
||||
cache-dependency-path: |
|
||||
./poetry.lock
|
||||
|
4
.github/workflows/codecov.yml
vendored
4
.github/workflows/codecov.yml
vendored
@@ -23,11 +23,11 @@ jobs:
|
||||
group: test-coverage-${{ github.ref }}-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.env }}
|
||||
cancel-in-progress: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
env: [pydantic-v1, pydantic-v2]
|
||||
fail-fast: false
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
|
45
.github/workflows/noneflow.yml
vendored
45
.github/workflows/noneflow.yml
vendored
@@ -15,9 +15,10 @@ concurrency:
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
plugin_test:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
name: nonebot2 plugin test
|
||||
name: check
|
||||
# do not run on forked PRs, do not run on not related issues, do not run on pr comments
|
||||
if: |
|
||||
!(
|
||||
(
|
||||
@@ -35,6 +36,46 @@ jobs:
|
||||
github.event_name == 'issue_comment' && github.event.issue.pull_request
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- run: echo "Check passed"
|
||||
reaction:
|
||||
runs-on: ubuntu-latest
|
||||
name: reaction
|
||||
needs: check
|
||||
if: |
|
||||
(
|
||||
github.event_name == 'issue_comment' &&
|
||||
github.event.action == 'created'
|
||||
) ||
|
||||
(
|
||||
github.event_name == 'issues' &&
|
||||
github.event.action == 'opened'
|
||||
)
|
||||
steps:
|
||||
- name: Generate token
|
||||
id: generate-token
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.APP_ID }}
|
||||
private_key: ${{ secrets.APP_KEY }}
|
||||
|
||||
- name: Reaction on issue
|
||||
if: github.event_name == 'issues'
|
||||
run: |
|
||||
gh api --method POST /repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/reactions -f "content=rocket"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- name: Reaction on issue comment
|
||||
if: github.event_name == 'issue_comment'
|
||||
run: |
|
||||
gh api --method POST /repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions -f "content=rocket"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
plugin_test:
|
||||
runs-on: ubuntu-latest
|
||||
name: nonebot2 plugin test
|
||||
needs: check
|
||||
permissions:
|
||||
issues: read
|
||||
outputs:
|
||||
|
2
.github/workflows/website-deploy.yml
vendored
2
.github/workflows/website-deploy.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV
|
||||
|
||||
- name: Deploy to Netlify
|
||||
uses: nwtgck/actions-netlify@v2
|
||||
uses: nwtgck/actions-netlify@v3
|
||||
with:
|
||||
publish-dir: "./website/build"
|
||||
production-deploy: true
|
||||
|
99
.github/workflows/website-preview-cd.yml
vendored
Normal file
99
.github/workflows/website-preview-cd.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
name: Site Deploy (Preview CD)
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Site Deploy (Preview CI)"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
preview-cd:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: pull-request-preview-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
|
||||
cancel-in-progress: true
|
||||
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
|
||||
environment: pull request
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
statuses: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Set Commit Status
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: context.payload.workflow_run.head_sha,
|
||||
context: 'Website Preview',
|
||||
description: 'Deploying...',
|
||||
state: 'pending',
|
||||
})
|
||||
|
||||
- name: Download Artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: website-preview
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
- name: Restore Context
|
||||
run: |
|
||||
cat action.env >> $GITHUB_ENV
|
||||
|
||||
- name: Set Deploy Name
|
||||
run: |
|
||||
echo "DEPLOY_NAME=deploy-preview-${{ env.PR_NUMBER }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: nwtgck/actions-netlify@v3
|
||||
with:
|
||||
publish-dir: ./website/build
|
||||
production-deploy: false
|
||||
deploy-message: "Deploy ${{ env.DEPLOY_NAME }}@${{ github.event.workflow_run.head_sha }}"
|
||||
alias: ${{ env.DEPLOY_NAME }}
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.SITE_ID }}
|
||||
|
||||
# action netlify has no pull request context, so we need to comment by ourselves
|
||||
- name: Comment on Pull Request
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
header: website
|
||||
number: ${{ env.PR_NUMBER }}
|
||||
message: |
|
||||
:rocket: Deployed to ${{ steps.deploy.outputs.deploy-url }}
|
||||
|
||||
- name: Set Commit Status
|
||||
uses: actions/github-script@v7
|
||||
if: always()
|
||||
with:
|
||||
script: |
|
||||
if (`${{ job.status }}` === 'success') {
|
||||
github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: context.payload.workflow_run.head_sha,
|
||||
context: 'Website Preview',
|
||||
description: `Deployed to ${{ steps.deploy.outputs.deploy-url }}`,
|
||||
state: 'success',
|
||||
target_url: `${{ steps.deploy.outputs.deploy-url }}`,
|
||||
})
|
||||
} else {
|
||||
github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: context.payload.workflow_run.head_sha,
|
||||
context: 'Website Preview',
|
||||
description: `Deploy ${{ job.status }}`,
|
||||
state: 'failure',
|
||||
})
|
||||
}
|
42
.github/workflows/website-preview-ci.yml
vendored
Normal file
42
.github/workflows/website-preview-ci.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Site Deploy (Preview CI)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
preview-ci:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: pull-request-preview-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Python Environment
|
||||
uses: ./.github/actions/setup-python
|
||||
|
||||
- name: Setup Node Environment
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build API Doc
|
||||
uses: ./.github/actions/build-api-doc
|
||||
|
||||
- name: Build Doc
|
||||
run: yarn build
|
||||
|
||||
- name: Export Context
|
||||
run: |
|
||||
echo "PR_NUMBER=${{ github.event.number }}" >> ./action.env
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: website-preview
|
||||
path: |
|
||||
./website/build
|
||||
./action.env
|
||||
retention-days: 1
|
46
.github/workflows/website-preview.yml
vendored
46
.github/workflows/website-preview.yml
vendored
@@ -1,46 +0,0 @@
|
||||
name: Site Deploy(Preview)
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
preview:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: pull-request-preview-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Python Environment
|
||||
uses: ./.github/actions/setup-python
|
||||
|
||||
- name: Setup Node Environment
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build API Doc
|
||||
uses: ./.github/actions/build-api-doc
|
||||
|
||||
- name: Build Doc
|
||||
run: yarn build
|
||||
|
||||
- name: Get Deploy Name
|
||||
run: |
|
||||
echo "DEPLOY_NAME=deploy-preview-${{ github.event.number }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Deploy to Netlify
|
||||
uses: nwtgck/actions-netlify@v2
|
||||
with:
|
||||
publish-dir: "./website/build"
|
||||
production-deploy: false
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
deploy-message: "Deploy ${{ env.DEPLOY_NAME }}@${{ github.sha }}"
|
||||
enable-commit-comment: false
|
||||
alias: ${{ env.DEPLOY_NAME }}
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.SITE_ID }}
|
@@ -7,7 +7,7 @@ ci:
|
||||
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.2.0
|
||||
rev: v0.5.6
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
@@ -20,7 +20,7 @@ repos:
|
||||
stages: [commit]
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 24.1.1
|
||||
rev: 24.8.0
|
||||
hooks:
|
||||
- id: black
|
||||
stages: [commit]
|
||||
|
46
README.md
46
README.md
@@ -21,7 +21,7 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
||||
<a href="https://pypi.python.org/pypi/nonebot2">
|
||||
<img src="https://img.shields.io/pypi/v/nonebot2?logo=python&logoColor=edb641" alt="pypi">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/python-3.8+-blue?logo=python&logoColor=edb641" alt="python">
|
||||
<img src="https://img.shields.io/badge/python-3.9+-blue?logo=python&logoColor=edb641" alt="python">
|
||||
<a href="https://github.com/psf/black">
|
||||
<img src="https://img.shields.io/badge/code%20style-black-000000.svg?logo=python&logoColor=edb641" alt="black">
|
||||
</a>
|
||||
@@ -110,26 +110,30 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
||||
- 社区丰富:社区用户众多,直接和间接用户超过十万人,每天都有大量的活跃用户 ([社区资源](#社区资源))
|
||||
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
|
||||
|
||||
| 协议名称 | 状态 | 注释 |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------: | :--: | :-----------------------------------------------------------------------: |
|
||||
| OneBot([仓库](https://github.com/nonebot/adapter-onebot),[协议](https://onebot.dev/)) | ✅ | 支持 QQ、TG、微信公众号、KOOK 等[平台](https://onebot.dev/ecosystem.html) |
|
||||
| Telegram([仓库](https://github.com/nonebot/adapter-telegram),[协议](https://core.telegram.org/bots/api)) | ✅ | |
|
||||
| 飞书([仓库](https://github.com/nonebot/adapter-feishu),[协议](https://open.feishu.cn/document/home/index)) | ✅ | |
|
||||
| GitHub([仓库](https://github.com/nonebot/adapter-github),[协议](https://docs.github.com/en/apps)) | ✅ | GitHub APP & OAuth APP |
|
||||
| QQ([仓库](https://github.com/nonebot/adapter-qq),[协议](https://bot.q.qq.com/wiki/)) | ✅ | QQ 官方接口调整较多 |
|
||||
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
||||
| Console([仓库](https://github.com/nonebot/adapter-console)) | ✅ | 控制台交互 |
|
||||
| Red ([仓库](https://github.com/nonebot/adapter-red),[协议](https://chrononeko.github.io/QQNTRedProtocol/)) | ✅ | QQ 协议 |
|
||||
| Satori([仓库](https://github.com/nonebot/adapter-satori),[协议](https://satori.js.org/zh-CN)) | ✅ | 支持 Onebot、TG、飞书、微信公众号、Koishi 等 |
|
||||
| Discord ([仓库](https://github.com/nonebot/adapter-discord),[协议](https://discord.com/developers/docs/intro)) | ✅ | Discord Bot 协议 |
|
||||
| DoDo ([仓库](https://github.com/nonebot/adapter-dodo),[协议](https://open.imdodo.com/)) | ✅ | DoDo Bot 协议 |
|
||||
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila),[协议](https://developer.kookapp.cn/)) | ↗️ | 由社区贡献 |
|
||||
| Mirai([仓库](https://github.com/ieew/nonebot_adapter_mirai2),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ↗️ | QQ 协议,由社区贡献 |
|
||||
| Ntchat([仓库](https://github.com/JustUndertaker/adapter-ntchat)) | ↗️ | 微信协议,由社区贡献 |
|
||||
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
||||
| BiliBili Live([仓库](https://github.com/wwweww/adapter-bilibili)) | ↗️ | 由社区贡献 |
|
||||
| Walle-Q([仓库](https://github.com/onebot-walle/nonebot_adapter_walleq)) | ↗️ | QQ 协议,由社区贡献 |
|
||||
| Villa([仓库](https://github.com/CMHopeSunshine/nonebot-adapter-villa),[协议](https://webstatic.mihoyo.com/vila/bot/doc/)) | ↗️ | 米游社大别野 Bot 协议,由社区贡献 |
|
||||
| 协议名称 | 状态 | 注释 |
|
||||
| :-------------------------------------------------------------------------------------------------------------------: | :--: | :-----------------------------------------------------------------------: |
|
||||
| OneBot([仓库](https://github.com/nonebot/adapter-onebot),[协议](https://onebot.dev/)) | ✅ | 支持 QQ、TG、微信公众号、KOOK 等[平台](https://onebot.dev/ecosystem.html) |
|
||||
| Telegram([仓库](https://github.com/nonebot/adapter-telegram),[协议](https://core.telegram.org/bots/api)) | ✅ | |
|
||||
| 飞书([仓库](https://github.com/nonebot/adapter-feishu),[协议](https://open.feishu.cn/document/home/index)) | ✅ | |
|
||||
| GitHub([仓库](https://github.com/nonebot/adapter-github),[协议](https://docs.github.com/en/apps)) | ✅ | GitHub APP & OAuth APP |
|
||||
| QQ([仓库](https://github.com/nonebot/adapter-qq),[协议](https://bot.q.qq.com/wiki/)) | ✅ | QQ 官方接口调整较多 |
|
||||
| Console([仓库](https://github.com/nonebot/adapter-console)) | ✅ | 控制台交互 |
|
||||
| Red([仓库](https://github.com/nonebot/adapter-red),[协议](https://chrononeko.github.io/QQNTRedProtocol/)) | ✅ | QQ 协议 |
|
||||
| Satori([仓库](https://github.com/nonebot/adapter-satori),[协议](https://satori.js.org/zh-CN)) | ✅ | 支持 Onebot、TG、飞书、微信公众号、Koishi 等 |
|
||||
| Discord([仓库](https://github.com/nonebot/adapter-discord),[协议](https://discord.com/developers/docs/intro)) | ✅ | Discord Bot 协议 |
|
||||
| DoDo([仓库](https://github.com/nonebot/adapter-dodo),[协议](https://open.imdodo.com/)) | ✅ | DoDo Bot 协议 |
|
||||
| Kritor([仓库](https://github.com/nonebot/adapter-kritor),[协议](https://github.com/KarinJS/kritor)) | ✅ | Kritor (OnebotX) 协议,QQ 机器人接口标准 |
|
||||
| Mirai([仓库](https://github.com/nonebot/adapter-mirai),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ✅ | QQ 协议 |
|
||||
| 钉钉([仓库](https://github.com/nonebot/adapter-ding),[协议](https://open.dingtalk.com/document/)) | 🤗 | 寻找 Maintainer(暂不可用) |
|
||||
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila),[协议](https://developer.kookapp.cn/)) | ↗️ | 由社区贡献 |
|
||||
| Mirai([仓库](https://github.com/ieew/nonebot_adapter_mirai2),[协议](https://docs.mirai.mamoe.net/mirai-api-http/)) | ↗️ | QQ 协议,由社区贡献 |
|
||||
| Ntchat([仓库](https://github.com/JustUndertaker/adapter-ntchat)) | ↗️ | 微信协议,由社区贡献 |
|
||||
| MineCraft([仓库](https://github.com/17TheWord/nonebot-adapter-minecraft)) | ↗️ | 由社区贡献 |
|
||||
| BiliBili Live([仓库](https://github.com/wwweww/adapter-bilibili)) | ↗️ | 由社区贡献 |
|
||||
| Walle-Q([仓库](https://github.com/onebot-walle/nonebot_adapter_walleq)) | ↗️ | QQ 协议,由社区贡献 |
|
||||
| Villa([仓库](https://github.com/CMHopeSunshine/nonebot-adapter-villa)) | ❌ | 米游社大别野 Bot 协议,官方已下线 |
|
||||
| Rocket.Chat([仓库](https://github.com/IUnlimit/nonebot-adapter-rocketchat),[协议](https://developer.rocket.chat/)) | ↗️ | Rocket.Chat Bot 协议,由社区贡献 |
|
||||
| Tailchat([仓库](https://github.com/eya46/nonebot-adapter-tailchat),[协议](https://tailchat.msgbyte.com/)) | ↗️ | Tailchat 开放平台 Bot 协议,由社区贡献 |
|
||||
|
||||
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
|
||||
|
||||
|
@@ -59,6 +59,16 @@
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapters.mirai",
|
||||
"project_link": "nonebot-adapter-mirai",
|
||||
"name": "Mirai",
|
||||
"desc": "mirai-api-http v2 协议适配",
|
||||
"author": "RF-Tar-Railt",
|
||||
"homepage": "https://github.com/nonebot/adapter-mirai",
|
||||
"tags": [],
|
||||
"is_official": true
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapters.mirai2",
|
||||
"project_link": "nonebot_adapter_mirai2",
|
||||
@@ -213,5 +223,40 @@
|
||||
"homepage": "https://github.com/nonebot/adapter-dodo",
|
||||
"tags": [],
|
||||
"is_official": true
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapters.rocketchat",
|
||||
"project_link": "nonebot-adapter-rocketchat",
|
||||
"name": "RocketChat",
|
||||
"desc": "RocketChat adapter for nonebot2",
|
||||
"author": "IllTamer",
|
||||
"homepage": "https://github.com/IUnlimit/nonebot-adapter-rocketchat",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot.adapters.kritor",
|
||||
"project_link": "nonebot-adapter-kritor",
|
||||
"name": "Kritor",
|
||||
"desc": "Kritor 协议适配",
|
||||
"author": "RF-Tar-Railt",
|
||||
"homepage": "https://github.com/nonebot/adapter-kritor",
|
||||
"tags": [
|
||||
{
|
||||
"label": "QQNT",
|
||||
"color": "#35a7c9"
|
||||
}
|
||||
],
|
||||
"is_official": true
|
||||
},
|
||||
{
|
||||
"module_name": "nonebot_adapter_tailchat",
|
||||
"project_link": "nonebot-adapter-tailchat",
|
||||
"name": "Tailchat",
|
||||
"desc": "Tailchat 适配器",
|
||||
"author": "eya46",
|
||||
"homepage": "https://github.com/eya46/nonebot-adapter-tailchat",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
}
|
||||
]
|
||||
|
@@ -607,5 +607,30 @@
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "星辰 Bot",
|
||||
"desc": "欢迎使用 星辰 Bot 项目!这是一款基于 NoneBot2 打造的智能 QQ 机器人,旨在为用户提供丰富的功能体验。无论是获取一言的灵感,探索历史上的今天,还是穿梭60s世界,星辰 Bot 为您打开了全新的交流之门。快来尝试吧!",
|
||||
"author": "StarXinXin",
|
||||
"homepage": "https://github.com/StarXinXin/StarsBot",
|
||||
"tags": [],
|
||||
"is_official": false
|
||||
},
|
||||
{
|
||||
"name": "Minecraft_QQBot",
|
||||
"desc": "基于 NoneBot2 的 Minecraft 群服互联 QQ 机器人,支持多服务器多种方式连接。",
|
||||
"author": "Lonely-Sails",
|
||||
"homepage": "https://github.com/Minecraft-QQBot/BotServer",
|
||||
"tags": [
|
||||
{
|
||||
"label": "Minecraft",
|
||||
"color": "#ea5252"
|
||||
},
|
||||
{
|
||||
"label": "娱乐",
|
||||
"color": "#37a7e7"
|
||||
}
|
||||
],
|
||||
"is_official": false
|
||||
}
|
||||
]
|
||||
|
1290
assets/plugins.json
1290
assets/plugins.json
File diff suppressed because it is too large
Load Diff
1089
envs/pydantic-v1/poetry.lock
generated
1089
envs/pydantic-v1/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||
license = "MIT"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
python = "^3.9"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pydantic = "^1.0.0"
|
||||
|
1202
envs/pydantic-v2/poetry.lock
generated
1202
envs/pydantic-v2/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||
license = "MIT"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
python = "^3.9"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pydantic = "^2.0.0"
|
||||
|
440
envs/test/poetry.lock
generated
440
envs/test/poetry.lock
generated
@@ -1,14 +1,25 @@
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
description = "Reusable constraint types to use with typing.Annotated"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
|
||||
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.7.2"
|
||||
version = "3.8.1"
|
||||
description = "ASGI specs, helper code, and adapters"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"},
|
||||
{file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"},
|
||||
{file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
|
||||
{file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -33,13 +44,13 @@ requests = ">=2.21,<3.0"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.2.2"
|
||||
version = "2024.7.4"
|
||||
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"},
|
||||
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
|
||||
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -154,63 +165,63 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.4.1"
|
||||
version = "7.5.4"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"},
|
||||
{file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"},
|
||||
{file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"},
|
||||
{file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"},
|
||||
{file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"},
|
||||
{file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"},
|
||||
{file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"},
|
||||
{file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"},
|
||||
{file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"},
|
||||
{file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"},
|
||||
{file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"},
|
||||
{file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"},
|
||||
{file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"},
|
||||
{file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"},
|
||||
{file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"},
|
||||
{file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"},
|
||||
{file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"},
|
||||
{file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"},
|
||||
{file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"},
|
||||
{file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"},
|
||||
{file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"},
|
||||
{file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"},
|
||||
{file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"},
|
||||
{file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"},
|
||||
{file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"},
|
||||
{file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"},
|
||||
{file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"},
|
||||
{file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"},
|
||||
{file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"},
|
||||
{file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"},
|
||||
{file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"},
|
||||
{file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"},
|
||||
{file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"},
|
||||
{file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"},
|
||||
{file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"},
|
||||
{file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"},
|
||||
{file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"},
|
||||
{file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"},
|
||||
{file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"},
|
||||
{file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"},
|
||||
{file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"},
|
||||
{file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"},
|
||||
{file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"},
|
||||
{file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"},
|
||||
{file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"},
|
||||
{file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"},
|
||||
{file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"},
|
||||
{file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"},
|
||||
{file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"},
|
||||
{file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"},
|
||||
{file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"},
|
||||
{file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"},
|
||||
{file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"},
|
||||
{file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"},
|
||||
{file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"},
|
||||
{file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"},
|
||||
{file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"},
|
||||
{file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"},
|
||||
{file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"},
|
||||
{file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -237,13 +248,13 @@ packaging = ">=20.4"
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
|
||||
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
|
||||
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
|
||||
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -251,13 +262,13 @@ test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "execnet"
|
||||
version = "2.0.2"
|
||||
version = "2.1.1"
|
||||
description = "execnet: rapid multi-Python deployment"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"},
|
||||
{file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"},
|
||||
{file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
|
||||
{file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -276,33 +287,33 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.6"
|
||||
version = "3.7"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
|
||||
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
|
||||
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
|
||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "7.0.1"
|
||||
version = "8.0.0"
|
||||
description = "Read metadata from Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"},
|
||||
{file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"},
|
||||
{file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"},
|
||||
{file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"},
|
||||
]
|
||||
|
||||
[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"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
perf = ["ipython"]
|
||||
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
|
||||
test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
@@ -503,19 +514,20 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "nonebot2"
|
||||
version = "2.1.3"
|
||||
version = "2.3.1"
|
||||
description = "An asynchronous python bot framework."
|
||||
optional = false
|
||||
python-versions = ">=3.8,<4.0"
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "nonebot2-2.1.3-py3-none-any.whl", hash = "sha256:c36c1a60ce4355d9777fee431c08619f22ffd60f7060993fbbbd1fe67b6368f7"},
|
||||
{file = "nonebot2-2.1.3.tar.gz", hash = "sha256:e750e615f1ad2503721ce055fbe55ec3b061277135d995be112fecd27f7232e5"},
|
||||
{file = "nonebot2-2.3.1-py3-none-any.whl", hash = "sha256:91ac0abebe6c403c2443b11a49e065b79e6199460bdd61a32148366b35f81c4d"},
|
||||
{file = "nonebot2-2.3.1.tar.gz", hash = "sha256:ac5a1a1759f15310e9183b606ce6bdbe52a90287bf36a69201be548e23d41e6c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
loguru = ">=0.6.0,<1.0.0"
|
||||
pydantic = {version = ">=1.10.0,<2.0.0", extras = ["dotenv"]}
|
||||
pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<3.0.0"
|
||||
pygtrie = ">=2.4.1,<3.0.0"
|
||||
python-dotenv = ">=0.21.0,<2.0.0"
|
||||
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = ">=4.4.0,<5.0.0"
|
||||
yarl = ">=1.7.2,<2.0.0"
|
||||
@@ -530,42 +542,42 @@ websockets = ["websockets (>=10.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "nonebug"
|
||||
version = "0.3.5"
|
||||
version = "0.3.7"
|
||||
description = "nonebot2 test framework"
|
||||
optional = false
|
||||
python-versions = ">=3.8,<4.0"
|
||||
python-versions = "<4.0,>=3.8"
|
||||
files = [
|
||||
{file = "nonebug-0.3.5-py3-none-any.whl", hash = "sha256:588831b08b3ea42d058874214bedae646e2ab8c1ec4ae1540ff789873107a8fa"},
|
||||
{file = "nonebug-0.3.5.tar.gz", hash = "sha256:4d4bf9448cd1cbfaaabaab73dbe4ac8757e86dd92a41ef79cdece8dd61e724e2"},
|
||||
{file = "nonebug-0.3.7-py3-none-any.whl", hash = "sha256:c39f462aafe20660602a8b789a575db6c9346ab5b6f1985eb9d98b861528299a"},
|
||||
{file = "nonebug-0.3.7.tar.gz", hash = "sha256:8a75183400681f34eafc7caa2bb6dd511c3b5660c59264f1c379a088c7ac2247"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
asgiref = ">=3.4.0,<4.0.0"
|
||||
async-asgi-testclient = ">=1.4.8,<2.0.0"
|
||||
nonebot2 = ">=2.0.0-rc.2,<3.0.0"
|
||||
pytest = ">=7.0.0,<8.0.0"
|
||||
nonebot2 = ">=2.2.0,<3.0.0"
|
||||
pytest = ">=7.0.0,<9.0.0"
|
||||
typing-extensions = ">=4.0.0,<5.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "23.2"
|
||||
version = "24.1"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
|
||||
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
||||
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
||||
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
|
||||
{file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
|
||||
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
||||
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -574,56 +586,126 @@ testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.10.14"
|
||||
description = "Data validation and settings management using python type hints"
|
||||
version = "2.8.2"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"},
|
||||
{file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"},
|
||||
{file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"},
|
||||
{file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"},
|
||||
{file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"},
|
||||
{file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"},
|
||||
{file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"},
|
||||
{file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"},
|
||||
{file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"},
|
||||
{file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
python-dotenv = {version = ">=0.10.4", optional = true, markers = "extra == \"dotenv\""}
|
||||
typing-extensions = ">=4.2.0"
|
||||
annotated-types = ">=0.4.0"
|
||||
pydantic-core = "2.20.1"
|
||||
typing-extensions = [
|
||||
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
|
||||
{version = ">=4.6.1", markers = "python_version < \"3.13\""},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||
email = ["email-validator (>=1.0.3)"]
|
||||
email = ["email-validator (>=2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.20.1"
|
||||
description = "Core functionality for Pydantic validation and serialization"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"},
|
||||
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"},
|
||||
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"},
|
||||
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"},
|
||||
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"},
|
||||
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"},
|
||||
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"},
|
||||
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"},
|
||||
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"},
|
||||
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"},
|
||||
{file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"},
|
||||
{file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"},
|
||||
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"},
|
||||
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"},
|
||||
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"},
|
||||
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"},
|
||||
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"},
|
||||
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"},
|
||||
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"},
|
||||
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"},
|
||||
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"},
|
||||
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"},
|
||||
{file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"},
|
||||
{file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"},
|
||||
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"},
|
||||
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"},
|
||||
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"},
|
||||
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"},
|
||||
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"},
|
||||
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"},
|
||||
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"},
|
||||
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"},
|
||||
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"},
|
||||
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"},
|
||||
{file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"},
|
||||
{file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"},
|
||||
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"},
|
||||
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"},
|
||||
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"},
|
||||
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"},
|
||||
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"},
|
||||
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"},
|
||||
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"},
|
||||
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"},
|
||||
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"},
|
||||
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"},
|
||||
{file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"},
|
||||
{file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"},
|
||||
{file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"},
|
||||
{file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"},
|
||||
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"},
|
||||
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"},
|
||||
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"},
|
||||
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"},
|
||||
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"},
|
||||
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"},
|
||||
{file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"},
|
||||
{file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"},
|
||||
{file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"},
|
||||
{file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"},
|
||||
{file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"},
|
||||
{file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"},
|
||||
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"},
|
||||
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"},
|
||||
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"},
|
||||
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"},
|
||||
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"},
|
||||
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"},
|
||||
{file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"},
|
||||
{file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"},
|
||||
{file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"},
|
||||
{file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"},
|
||||
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"},
|
||||
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"},
|
||||
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"},
|
||||
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"},
|
||||
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"},
|
||||
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"},
|
||||
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"},
|
||||
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"},
|
||||
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"},
|
||||
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"},
|
||||
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"},
|
||||
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"},
|
||||
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"},
|
||||
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"},
|
||||
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"},
|
||||
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"},
|
||||
{file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "pygtrie"
|
||||
@@ -638,13 +720,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.4.4"
|
||||
version = "8.2.2"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
|
||||
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
|
||||
{file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
|
||||
{file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -652,25 +734,25 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||
pluggy = ">=1.5,<2.0"
|
||||
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-asyncio"
|
||||
version = "0.23.4"
|
||||
version = "0.23.7"
|
||||
description = "Pytest support for asyncio"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-asyncio-0.23.4.tar.gz", hash = "sha256:2143d9d9375bf372a73260e4114541485e84fca350b0b6b92674ca56ff5f7ea2"},
|
||||
{file = "pytest_asyncio-0.23.4-py3-none-any.whl", hash = "sha256:b0079dfac14b60cd1ce4691fbfb1748fe939db7d0234b5aba97197d10fbe0fef"},
|
||||
{file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"},
|
||||
{file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=7.0.0,<8"
|
||||
pytest = ">=7.0.0,<9"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
|
||||
@@ -678,13 +760,13 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "4.1.0"
|
||||
version = "5.0.0"
|
||||
description = "Pytest plugin for measuring coverage."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
|
||||
{file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
|
||||
{file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"},
|
||||
{file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -692,22 +774,22 @@ coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||
pytest = ">=4.6"
|
||||
|
||||
[package.extras]
|
||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-xdist"
|
||||
version = "3.5.0"
|
||||
version = "3.6.1"
|
||||
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"},
|
||||
{file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"},
|
||||
{file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
|
||||
{file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
execnet = ">=1.1"
|
||||
pytest = ">=6.2.0"
|
||||
execnet = ">=2.1"
|
||||
pytest = ">=7.0.0"
|
||||
|
||||
[package.extras]
|
||||
psutil = ["psutil (>=3.0)"]
|
||||
@@ -730,13 +812,13 @@ cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.31.0"
|
||||
version = "2.32.3"
|
||||
description = "Python HTTP for Humans."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
|
||||
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
|
||||
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
|
||||
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -762,24 +844,24 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.9.0"
|
||||
version = "4.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
|
||||
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.0"
|
||||
version = "2.2.2"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"},
|
||||
{file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"},
|
||||
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
|
||||
{file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -790,13 +872,13 @@ zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.0.1"
|
||||
version = "3.0.3"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
|
||||
{file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
|
||||
{file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"},
|
||||
{file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -938,20 +1020,20 @@ multidict = ">=4.0"
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.17.0"
|
||||
version = "3.19.2"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
|
||||
{file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
|
||||
{file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"},
|
||||
{file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"},
|
||||
]
|
||||
|
||||
[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"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "ab5729309587cb130ac7848e8862368995372cf2fc91d0966598b3c6b49028e5"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "ee7bfccee3250456dbe0b9f11914fbbc290b83abdc1a82ae6f4c3525c2818416"
|
||||
|
@@ -7,10 +7,10 @@ license = "MIT"
|
||||
packages = [{ include = "nonebot-test.py" }]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
nonebug = "^0.3.0"
|
||||
python = "^3.9"
|
||||
nonebug = "^0.3.7"
|
||||
wsproto = "^1.2.0"
|
||||
pytest-cov = "^4.0.0"
|
||||
pytest-cov = "^5.0.0"
|
||||
pytest-xdist = "^3.0.2"
|
||||
pytest-asyncio = "^0.23.2"
|
||||
werkzeug = ">=2.3.6,<4.0.0"
|
||||
|
@@ -45,10 +45,11 @@ FrontMatter:
|
||||
|
||||
import os
|
||||
from importlib.metadata import version
|
||||
from typing import Any, Dict, Type, Union, TypeVar, Optional, overload
|
||||
from typing import Any, Union, TypeVar, Optional, overload
|
||||
|
||||
import loguru
|
||||
|
||||
from nonebot.compat import model_dump
|
||||
from nonebot.log import logger as logger
|
||||
from nonebot.adapters import Bot, Adapter
|
||||
from nonebot.config import DOTENV_TYPE, Env, Config
|
||||
@@ -99,7 +100,7 @@ def get_adapter(name: str) -> Adapter:
|
||||
|
||||
|
||||
@overload
|
||||
def get_adapter(name: Type[A]) -> A:
|
||||
def get_adapter(name: type[A]) -> A:
|
||||
"""
|
||||
参数:
|
||||
name: 适配器类型
|
||||
@@ -109,7 +110,7 @@ def get_adapter(name: Type[A]) -> A:
|
||||
"""
|
||||
|
||||
|
||||
def get_adapter(name: Union[str, Type[Adapter]]) -> Adapter:
|
||||
def get_adapter(name: Union[str, type[Adapter]]) -> Adapter:
|
||||
"""获取已注册的 {ref}`nonebot.adapters.Adapter` 实例。
|
||||
|
||||
异常:
|
||||
@@ -130,7 +131,7 @@ def get_adapter(name: Union[str, Type[Adapter]]) -> Adapter:
|
||||
return adapters[target]
|
||||
|
||||
|
||||
def get_adapters() -> Dict[str, Adapter]:
|
||||
def get_adapters() -> dict[str, Adapter]:
|
||||
"""获取所有已注册的 {ref}`nonebot.adapters.Adapter` 实例。
|
||||
|
||||
返回:
|
||||
@@ -229,7 +230,7 @@ def get_bot(self_id: Optional[str] = None) -> Bot:
|
||||
raise ValueError("There are no bots to get.")
|
||||
|
||||
|
||||
def get_bots() -> Dict[str, Bot]:
|
||||
def get_bots() -> dict[str, Bot]:
|
||||
"""获取所有连接到 NoneBot 的 {ref}`nonebot.adapters.Bot` 对象。
|
||||
|
||||
返回:
|
||||
@@ -248,7 +249,7 @@ def get_bots() -> Dict[str, Bot]:
|
||||
return get_driver().bots
|
||||
|
||||
|
||||
def _resolve_combine_expr(obj_str: str) -> Type[Driver]:
|
||||
def _resolve_combine_expr(obj_str: str) -> type[Driver]:
|
||||
drivers = obj_str.split("+")
|
||||
DriverClass = resolve_dot_notation(
|
||||
drivers[0], "Driver", default_prefix="nonebot.drivers."
|
||||
@@ -265,11 +266,12 @@ def _resolve_combine_expr(obj_str: str) -> Type[Driver]:
|
||||
|
||||
|
||||
def _log_patcher(record: "loguru.Record"):
|
||||
"""使用插件标识优化日志展示"""
|
||||
record["name"] = (
|
||||
plugin.name
|
||||
plugin.id_
|
||||
if (module_name := record["name"])
|
||||
and (plugin := get_plugin_by_module_name(module_name))
|
||||
else (module_name and module_name.split(".")[0])
|
||||
else (module_name and module_name.split(".", maxsplit=1)[0])
|
||||
)
|
||||
|
||||
|
||||
@@ -310,7 +312,7 @@ def init(*, _env_file: Optional[DOTENV_TYPE] = None, **kwargs: Any) -> None:
|
||||
f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>"
|
||||
)
|
||||
logger.opt(colors=True).debug(
|
||||
f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}"
|
||||
f"Loaded <y><b>Config</b></y>: {escape_tag(str(model_dump(config)))}"
|
||||
)
|
||||
|
||||
DriverClass = _resolve_combine_expr(config.driver)
|
||||
|
@@ -7,20 +7,21 @@ FrontMatter:
|
||||
description: nonebot.compat 模块
|
||||
"""
|
||||
|
||||
from collections.abc import Generator
|
||||
from functools import cached_property
|
||||
from dataclasses import dataclass, is_dataclass
|
||||
from typing_extensions import Self, Annotated, get_args, get_origin, is_typeddict
|
||||
from typing_extensions import Self, get_args, get_origin, is_typeddict
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Set,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Union,
|
||||
Generic,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Optional,
|
||||
Protocol,
|
||||
Generator,
|
||||
Annotated,
|
||||
overload,
|
||||
)
|
||||
|
||||
from pydantic import VERSION, BaseModel
|
||||
@@ -48,12 +49,13 @@ __all__ = (
|
||||
"DEFAULT_CONFIG",
|
||||
"FieldInfo",
|
||||
"ModelField",
|
||||
"TypeAdapter",
|
||||
"extract_field_info",
|
||||
"model_field_validate",
|
||||
"model_fields",
|
||||
"model_config",
|
||||
"model_dump",
|
||||
"type_validate_python",
|
||||
"type_validate_json",
|
||||
"custom_validation",
|
||||
)
|
||||
|
||||
@@ -64,9 +66,10 @@ __autodoc__ = {
|
||||
|
||||
|
||||
if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
from pydantic import GetCoreSchemaHandler
|
||||
from pydantic import TypeAdapter as TypeAdapter
|
||||
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
|
||||
@@ -92,7 +95,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
super().__init__(default=default, **kwargs)
|
||||
|
||||
@property
|
||||
def extra(self) -> Dict[str, Any]:
|
||||
def extra(self) -> dict[str, Any]:
|
||||
"""Extra data that is not part of the standard pydantic fields.
|
||||
|
||||
For compatibility with pydantic v1.
|
||||
@@ -126,6 +129,25 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
"""Construct a ModelField from given infos."""
|
||||
return cls._construct(name, annotation, field_info or FieldInfo())
|
||||
|
||||
def __hash__(self) -> int:
|
||||
# Each ModelField is unique for our purposes,
|
||||
# to allow store them in a set.
|
||||
return id(self)
|
||||
|
||||
@cached_property
|
||||
def type_adapter(self) -> TypeAdapter:
|
||||
"""TypeAdapter of the field.
|
||||
|
||||
Cache the TypeAdapter to avoid creating it multiple times.
|
||||
Pydantic v2 uses too much cpu time to create TypeAdapter.
|
||||
|
||||
See: https://github.com/pydantic/pydantic/issues/9834
|
||||
"""
|
||||
return TypeAdapter(
|
||||
Annotated[self.annotation, self.field_info],
|
||||
config=None if self._annotation_has_config() else DEFAULT_CONFIG,
|
||||
)
|
||||
|
||||
def _annotation_has_config(self) -> bool:
|
||||
"""Check if the annotation has config.
|
||||
|
||||
@@ -153,28 +175,18 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
"""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 validate_value(self, value: Any) -> Any:
|
||||
"""Validate the value pass to the field."""
|
||||
return self.type_adapter.validate_python(value)
|
||||
|
||||
def extract_field_info(field_info: BaseFieldInfo) -> Dict[str, Any]:
|
||||
def extract_field_info(field_info: BaseFieldInfo) -> dict[str, Any]:
|
||||
"""Get FieldInfo init kwargs from a FieldInfo instance."""
|
||||
|
||||
kwargs = field_info._attributes_set.copy()
|
||||
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]:
|
||||
def model_fields(model: type[BaseModel]) -> list[ModelField]:
|
||||
"""Get field list of a model."""
|
||||
|
||||
return [
|
||||
@@ -186,23 +198,38 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
for name, field_info in model.model_fields.items()
|
||||
]
|
||||
|
||||
def model_config(model: Type[BaseModel]) -> Any:
|
||||
def model_config(model: type[BaseModel]) -> Any:
|
||||
"""Get config of a model."""
|
||||
return model.model_config
|
||||
|
||||
def model_dump(
|
||||
model: BaseModel,
|
||||
include: Optional[Set[str]] = None,
|
||||
exclude: Optional[Set[str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
return model.model_dump(include=include, exclude=exclude)
|
||||
include: Optional[set[str]] = None,
|
||||
exclude: Optional[set[str]] = None,
|
||||
by_alias: bool = False,
|
||||
exclude_unset: bool = False,
|
||||
exclude_defaults: bool = False,
|
||||
exclude_none: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return model.model_dump(
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
by_alias=by_alias,
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_defaults=exclude_defaults,
|
||||
exclude_none=exclude_none,
|
||||
)
|
||||
|
||||
def type_validate_python(type_: Type[T], data: Any) -> T:
|
||||
def type_validate_python(type_: type[T], data: Any) -> T:
|
||||
"""Validate data with given type."""
|
||||
return TypeAdapter(type_).validate_python(data)
|
||||
|
||||
def type_validate_json(type_: type[T], data: Union[str, bytes]) -> T:
|
||||
"""Validate JSON with given type."""
|
||||
return TypeAdapter(type_).validate_json(data)
|
||||
|
||||
def __get_pydantic_core_schema__(
|
||||
cls: Type["_CustomValidationClass"],
|
||||
cls: type["_CustomValidationClass"],
|
||||
source_type: Any,
|
||||
handler: GetCoreSchemaHandler,
|
||||
) -> CoreSchema:
|
||||
@@ -213,7 +240,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
[core_schema.no_info_plain_validator_function(func) for func in validators]
|
||||
)
|
||||
|
||||
def custom_validation(class_: Type["CVC"]) -> Type["CVC"]:
|
||||
def custom_validation(class_: type["CVC"]) -> type["CVC"]:
|
||||
"""Use pydantic v1 like validator generator in pydantic v2"""
|
||||
|
||||
setattr(
|
||||
@@ -225,7 +252,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
|
||||
else: # pragma: pydantic-v1
|
||||
from pydantic import Extra
|
||||
from pydantic import parse_obj_as
|
||||
from pydantic import parse_obj_as, parse_raw_as
|
||||
from pydantic import BaseConfig as PydanticConfig
|
||||
from pydantic.fields import FieldInfo as BaseFieldInfo
|
||||
from pydantic.fields import ModelField as BaseModelField
|
||||
@@ -291,7 +318,46 @@ else: # pragma: pydantic-v1
|
||||
)
|
||||
return cls._construct(name, annotation, field_info or FieldInfo())
|
||||
|
||||
def extract_field_info(field_info: BaseFieldInfo) -> Dict[str, Any]:
|
||||
def validate_value(self, value: Any) -> Any:
|
||||
"""Validate the value pass to the field."""
|
||||
v, errs_ = self.validate(value, {}, loc=())
|
||||
if errs_:
|
||||
raise ValueError(value, self)
|
||||
return v
|
||||
|
||||
class TypeAdapter(Generic[T]):
|
||||
@overload
|
||||
def __init__(
|
||||
self,
|
||||
type: type[T],
|
||||
*,
|
||||
config: Optional[ConfigDict] = ...,
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self,
|
||||
type: Any,
|
||||
*,
|
||||
config: Optional[ConfigDict] = ...,
|
||||
) -> None: ...
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
type: Any,
|
||||
*,
|
||||
config: Optional[ConfigDict] = None,
|
||||
) -> None:
|
||||
self.type = type
|
||||
self.config = config
|
||||
|
||||
def validate_python(self, value: Any) -> T:
|
||||
return type_validate_python(self.type, value)
|
||||
|
||||
def validate_json(self, value: Union[str, bytes]) -> T:
|
||||
return type_validate_json(self.type, value)
|
||||
|
||||
def extract_field_info(field_info: BaseFieldInfo) -> dict[str, Any]:
|
||||
"""Get FieldInfo init kwargs from a FieldInfo instance."""
|
||||
|
||||
kwargs = {
|
||||
@@ -300,23 +366,7 @@ else: # pragma: pydantic-v1
|
||||
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]:
|
||||
def model_fields(model: type[BaseModel]) -> list[ModelField]:
|
||||
"""Get field list of a model."""
|
||||
|
||||
# construct the model field without preprocess to avoid error
|
||||
@@ -331,21 +381,36 @@ else: # pragma: pydantic-v1
|
||||
for model_field in model.__fields__.values()
|
||||
]
|
||||
|
||||
def model_config(model: Type[BaseModel]) -> Any:
|
||||
def model_config(model: type[BaseModel]) -> Any:
|
||||
"""Get config of a model."""
|
||||
return model.__config__
|
||||
|
||||
def model_dump(
|
||||
model: BaseModel,
|
||||
include: Optional[Set[str]] = None,
|
||||
exclude: Optional[Set[str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
return model.dict(include=include, exclude=exclude)
|
||||
include: Optional[set[str]] = None,
|
||||
exclude: Optional[set[str]] = None,
|
||||
by_alias: bool = False,
|
||||
exclude_unset: bool = False,
|
||||
exclude_defaults: bool = False,
|
||||
exclude_none: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return model.dict(
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
by_alias=by_alias,
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_defaults=exclude_defaults,
|
||||
exclude_none=exclude_none,
|
||||
)
|
||||
|
||||
def type_validate_python(type_: Type[T], data: Any) -> T:
|
||||
def type_validate_python(type_: type[T], data: Any) -> T:
|
||||
"""Validate data with given type."""
|
||||
return parse_obj_as(type_, data)
|
||||
|
||||
def custom_validation(class_: Type["CVC"]) -> Type["CVC"]:
|
||||
def type_validate_json(type_: type[T], data: Union[str, bytes]) -> T:
|
||||
"""Validate JSON with given type."""
|
||||
return parse_raw_as(type_, data)
|
||||
|
||||
def custom_validation(class_: type["CVC"]) -> type["CVC"]:
|
||||
"""Do nothing in pydantic v1"""
|
||||
return class_
|
||||
|
@@ -17,19 +17,9 @@ import json
|
||||
from pathlib import Path
|
||||
from datetime import timedelta
|
||||
from ipaddress import IPv4Address
|
||||
from collections.abc import Mapping
|
||||
from typing import TYPE_CHECKING, Any, Union, Optional
|
||||
from typing_extensions import TypeAlias, get_args, get_origin
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Set,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Mapping,
|
||||
Optional,
|
||||
)
|
||||
|
||||
from dotenv import dotenv_values
|
||||
from pydantic import Field, BaseModel
|
||||
@@ -49,7 +39,7 @@ from nonebot.compat import (
|
||||
)
|
||||
|
||||
DOTENV_TYPE: TypeAlias = Union[
|
||||
Path, str, List[Union[Path, str]], Tuple[Union[Path, str], ...]
|
||||
Path, str, list[Union[Path, str]], tuple[Union[Path, str], ...]
|
||||
]
|
||||
|
||||
ENV_FILE_SENTINEL = Path("")
|
||||
@@ -59,7 +49,7 @@ class SettingsError(ValueError): ...
|
||||
|
||||
|
||||
class BaseSettingsSource(abc.ABC):
|
||||
def __init__(self, settings_cls: Type["BaseSettings"]) -> None:
|
||||
def __init__(self, settings_cls: type["BaseSettings"]) -> None:
|
||||
self.settings_cls = settings_cls
|
||||
|
||||
@property
|
||||
@@ -67,7 +57,7 @@ class BaseSettingsSource(abc.ABC):
|
||||
return model_config(self.settings_cls)
|
||||
|
||||
@abc.abstractmethod
|
||||
def __call__(self) -> Dict[str, Any]:
|
||||
def __call__(self) -> dict[str, Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -75,12 +65,12 @@ class InitSettingsSource(BaseSettingsSource):
|
||||
__slots__ = ("init_kwargs",)
|
||||
|
||||
def __init__(
|
||||
self, settings_cls: Type["BaseSettings"], init_kwargs: Dict[str, Any]
|
||||
self, settings_cls: type["BaseSettings"], init_kwargs: dict[str, Any]
|
||||
) -> None:
|
||||
self.init_kwargs = init_kwargs
|
||||
super().__init__(settings_cls)
|
||||
|
||||
def __call__(self) -> Dict[str, Any]:
|
||||
def __call__(self) -> dict[str, Any]:
|
||||
return self.init_kwargs
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -90,7 +80,7 @@ class InitSettingsSource(BaseSettingsSource):
|
||||
class DotEnvSettingsSource(BaseSettingsSource):
|
||||
def __init__(
|
||||
self,
|
||||
settings_cls: Type["BaseSettings"],
|
||||
settings_cls: type["BaseSettings"],
|
||||
env_file: Optional[DOTENV_TYPE] = ENV_FILE_SENTINEL,
|
||||
env_file_encoding: Optional[str] = None,
|
||||
case_sensitive: Optional[bool] = None,
|
||||
@@ -121,7 +111,7 @@ class DotEnvSettingsSource(BaseSettingsSource):
|
||||
def _apply_case_sensitive(self, var_name: str) -> str:
|
||||
return var_name if self.case_sensitive else var_name.lower()
|
||||
|
||||
def _field_is_complex(self, field: ModelField) -> Tuple[bool, bool]:
|
||||
def _field_is_complex(self, field: ModelField) -> tuple[bool, bool]:
|
||||
if type_is_complex(field.annotation):
|
||||
return True, False
|
||||
elif origin_is_union(get_origin(field.annotation)) and any(
|
||||
@@ -132,16 +122,16 @@ class DotEnvSettingsSource(BaseSettingsSource):
|
||||
|
||||
def _parse_env_vars(
|
||||
self, env_vars: Mapping[str, Optional[str]]
|
||||
) -> Dict[str, Optional[str]]:
|
||||
) -> dict[str, Optional[str]]:
|
||||
return {
|
||||
self._apply_case_sensitive(key): value for key, value in env_vars.items()
|
||||
}
|
||||
|
||||
def _read_env_file(self, file_path: Path) -> Dict[str, Optional[str]]:
|
||||
def _read_env_file(self, file_path: Path) -> dict[str, Optional[str]]:
|
||||
file_vars = dotenv_values(file_path, encoding=self.env_file_encoding)
|
||||
return self._parse_env_vars(file_vars)
|
||||
|
||||
def _read_env_files(self) -> Dict[str, Optional[str]]:
|
||||
def _read_env_files(self) -> dict[str, Optional[str]]:
|
||||
env_files = self.env_file
|
||||
if env_files is None:
|
||||
return {}
|
||||
@@ -149,7 +139,7 @@ class DotEnvSettingsSource(BaseSettingsSource):
|
||||
if isinstance(env_files, (str, os.PathLike)):
|
||||
env_files = [env_files]
|
||||
|
||||
dotenv_vars: Dict[str, Optional[str]] = {}
|
||||
dotenv_vars: dict[str, Optional[str]] = {}
|
||||
for env_file in env_files:
|
||||
env_path = Path(env_file).expanduser()
|
||||
if env_path.is_file():
|
||||
@@ -170,14 +160,14 @@ class DotEnvSettingsSource(BaseSettingsSource):
|
||||
def _explode_env_vars(
|
||||
self,
|
||||
field: ModelField,
|
||||
env_vars: Dict[str, Optional[str]],
|
||||
env_file_vars: Dict[str, Optional[str]],
|
||||
) -> Dict[str, Any]:
|
||||
env_vars: dict[str, Optional[str]],
|
||||
env_file_vars: dict[str, Optional[str]],
|
||||
) -> dict[str, Any]:
|
||||
if self.env_nested_delimiter is None:
|
||||
return {}
|
||||
|
||||
prefix = f"{field.name}{self.env_nested_delimiter}"
|
||||
result: Dict[str, Any] = {}
|
||||
result: dict[str, Any] = {}
|
||||
for env_name, env_val in env_vars.items():
|
||||
if not env_name.startswith(prefix):
|
||||
continue
|
||||
@@ -209,10 +199,10 @@ class DotEnvSettingsSource(BaseSettingsSource):
|
||||
|
||||
return result
|
||||
|
||||
def __call__(self) -> Dict[str, Any]:
|
||||
def __call__(self) -> dict[str, Any]:
|
||||
"""从环境变量和 dotenv 配置文件中读取配置项。"""
|
||||
|
||||
d: Dict[str, Any] = {}
|
||||
d: dict[str, Any] = {}
|
||||
|
||||
env_vars = self._parse_env_vars(os.environ)
|
||||
env_file_vars = self._read_env_files()
|
||||
@@ -317,7 +307,7 @@ class BaseSettings(BaseModel):
|
||||
return self.__dict__.get(name)
|
||||
|
||||
if PYDANTIC_V2: # pragma: pydantic-v2
|
||||
model_config: SettingsConfig = SettingsConfig(
|
||||
model_config = SettingsConfig(
|
||||
extra="allow",
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
@@ -351,11 +341,11 @@ class BaseSettings(BaseModel):
|
||||
|
||||
def _settings_build_values(
|
||||
self,
|
||||
init_kwargs: Dict[str, Any],
|
||||
init_kwargs: dict[str, Any],
|
||||
env_file: Optional[DOTENV_TYPE] = None,
|
||||
env_file_encoding: Optional[str] = None,
|
||||
env_nested_delimiter: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
init_settings = InitSettingsSource(self.__class__, init_kwargs=init_kwargs)
|
||||
env_settings = DotEnvSettingsSource(
|
||||
self.__class__,
|
||||
@@ -426,7 +416,7 @@ class Config(BaseSettings):
|
||||
"""API 请求超时时间,单位: 秒。"""
|
||||
|
||||
# bot runtime configs
|
||||
superusers: Set[str] = set()
|
||||
superusers: set[str] = set()
|
||||
"""机器人超级用户。
|
||||
|
||||
用法:
|
||||
@@ -434,9 +424,9 @@ class Config(BaseSettings):
|
||||
SUPERUSERS=["12345789"]
|
||||
```
|
||||
"""
|
||||
nickname: Set[str] = set()
|
||||
nickname: set[str] = set()
|
||||
"""机器人昵称。"""
|
||||
command_start: Set[str] = {"/"}
|
||||
command_start: set[str] = {"/"}
|
||||
"""命令的起始标记,用于判断一条消息是不是命令。
|
||||
|
||||
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
|
||||
@@ -446,7 +436,7 @@ class Config(BaseSettings):
|
||||
COMMAND_START=["/", ""]
|
||||
```
|
||||
"""
|
||||
command_sep: Set[str] = {"."}
|
||||
command_sep: set[str] = {"."}
|
||||
"""命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
|
||||
|
||||
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
|
||||
@@ -461,9 +451,8 @@ class Config(BaseSettings):
|
||||
|
||||
用法:
|
||||
```conf
|
||||
SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒
|
||||
SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff]
|
||||
SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601
|
||||
SESSION_EXPIRE_TIMEOUT=[-][DD]D[,][HH:MM:]SS[.ffffff]
|
||||
SESSION_EXPIRE_TIMEOUT=[±]P[DD]DT[HH]H[MM]M[SS]S # ISO 8601
|
||||
```
|
||||
"""
|
||||
|
||||
@@ -478,7 +467,9 @@ class Config(BaseSettings):
|
||||
model_config = SettingsConfig(env_file=(".env", ".env.prod"))
|
||||
else: # pragma: pydantic-v1
|
||||
|
||||
class Config(SettingsConfig):
|
||||
class Config( # pyright: ignore[reportIncompatibleVariableOverride]
|
||||
SettingsConfig
|
||||
):
|
||||
env_file = ".env", ".env.prod"
|
||||
|
||||
|
||||
|
@@ -9,20 +9,8 @@ import abc
|
||||
import asyncio
|
||||
import inspect
|
||||
from dataclasses import field, dataclass
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Generic,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Iterable,
|
||||
Optional,
|
||||
Awaitable,
|
||||
cast,
|
||||
)
|
||||
from collections.abc import Iterable, Awaitable
|
||||
from typing import Any, Generic, TypeVar, Callable, Optional, cast
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.typing import _DependentCallable
|
||||
@@ -48,13 +36,13 @@ class Param(abc.ABC, FieldInfo):
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type["Param"], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type["Param"], ...]
|
||||
) -> Optional["Param"]:
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def _check_parameterless(
|
||||
cls, value: Any, allow_types: Tuple[Type["Param"], ...]
|
||||
cls, value: Any, allow_types: tuple[type["Param"], ...]
|
||||
) -> Optional["Param"]:
|
||||
return
|
||||
|
||||
@@ -79,8 +67,8 @@ class Dependent(Generic[R]):
|
||||
"""
|
||||
|
||||
call: _DependentCallable[R]
|
||||
params: Tuple[ModelField, ...] = field(default_factory=tuple)
|
||||
parameterless: Tuple[Param, ...] = field(default_factory=tuple)
|
||||
params: tuple[ModelField, ...] = field(default_factory=tuple)
|
||||
parameterless: tuple[Param, ...] = field(default_factory=tuple)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if inspect.isfunction(self.call) or inspect.isclass(self.call):
|
||||
@@ -112,9 +100,9 @@ class Dependent(Generic[R]):
|
||||
|
||||
@staticmethod
|
||||
def parse_params(
|
||||
call: _DependentCallable[R], allow_types: Tuple[Type[Param], ...]
|
||||
) -> Tuple[ModelField, ...]:
|
||||
fields: List[ModelField] = []
|
||||
call: _DependentCallable[R], allow_types: tuple[type[Param], ...]
|
||||
) -> tuple[ModelField, ...]:
|
||||
fields: list[ModelField] = []
|
||||
params = get_typed_signature(call).parameters.values()
|
||||
|
||||
for param in params:
|
||||
@@ -144,9 +132,9 @@ class Dependent(Generic[R]):
|
||||
|
||||
@staticmethod
|
||||
def parse_parameterless(
|
||||
parameterless: Tuple[Any, ...], allow_types: Tuple[Type[Param], ...]
|
||||
) -> Tuple[Param, ...]:
|
||||
parameterless_params: List[Param] = []
|
||||
parameterless: tuple[Any, ...], allow_types: tuple[type[Param], ...]
|
||||
) -> tuple[Param, ...]:
|
||||
parameterless_params: list[Param] = []
|
||||
for value in parameterless:
|
||||
for allow_type in allow_types:
|
||||
if param := allow_type._check_parameterless(value, allow_types):
|
||||
@@ -162,7 +150,7 @@ class Dependent(Generic[R]):
|
||||
*,
|
||||
call: _DependentCallable[R],
|
||||
parameterless: Optional[Iterable[Any]] = None,
|
||||
allow_types: Iterable[Type[Param]],
|
||||
allow_types: Iterable[type[Param]],
|
||||
) -> "Dependent[R]":
|
||||
allow_types = tuple(allow_types)
|
||||
|
||||
@@ -181,7 +169,7 @@ class Dependent(Generic[R]):
|
||||
*(cast(Param, param.field_info)._check(**params) for param in self.params)
|
||||
)
|
||||
|
||||
async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
|
||||
async def _solve_field(self, field: ModelField, params: dict[str, Any]) -> Any:
|
||||
param = cast(Param, field.field_info)
|
||||
value = await param._solve(**params)
|
||||
if value is PydanticUndefined:
|
||||
@@ -189,7 +177,7 @@ class Dependent(Generic[R]):
|
||||
v = check_field_type(field, value)
|
||||
return v if param.validate else value
|
||||
|
||||
async def solve(self, **params: Any) -> Dict[str, Any]:
|
||||
async def solve(self, **params: Any) -> dict[str, Any]:
|
||||
# solve parameterless
|
||||
for param in self.parameterless:
|
||||
await param._solve(**params)
|
||||
|
@@ -5,13 +5,13 @@ FrontMatter:
|
||||
"""
|
||||
|
||||
import inspect
|
||||
from typing import Any, Dict, Callable, ForwardRef
|
||||
from typing import Any, Callable, ForwardRef
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from nonebot.compat import ModelField
|
||||
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:
|
||||
@@ -31,7 +31,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
||||
return inspect.Signature(typed_params)
|
||||
|
||||
|
||||
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:
|
||||
def get_typed_annotation(param: inspect.Parameter, globalns: dict[str, Any]) -> Any:
|
||||
"""获取参数的类型注解"""
|
||||
|
||||
annotation = param.annotation
|
||||
@@ -51,6 +51,6 @@ def check_field_type(field: ModelField, value: Any) -> Any:
|
||||
"""检查字段类型是否匹配"""
|
||||
|
||||
try:
|
||||
return model_field_validate(field, value, DEFAULT_CONFIG)
|
||||
return field.validate_value(value)
|
||||
except ValueError:
|
||||
raise TypeMisMatch(field, value)
|
||||
|
@@ -23,6 +23,7 @@ from nonebot.internal.driver import ReverseDriver as ReverseDriver
|
||||
from nonebot.internal.driver import combine_driver as combine_driver
|
||||
from nonebot.internal.driver import HTTPClientMixin as HTTPClientMixin
|
||||
from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup
|
||||
from nonebot.internal.driver import HTTPClientSession as HTTPClientSession
|
||||
from nonebot.internal.driver import WebSocketClientMixin as WebSocketClientMixin
|
||||
from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
|
||||
|
||||
|
@@ -16,16 +16,21 @@ FrontMatter:
|
||||
"""
|
||||
|
||||
from typing_extensions import override
|
||||
from collections.abc import AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import TYPE_CHECKING, AsyncGenerator
|
||||
from typing import TYPE_CHECKING, Union, Optional
|
||||
|
||||
from multidict import CIMultiDict
|
||||
|
||||
from nonebot.drivers import Request, Response
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.drivers import URL, Request, Response
|
||||
from nonebot.drivers.none import Driver as NoneDriver
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.internal.driver import Cookies, QueryTypes, CookieTypes, HeaderTypes
|
||||
from nonebot.drivers import (
|
||||
HTTPVersion,
|
||||
HTTPClientMixin,
|
||||
HTTPClientSession,
|
||||
WebSocketClientMixin,
|
||||
combine_driver,
|
||||
)
|
||||
@@ -39,6 +44,107 @@ except ModuleNotFoundError as e: # pragma: no cover
|
||||
) from e
|
||||
|
||||
|
||||
class Session(HTTPClientSession):
|
||||
@override
|
||||
def __init__(
|
||||
self,
|
||||
params: QueryTypes = None,
|
||||
headers: HeaderTypes = None,
|
||||
cookies: CookieTypes = None,
|
||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||
timeout: Optional[float] = None,
|
||||
proxy: Optional[str] = None,
|
||||
):
|
||||
self._client: Optional[aiohttp.ClientSession] = None
|
||||
|
||||
self._params = URL.build(query=params).query if params is not None else None
|
||||
|
||||
self._headers = CIMultiDict(headers) if headers is not None else None
|
||||
self._cookies = tuple(
|
||||
(cookie.name, cookie.value)
|
||||
for cookie in Cookies(cookies)
|
||||
if cookie.value is not None
|
||||
)
|
||||
|
||||
version = HTTPVersion(version)
|
||||
if version == HTTPVersion.H10:
|
||||
self._version = aiohttp.HttpVersion10
|
||||
elif version == HTTPVersion.H11:
|
||||
self._version = aiohttp.HttpVersion11
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported HTTP version: {version}")
|
||||
|
||||
self._timeout = timeout
|
||||
self._proxy = proxy
|
||||
|
||||
@property
|
||||
def client(self) -> aiohttp.ClientSession:
|
||||
if self._client is None:
|
||||
raise RuntimeError("Session is not initialized")
|
||||
return self._client
|
||||
|
||||
@override
|
||||
async def request(self, setup: Request) -> Response:
|
||||
if self._params:
|
||||
params = self._params.copy()
|
||||
params.update(setup.url.query)
|
||||
url = setup.url.with_query(params)
|
||||
else:
|
||||
url = setup.url
|
||||
|
||||
data = setup.data
|
||||
if setup.files:
|
||||
data = aiohttp.FormData(data or {}, quote_fields=False)
|
||||
for name, file in setup.files:
|
||||
data.add_field(name, file[1], content_type=file[2], filename=file[0])
|
||||
|
||||
cookies = (
|
||||
(cookie.name, cookie.value)
|
||||
for cookie in setup.cookies
|
||||
if cookie.value is not None
|
||||
)
|
||||
|
||||
timeout = aiohttp.ClientTimeout(setup.timeout)
|
||||
|
||||
async with await self.client.request(
|
||||
setup.method,
|
||||
url,
|
||||
data=setup.content or data,
|
||||
json=setup.json,
|
||||
cookies=cookies,
|
||||
headers=setup.headers,
|
||||
proxy=setup.proxy or self._proxy,
|
||||
timeout=timeout,
|
||||
) as response:
|
||||
return Response(
|
||||
response.status,
|
||||
headers=response.headers.copy(),
|
||||
content=await response.read(),
|
||||
request=setup,
|
||||
)
|
||||
|
||||
@override
|
||||
async def setup(self) -> None:
|
||||
if self._client is not None:
|
||||
raise RuntimeError("Session has already been initialized")
|
||||
self._client = aiohttp.ClientSession(
|
||||
cookies=self._cookies,
|
||||
headers=self._headers,
|
||||
version=self._version,
|
||||
timeout=self._timeout,
|
||||
trust_env=True,
|
||||
)
|
||||
await self._client.__aenter__()
|
||||
|
||||
@override
|
||||
async def close(self) -> None:
|
||||
try:
|
||||
if self._client is not None:
|
||||
await self._client.close()
|
||||
finally:
|
||||
self._client = None
|
||||
|
||||
|
||||
class Mixin(HTTPClientMixin, WebSocketClientMixin):
|
||||
"""AIOHTTP Mixin"""
|
||||
|
||||
@@ -49,42 +155,8 @@ class Mixin(HTTPClientMixin, WebSocketClientMixin):
|
||||
|
||||
@override
|
||||
async def request(self, setup: Request) -> Response:
|
||||
if setup.version == HTTPVersion.H10:
|
||||
version = aiohttp.HttpVersion10
|
||||
elif setup.version == HTTPVersion.H11:
|
||||
version = aiohttp.HttpVersion11
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported HTTP version: {setup.version}")
|
||||
|
||||
timeout = aiohttp.ClientTimeout(setup.timeout)
|
||||
|
||||
data = setup.data
|
||||
if setup.files:
|
||||
data = aiohttp.FormData(data or {})
|
||||
for name, file in setup.files:
|
||||
data.add_field(name, file[1], content_type=file[2], filename=file[0])
|
||||
|
||||
cookies = {
|
||||
cookie.name: cookie.value for cookie in setup.cookies if cookie.value
|
||||
}
|
||||
async with aiohttp.ClientSession(
|
||||
cookies=cookies, version=version, trust_env=True
|
||||
) as session:
|
||||
async with session.request(
|
||||
setup.method,
|
||||
setup.url,
|
||||
data=setup.content or data,
|
||||
json=setup.json,
|
||||
headers=setup.headers,
|
||||
timeout=timeout,
|
||||
proxy=setup.proxy,
|
||||
) as response:
|
||||
return Response(
|
||||
response.status,
|
||||
headers=response.headers.copy(),
|
||||
content=await response.read(),
|
||||
request=setup,
|
||||
)
|
||||
async with self.get_session() as session:
|
||||
return await session.request(setup)
|
||||
|
||||
@override
|
||||
@asynccontextmanager
|
||||
@@ -106,6 +178,25 @@ class Mixin(HTTPClientMixin, WebSocketClientMixin):
|
||||
) as ws:
|
||||
yield WebSocket(request=setup, session=session, websocket=ws)
|
||||
|
||||
@override
|
||||
def get_session(
|
||||
self,
|
||||
params: QueryTypes = None,
|
||||
headers: HeaderTypes = None,
|
||||
cookies: CookieTypes = None,
|
||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||
timeout: Optional[float] = None,
|
||||
proxy: Optional[str] = None,
|
||||
) -> Session:
|
||||
return Session(
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
version=version,
|
||||
timeout=timeout,
|
||||
proxy=proxy,
|
||||
)
|
||||
|
||||
|
||||
class WebSocket(BaseWebSocket):
|
||||
"""AIOHTTP Websocket Wrapper"""
|
||||
@@ -131,8 +222,8 @@ class WebSocket(BaseWebSocket):
|
||||
raise NotImplementedError
|
||||
|
||||
@override
|
||||
async def close(self, code: int = 1000):
|
||||
await self.websocket.close(code=code)
|
||||
async def close(self, code: int = 1000, reason: str = ""):
|
||||
await self.websocket.close(code=code, message=reason.encode("utf-8"))
|
||||
await self.session.close()
|
||||
|
||||
async def _receive(self) -> aiohttp.WSMessage:
|
||||
|
@@ -19,7 +19,7 @@ import logging
|
||||
import contextlib
|
||||
from functools import wraps
|
||||
from typing_extensions import override
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||
from typing import Any, Union, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -31,6 +31,7 @@ from nonebot.drivers import Driver as BaseDriver
|
||||
from nonebot.config import Config as NoneBotConfig
|
||||
from nonebot.drivers import Request as BaseRequest
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.compat import model_dump, type_validate_python
|
||||
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
||||
|
||||
try:
|
||||
@@ -71,15 +72,15 @@ class Config(BaseModel):
|
||||
"""是否包含适配器路由的 schema,默认为 `True`"""
|
||||
fastapi_reload: bool = False
|
||||
"""开启/关闭冷重载"""
|
||||
fastapi_reload_dirs: Optional[List[str]] = None
|
||||
fastapi_reload_dirs: Optional[list[str]] = None
|
||||
"""重载监控文件夹列表,默认为 uvicorn 默认值"""
|
||||
fastapi_reload_delay: float = 0.25
|
||||
"""重载延迟,默认为 uvicorn 默认值"""
|
||||
fastapi_reload_includes: Optional[List[str]] = None
|
||||
fastapi_reload_includes: Optional[list[str]] = None
|
||||
"""要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
fastapi_reload_excludes: Optional[List[str]] = None
|
||||
fastapi_reload_excludes: Optional[list[str]] = None
|
||||
"""不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
fastapi_extra: Dict[str, Any] = {}
|
||||
fastapi_extra: dict[str, Any] = {}
|
||||
"""传递给 `FastAPI` 的其他参数。"""
|
||||
|
||||
|
||||
@@ -89,7 +90,7 @@ class Driver(BaseDriver, ASGIMixin):
|
||||
def __init__(self, env: Env, config: NoneBotConfig):
|
||||
super().__init__(env, config)
|
||||
|
||||
self.fastapi_config: Config = Config(**config.dict())
|
||||
self.fastapi_config: Config = type_validate_python(Config, model_dump(config))
|
||||
|
||||
self._server_app = FastAPI(
|
||||
lifespan=self._lifespan_manager,
|
||||
@@ -160,7 +161,7 @@ class Driver(BaseDriver, ASGIMixin):
|
||||
self,
|
||||
host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
*,
|
||||
*args,
|
||||
app: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
@@ -205,7 +206,7 @@ class Driver(BaseDriver, ASGIMixin):
|
||||
json = await request.json()
|
||||
|
||||
data: Optional[dict] = None
|
||||
files: Optional[List[Tuple[str, FileTypes]]] = None
|
||||
files: Optional[list[tuple[str, FileTypes]]] = None
|
||||
with contextlib.suppress(Exception):
|
||||
form = await request.form()
|
||||
data = {}
|
||||
|
@@ -15,15 +15,20 @@ FrontMatter:
|
||||
description: nonebot.drivers.httpx 模块
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing_extensions import override
|
||||
from typing import TYPE_CHECKING, Union, Optional
|
||||
|
||||
from multidict import CIMultiDict
|
||||
|
||||
from nonebot.drivers.none import Driver as NoneDriver
|
||||
from nonebot.internal.driver import Cookies, QueryTypes, CookieTypes, HeaderTypes
|
||||
from nonebot.drivers import (
|
||||
URL,
|
||||
Request,
|
||||
Response,
|
||||
HTTPVersion,
|
||||
HTTPClientMixin,
|
||||
HTTPClientSession,
|
||||
combine_driver,
|
||||
)
|
||||
|
||||
@@ -36,6 +41,79 @@ except ModuleNotFoundError as e: # pragma: no cover
|
||||
) from e
|
||||
|
||||
|
||||
class Session(HTTPClientSession):
|
||||
@override
|
||||
def __init__(
|
||||
self,
|
||||
params: QueryTypes = None,
|
||||
headers: HeaderTypes = None,
|
||||
cookies: CookieTypes = None,
|
||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||
timeout: Optional[float] = None,
|
||||
proxy: Optional[str] = None,
|
||||
):
|
||||
self._client: Optional[httpx.AsyncClient] = None
|
||||
|
||||
self._params = (
|
||||
tuple(URL.build(query=params).query.items()) if params is not None else None
|
||||
)
|
||||
self._headers = (
|
||||
tuple(CIMultiDict(headers).items()) if headers is not None else None
|
||||
)
|
||||
self._cookies = Cookies(cookies)
|
||||
self._version = HTTPVersion(version)
|
||||
self._timeout = timeout
|
||||
self._proxy = proxy
|
||||
|
||||
@property
|
||||
def client(self) -> httpx.AsyncClient:
|
||||
if self._client is None:
|
||||
raise RuntimeError("Session is not initialized")
|
||||
return self._client
|
||||
|
||||
@override
|
||||
async def request(self, setup: Request) -> Response:
|
||||
response = await self.client.request(
|
||||
setup.method,
|
||||
str(setup.url),
|
||||
content=setup.content,
|
||||
data=setup.data,
|
||||
files=setup.files,
|
||||
json=setup.json,
|
||||
headers=tuple(setup.headers.items()),
|
||||
cookies=setup.cookies.jar,
|
||||
timeout=setup.timeout,
|
||||
)
|
||||
return Response(
|
||||
response.status_code,
|
||||
headers=response.headers.multi_items(),
|
||||
content=response.content,
|
||||
request=setup,
|
||||
)
|
||||
|
||||
@override
|
||||
async def setup(self) -> None:
|
||||
if self._client is not None:
|
||||
raise RuntimeError("Session has already been initialized")
|
||||
self._client = httpx.AsyncClient(
|
||||
params=self._params,
|
||||
headers=self._headers,
|
||||
cookies=self._cookies.jar,
|
||||
http2=self._version == HTTPVersion.H2,
|
||||
proxies=self._proxy,
|
||||
follow_redirects=True,
|
||||
)
|
||||
await self._client.__aenter__()
|
||||
|
||||
@override
|
||||
async def close(self) -> None:
|
||||
try:
|
||||
if self._client is not None:
|
||||
await self._client.aclose()
|
||||
finally:
|
||||
self._client = None
|
||||
|
||||
|
||||
class Mixin(HTTPClientMixin):
|
||||
"""HTTPX Mixin"""
|
||||
|
||||
@@ -46,28 +124,29 @@ class Mixin(HTTPClientMixin):
|
||||
|
||||
@override
|
||||
async def request(self, setup: Request) -> Response:
|
||||
async with httpx.AsyncClient(
|
||||
cookies=setup.cookies.jar,
|
||||
http2=setup.version == HTTPVersion.H2,
|
||||
proxies=setup.proxy,
|
||||
follow_redirects=True,
|
||||
) as client:
|
||||
response = await client.request(
|
||||
setup.method,
|
||||
str(setup.url),
|
||||
content=setup.content,
|
||||
data=setup.data,
|
||||
json=setup.json,
|
||||
files=setup.files,
|
||||
headers=tuple(setup.headers.items()),
|
||||
timeout=setup.timeout,
|
||||
)
|
||||
return Response(
|
||||
response.status_code,
|
||||
headers=response.headers.multi_items(),
|
||||
content=response.content,
|
||||
request=setup,
|
||||
)
|
||||
async with self.get_session(
|
||||
version=setup.version, proxy=setup.proxy
|
||||
) as session:
|
||||
return await session.request(setup)
|
||||
|
||||
@override
|
||||
def get_session(
|
||||
self,
|
||||
params: QueryTypes = None,
|
||||
headers: HeaderTypes = None,
|
||||
cookies: CookieTypes = None,
|
||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||
timeout: Optional[float] = None,
|
||||
proxy: Optional[str] = None,
|
||||
) -> Session:
|
||||
return Session(
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
version=version,
|
||||
timeout=timeout,
|
||||
proxy=proxy,
|
||||
)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@@ -68,9 +68,11 @@ class Driver(BaseDriver):
|
||||
await self._lifespan.startup()
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running startup function. "
|
||||
"Ignored!</bg #f8bbd0></r>"
|
||||
"<r><bg #f8bbd0>Application startup failed. "
|
||||
"Exiting.</bg #f8bbd0></r>"
|
||||
)
|
||||
self.should_exit.set()
|
||||
return
|
||||
|
||||
logger.info("Application startup completed.")
|
||||
|
||||
|
@@ -18,7 +18,7 @@ FrontMatter:
|
||||
import asyncio
|
||||
from functools import wraps
|
||||
from typing_extensions import override
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional, cast
|
||||
from typing import Any, Union, Optional, cast
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -30,6 +30,7 @@ from nonebot.drivers import Driver as BaseDriver
|
||||
from nonebot.config import Config as NoneBotConfig
|
||||
from nonebot.drivers import Request as BaseRequest
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.compat import model_dump, type_validate_python
|
||||
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
|
||||
|
||||
try:
|
||||
@@ -63,15 +64,15 @@ class Config(BaseModel):
|
||||
|
||||
quart_reload: bool = False
|
||||
"""开启/关闭冷重载"""
|
||||
quart_reload_dirs: Optional[List[str]] = None
|
||||
quart_reload_dirs: Optional[list[str]] = None
|
||||
"""重载监控文件夹列表,默认为 uvicorn 默认值"""
|
||||
quart_reload_delay: float = 0.25
|
||||
"""重载延迟,默认为 uvicorn 默认值"""
|
||||
quart_reload_includes: Optional[List[str]] = None
|
||||
quart_reload_includes: Optional[list[str]] = None
|
||||
"""要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
quart_reload_excludes: Optional[List[str]] = None
|
||||
quart_reload_excludes: Optional[list[str]] = None
|
||||
"""不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
quart_extra: Dict[str, Any] = {}
|
||||
quart_extra: dict[str, Any] = {}
|
||||
"""传递给 `Quart` 的其他参数。"""
|
||||
|
||||
|
||||
@@ -81,7 +82,7 @@ class Driver(BaseDriver, ASGIMixin):
|
||||
def __init__(self, env: Env, config: NoneBotConfig):
|
||||
super().__init__(env, config)
|
||||
|
||||
self.quart_config = Config(**config.dict())
|
||||
self.quart_config = type_validate_python(Config, model_dump(config))
|
||||
|
||||
self._server_app = Quart(
|
||||
self.__class__.__qualname__, **self.quart_config.quart_extra
|
||||
@@ -141,7 +142,7 @@ class Driver(BaseDriver, ASGIMixin):
|
||||
self,
|
||||
host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
*,
|
||||
*args,
|
||||
app: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
@@ -183,7 +184,7 @@ class Driver(BaseDriver, ASGIMixin):
|
||||
|
||||
data = await request.form
|
||||
files_dict = await request.files
|
||||
files: List[Tuple[str, FileTypes]] = []
|
||||
files: list[tuple[str, FileTypes]] = []
|
||||
key: str
|
||||
value: FileStorage
|
||||
for key, value in files_dict.items():
|
||||
|
@@ -19,7 +19,8 @@ import logging
|
||||
from functools import wraps
|
||||
from contextlib import asynccontextmanager
|
||||
from typing_extensions import ParamSpec, override
|
||||
from typing import TYPE_CHECKING, Union, TypeVar, Callable, Awaitable, AsyncGenerator
|
||||
from collections.abc import Coroutine, AsyncGenerator
|
||||
from typing import TYPE_CHECKING, Any, Union, TypeVar, Callable
|
||||
|
||||
from nonebot.drivers import Request
|
||||
from nonebot.log import LoguruHandler
|
||||
@@ -44,7 +45,9 @@ logger = logging.Logger("websockets.client", "INFO")
|
||||
logger.addHandler(LoguruHandler())
|
||||
|
||||
|
||||
def catch_closed(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
|
||||
def catch_closed(
|
||||
func: Callable[P, Coroutine[Any, Any, T]]
|
||||
) -> Callable[P, Coroutine[Any, Any, T]]:
|
||||
@wraps(func)
|
||||
async def decorator(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||
try:
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import abc
|
||||
from typing import Any
|
||||
from collections.abc import AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any, Dict, AsyncGenerator
|
||||
|
||||
from nonebot.config import Config
|
||||
from nonebot.internal.driver._lifespan import LIFESPAN_FUNC
|
||||
@@ -32,7 +33,7 @@ class Adapter(abc.ABC):
|
||||
def __init__(self, driver: Driver, **kwargs: Any):
|
||||
self.driver: Driver = driver
|
||||
"""{ref}`nonebot.drivers.Driver` 实例"""
|
||||
self.bots: Dict[str, Bot] = {}
|
||||
self.bots: dict[str, Bot] = {}
|
||||
"""本协议适配器已建立连接的 {ref}`nonebot.adapters.Bot` 实例"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import abc
|
||||
import asyncio
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Any, Set, Union, Optional, Protocol
|
||||
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional, Protocol
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.config import Config
|
||||
@@ -27,9 +27,9 @@ class Bot(abc.ABC):
|
||||
self_id: 机器人 ID
|
||||
"""
|
||||
|
||||
_calling_api_hook: Set[T_CallingAPIHook] = set()
|
||||
_calling_api_hook: ClassVar[set[T_CallingAPIHook]] = set()
|
||||
"""call_api 时执行的函数"""
|
||||
_called_api_hook: Set[T_CalledAPIHook] = set()
|
||||
_called_api_hook: ClassVar[set[T_CalledAPIHook]] = set()
|
||||
"""call_api 后执行的函数"""
|
||||
|
||||
def __init__(self, adapter: "Adapter", self_id: str):
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import abc
|
||||
from typing import Any, Type, TypeVar
|
||||
from typing import Any, TypeVar
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -20,12 +20,12 @@ class Event(abc.ABC, BaseModel):
|
||||
|
||||
class Config(ConfigDict):
|
||||
extra = "allow" # type: ignore
|
||||
json_encoders = {Message: DataclassEncoder}
|
||||
json_encoders = {Message: DataclassEncoder} # noqa: RUF012
|
||||
|
||||
if not PYDANTIC_V2: # pragma: pydantic-v1
|
||||
|
||||
@classmethod
|
||||
def validate(cls: Type["E"], value: Any) -> "E":
|
||||
def validate(cls: type["E"], value: Any) -> "E":
|
||||
if isinstance(value, Event) and not isinstance(value, cls):
|
||||
raise TypeError(f"{value} is incompatible with Event type {cls}")
|
||||
return super().validate(value)
|
||||
|
@@ -1,17 +1,14 @@
|
||||
import abc
|
||||
from copy import deepcopy
|
||||
from typing_extensions import Self
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import field, asdict, dataclass
|
||||
from typing import (
|
||||
from typing import ( # noqa: UP035
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Generic,
|
||||
TypeVar,
|
||||
Iterable,
|
||||
Optional,
|
||||
SupportsIndex,
|
||||
overload,
|
||||
@@ -32,12 +29,12 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
||||
|
||||
type: str
|
||||
"""消息段类型"""
|
||||
data: Dict[str, Any] = field(default_factory=dict)
|
||||
data: dict[str, Any] = field(default_factory=dict)
|
||||
"""消息段数据"""
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_message_class(cls) -> Type[TM]:
|
||||
def get_message_class(cls) -> Type[TM]: # noqa: UP006
|
||||
"""获取消息数组类型"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -49,7 +46,9 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
||||
def __len__(self) -> int:
|
||||
return len(str(self))
|
||||
|
||||
def __ne__(self, other: Self) -> bool:
|
||||
def __ne__( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, other: Self
|
||||
) -> bool:
|
||||
return not self == other
|
||||
|
||||
def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
||||
@@ -101,7 +100,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
|
||||
|
||||
|
||||
@custom_validation
|
||||
class Message(List[TMS], abc.ABC):
|
||||
class Message(list[TMS], abc.ABC):
|
||||
"""消息序列
|
||||
|
||||
参数:
|
||||
@@ -142,7 +141,7 @@ class Message(List[TMS], abc.ABC):
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_segment_class(cls) -> Type[TMS]:
|
||||
def get_segment_class(cls) -> type[TMS]:
|
||||
"""获取消息段类型"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -177,7 +176,9 @@ class Message(List[TMS], abc.ABC):
|
||||
"""构造消息数组"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __add__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
|
||||
def __add__( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, other: Union[str, TMS, Iterable[TMS]]
|
||||
) -> Self:
|
||||
result = self.copy()
|
||||
result += other
|
||||
return result
|
||||
@@ -209,7 +210,7 @@ class Message(List[TMS], abc.ABC):
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __getitem__(self, args: Tuple[str, int]) -> TMS:
|
||||
def __getitem__(self, args: tuple[str, int]) -> TMS:
|
||||
"""索引指定类型的消息段
|
||||
|
||||
参数:
|
||||
@@ -220,7 +221,7 @@ class Message(List[TMS], abc.ABC):
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __getitem__(self, args: Tuple[str, slice]) -> Self:
|
||||
def __getitem__(self, args: tuple[str, slice]) -> Self:
|
||||
"""切片指定类型的消息段
|
||||
|
||||
参数:
|
||||
@@ -252,12 +253,12 @@ class Message(List[TMS], abc.ABC):
|
||||
消息切片 `args`
|
||||
"""
|
||||
|
||||
def __getitem__(
|
||||
def __getitem__( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self,
|
||||
args: Union[
|
||||
str,
|
||||
Tuple[str, int],
|
||||
Tuple[str, slice],
|
||||
tuple[str, int],
|
||||
tuple[str, slice],
|
||||
int,
|
||||
slice,
|
||||
],
|
||||
@@ -276,7 +277,9 @@ class Message(List[TMS], abc.ABC):
|
||||
else:
|
||||
raise ValueError("Incorrect arguments to slice") # pragma: no cover
|
||||
|
||||
def __contains__(self, value: Union[TMS, str]) -> bool:
|
||||
def __contains__( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, value: Union[TMS, str]
|
||||
) -> bool:
|
||||
"""检查消息段是否存在
|
||||
|
||||
参数:
|
||||
@@ -285,7 +288,7 @@ class Message(List[TMS], abc.ABC):
|
||||
消息内是否存在给定消息段或给定类型的消息段
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
return bool(next((seg for seg in self if seg.type == value), None))
|
||||
return next((seg for seg in self if seg.type == value), None) is not None
|
||||
return super().__contains__(value)
|
||||
|
||||
def has(self, value: Union[TMS, str]) -> bool:
|
||||
@@ -359,7 +362,9 @@ class Message(List[TMS], abc.ABC):
|
||||
return all(seg.type == value for seg in self)
|
||||
return all(seg == value for seg in self)
|
||||
|
||||
def append(self, obj: Union[str, TMS]) -> Self:
|
||||
def append( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, obj: Union[str, TMS]
|
||||
) -> Self:
|
||||
"""添加一个消息段到消息数组末尾。
|
||||
|
||||
参数:
|
||||
@@ -373,7 +378,9 @@ class Message(List[TMS], abc.ABC):
|
||||
raise ValueError(f"Unexpected type: {type(obj)} {obj}") # pragma: no cover
|
||||
return self
|
||||
|
||||
def extend(self, obj: Union[Self, Iterable[TMS]]) -> Self:
|
||||
def extend( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, obj: Union[Self, Iterable[TMS]]
|
||||
) -> Self:
|
||||
"""拼接一个消息数组或多个消息段到消息数组末尾。
|
||||
|
||||
参数:
|
||||
|
@@ -1,21 +1,15 @@
|
||||
import functools
|
||||
from string import Formatter
|
||||
from typing_extensions import TypeAlias
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Set,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Generic,
|
||||
Mapping,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Optional,
|
||||
Sequence,
|
||||
cast,
|
||||
overload,
|
||||
)
|
||||
@@ -25,9 +19,9 @@ from _string import formatter_field_name_split # type: ignore
|
||||
if TYPE_CHECKING:
|
||||
from .message import Message, MessageSegment
|
||||
|
||||
def formatter_field_name_split( # noqa: F811
|
||||
def formatter_field_name_split(
|
||||
field_name: str,
|
||||
) -> Tuple[str, List[Tuple[bool, str]]]: ...
|
||||
) -> tuple[str, list[tuple[bool, str]]]: ...
|
||||
|
||||
|
||||
TM = TypeVar("TM", bound="Message")
|
||||
@@ -50,7 +44,7 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
def __init__(
|
||||
self: "MessageTemplate[str]",
|
||||
template: str,
|
||||
factory: Type[str] = str,
|
||||
factory: type[str] = str,
|
||||
private_getattr: bool = False,
|
||||
) -> None: ...
|
||||
|
||||
@@ -58,19 +52,19 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
def __init__(
|
||||
self: "MessageTemplate[TM]",
|
||||
template: Union[str, TM],
|
||||
factory: Type[TM],
|
||||
factory: type[TM],
|
||||
private_getattr: bool = False,
|
||||
) -> None: ...
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
template: Union[str, TM],
|
||||
factory: Union[Type[str], Type[TM]] = str,
|
||||
factory: Union[type[str], type[TM]] = str,
|
||||
private_getattr: bool = False,
|
||||
) -> None:
|
||||
self.template: TF = template # type: ignore
|
||||
self.factory: Type[TF] = factory # type: ignore
|
||||
self.format_specs: Dict[str, FormatSpecFunc] = {}
|
||||
self.factory: type[TF] = factory # type: ignore
|
||||
self.format_specs: dict[str, FormatSpecFunc] = {}
|
||||
self.private_getattr = private_getattr
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -85,7 +79,9 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
self.format_specs[name] = spec
|
||||
return spec
|
||||
|
||||
def format(self, *args, **kwargs):
|
||||
def format( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, *args, **kwargs
|
||||
) -> TF:
|
||||
"""根据传入参数和模板生成消息对象"""
|
||||
return self._format(args, kwargs)
|
||||
|
||||
@@ -118,7 +114,7 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
self.check_unused_args(used_args, args, kwargs)
|
||||
return cast(TF, full_message)
|
||||
|
||||
def vformat(
|
||||
def vformat( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self,
|
||||
format_string: str,
|
||||
args: Sequence[Any],
|
||||
@@ -126,15 +122,15 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
) -> TF:
|
||||
raise NotImplementedError("`vformat` has merged into `_format`")
|
||||
|
||||
def _vformat(
|
||||
def _vformat( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self,
|
||||
format_string: str,
|
||||
args: Sequence[Any],
|
||||
kwargs: Mapping[str, Any],
|
||||
used_args: Set[Union[int, str]],
|
||||
used_args: set[Union[int, str]],
|
||||
auto_arg_index: int = 0,
|
||||
) -> Tuple[TF, int]:
|
||||
results: List[Any] = [self.factory()]
|
||||
) -> tuple[TF, int]:
|
||||
results: list[Any] = [self.factory()]
|
||||
|
||||
for literal_text, field_name, format_spec, conversion in self.parse(
|
||||
format_string
|
||||
@@ -185,7 +181,7 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
|
||||
def get_field(
|
||||
self, field_name: str, args: Sequence[Any], kwargs: Mapping[str, Any]
|
||||
) -> Tuple[Any, Union[int, str]]:
|
||||
) -> tuple[Any, Union[int, str]]:
|
||||
first, rest = formatter_field_name_split(field_name)
|
||||
obj = self.get_value(first, args, kwargs)
|
||||
|
||||
@@ -199,7 +195,7 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
def format_field(self, value: Any, format_spec: str) -> Any:
|
||||
formatter: Optional[FormatSpecFunc] = self.format_specs.get(format_spec)
|
||||
if formatter is None and not issubclass(self.factory, str):
|
||||
segment_class: Type["MessageSegment"] = self.factory.get_segment_class()
|
||||
segment_class: type["MessageSegment"] = self.factory.get_segment_class()
|
||||
method = getattr(segment_class, format_spec, None)
|
||||
if callable(method) and not cast(str, method.__name__).startswith("_"):
|
||||
formatter = getattr(segment_class, format_spec)
|
||||
|
@@ -26,5 +26,6 @@ from .abstract import ReverseDriver as ReverseDriver
|
||||
from .combine import combine_driver as combine_driver
|
||||
from .model import HTTPServerSetup as HTTPServerSetup
|
||||
from .abstract import HTTPClientMixin as HTTPClientMixin
|
||||
from .abstract import HTTPClientSession as HTTPClientSession
|
||||
from .model import WebSocketServerSetup as WebSocketServerSetup
|
||||
from .abstract import WebSocketClientMixin as WebSocketClientMixin
|
||||
|
@@ -1,5 +1,6 @@
|
||||
from collections.abc import Awaitable
|
||||
from typing_extensions import TypeAlias
|
||||
from typing import Any, List, Union, Callable, Awaitable, cast
|
||||
from typing import Any, Union, Callable, cast
|
||||
|
||||
from nonebot.utils import run_sync, is_coroutine_callable
|
||||
|
||||
@@ -10,9 +11,9 @@ LIFESPAN_FUNC: TypeAlias = Union[SYNC_LIFESPAN_FUNC, ASYNC_LIFESPAN_FUNC]
|
||||
|
||||
class Lifespan:
|
||||
def __init__(self) -> None:
|
||||
self._startup_funcs: List[LIFESPAN_FUNC] = []
|
||||
self._ready_funcs: List[LIFESPAN_FUNC] = []
|
||||
self._shutdown_funcs: List[LIFESPAN_FUNC] = []
|
||||
self._startup_funcs: list[LIFESPAN_FUNC] = []
|
||||
self._ready_funcs: list[LIFESPAN_FUNC] = []
|
||||
self._shutdown_funcs: list[LIFESPAN_FUNC] = []
|
||||
|
||||
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||
self._startup_funcs.append(func)
|
||||
@@ -28,7 +29,7 @@ class Lifespan:
|
||||
|
||||
@staticmethod
|
||||
async def _run_lifespan_func(
|
||||
funcs: List[LIFESPAN_FUNC],
|
||||
funcs: list[LIFESPAN_FUNC],
|
||||
) -> None:
|
||||
for func in funcs:
|
||||
if is_coroutine_callable(func):
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import abc
|
||||
import asyncio
|
||||
from typing_extensions import TypeAlias
|
||||
from types import TracebackType
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing_extensions import Self, TypeAlias
|
||||
from contextlib import AsyncExitStack, asynccontextmanager
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, AsyncGenerator
|
||||
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.config import Env, Config
|
||||
@@ -17,7 +19,17 @@ from nonebot.typing import (
|
||||
)
|
||||
|
||||
from ._lifespan import LIFESPAN_FUNC, Lifespan
|
||||
from .model import Request, Response, WebSocket, HTTPServerSetup, WebSocketServerSetup
|
||||
from .model import (
|
||||
Request,
|
||||
Response,
|
||||
WebSocket,
|
||||
QueryTypes,
|
||||
CookieTypes,
|
||||
HeaderTypes,
|
||||
HTTPVersion,
|
||||
HTTPServerSetup,
|
||||
WebSocketServerSetup,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.internal.adapter import Bot, Adapter
|
||||
@@ -36,11 +48,11 @@ class Driver(abc.ABC):
|
||||
config: 包含配置信息的 Config 对象
|
||||
"""
|
||||
|
||||
_adapters: Dict[str, "Adapter"] = {}
|
||||
_adapters: ClassVar[dict[str, "Adapter"]] = {}
|
||||
"""已注册的适配器列表"""
|
||||
_bot_connection_hook: Set[Dependent[Any]] = set()
|
||||
_bot_connection_hook: ClassVar[set[Dependent[Any]]] = set()
|
||||
"""Bot 连接建立时执行的函数"""
|
||||
_bot_disconnection_hook: Set[Dependent[Any]] = set()
|
||||
_bot_disconnection_hook: ClassVar[set[Dependent[Any]]] = set()
|
||||
"""Bot 连接断开时执行的函数"""
|
||||
|
||||
def __init__(self, env: Env, config: Config):
|
||||
@@ -48,8 +60,8 @@ class Driver(abc.ABC):
|
||||
"""环境名称"""
|
||||
self.config: Config = config
|
||||
"""全局配置对象"""
|
||||
self._bots: Dict[str, "Bot"] = {}
|
||||
self._bot_tasks: Set[asyncio.Task] = set()
|
||||
self._bots: dict[str, "Bot"] = {}
|
||||
self._bot_tasks: set[asyncio.Task] = set()
|
||||
self._lifespan = Lifespan()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -59,11 +71,11 @@ class Driver(abc.ABC):
|
||||
)
|
||||
|
||||
@property
|
||||
def bots(self) -> Dict[str, "Bot"]:
|
||||
def bots(self) -> dict[str, "Bot"]:
|
||||
"""获取当前所有已连接的 Bot"""
|
||||
return self._bots
|
||||
|
||||
def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None:
|
||||
def register_adapter(self, adapter: type["Adapter"], **kwargs) -> None:
|
||||
"""注册一个协议适配器
|
||||
|
||||
参数:
|
||||
@@ -222,6 +234,49 @@ class ReverseMixin(Mixin):
|
||||
"""服务端混入基类。"""
|
||||
|
||||
|
||||
class HTTPClientSession(abc.ABC):
|
||||
"""HTTP 客户端会话基类。"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __init__(
|
||||
self,
|
||||
params: QueryTypes = None,
|
||||
headers: HeaderTypes = None,
|
||||
cookies: CookieTypes = None,
|
||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||
timeout: Optional[float] = None,
|
||||
proxy: Optional[str] = None,
|
||||
):
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
async def request(self, setup: Request) -> Response:
|
||||
"""发送一个 HTTP 请求"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
async def setup(self) -> None:
|
||||
"""初始化会话"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
async def close(self) -> None:
|
||||
"""关闭会话"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def __aenter__(self) -> Self:
|
||||
await self.setup()
|
||||
return self
|
||||
|
||||
async def __aexit__(
|
||||
self,
|
||||
exc_type: Optional[type[BaseException]],
|
||||
exc: Optional[BaseException],
|
||||
tb: Optional[TracebackType],
|
||||
) -> None:
|
||||
await self.close()
|
||||
|
||||
|
||||
class HTTPClientMixin(ForwardMixin):
|
||||
"""HTTP 客户端混入基类。"""
|
||||
|
||||
@@ -230,6 +285,19 @@ class HTTPClientMixin(ForwardMixin):
|
||||
"""发送一个 HTTP 请求"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_session(
|
||||
self,
|
||||
params: QueryTypes = None,
|
||||
headers: HeaderTypes = None,
|
||||
cookies: CookieTypes = None,
|
||||
version: Union[str, HTTPVersion] = HTTPVersion.H11,
|
||||
timeout: Optional[float] = None,
|
||||
proxy: Optional[str] = None,
|
||||
) -> HTTPClientSession:
|
||||
"""获取一个 HTTP 会话"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class WebSocketClientMixin(ForwardMixin):
|
||||
"""WebSocket 客户端混入基类。"""
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Type, Union, TypeVar, overload
|
||||
from typing import TYPE_CHECKING, Union, TypeVar, overload
|
||||
|
||||
from .abstract import Mixin, Driver
|
||||
|
||||
@@ -10,16 +10,18 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
@overload
|
||||
def combine_driver(driver: Type[D]) -> Type[D]: ...
|
||||
def combine_driver(driver: type[D]) -> type[D]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def combine_driver(driver: Type[D], *mixins: Type[Mixin]) -> Type["CombinedDriver"]: ...
|
||||
def combine_driver(
|
||||
driver: type[D], __m: type[Mixin], /, *mixins: type[Mixin]
|
||||
) -> type["CombinedDriver"]: ...
|
||||
|
||||
|
||||
def combine_driver(
|
||||
driver: Type[D], *mixins: Type[Mixin]
|
||||
) -> Union[Type[D], Type["CombinedDriver"]]:
|
||||
driver: type[D], *mixins: type[Mixin]
|
||||
) -> Union[type[D], type["CombinedDriver"]]:
|
||||
"""将一个驱动器和多个混入类合并。"""
|
||||
# check first
|
||||
if not issubclass(driver, Driver):
|
||||
|
@@ -4,56 +4,44 @@ from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing_extensions import TypeAlias
|
||||
from http.cookiejar import Cookie, CookieJar
|
||||
from typing import (
|
||||
IO,
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Tuple,
|
||||
Union,
|
||||
Mapping,
|
||||
Callable,
|
||||
Iterator,
|
||||
Optional,
|
||||
Awaitable,
|
||||
MutableMapping,
|
||||
)
|
||||
from typing import IO, Any, Union, Callable, Optional
|
||||
from collections.abc import Mapping, Iterator, Awaitable, MutableMapping
|
||||
|
||||
from yarl import URL as URL
|
||||
from multidict import CIMultiDict
|
||||
|
||||
RawURL: TypeAlias = Tuple[bytes, bytes, Optional[int], bytes]
|
||||
RawURL: TypeAlias = tuple[bytes, bytes, Optional[int], bytes]
|
||||
|
||||
SimpleQuery: TypeAlias = Union[str, int, float]
|
||||
QueryVariable: TypeAlias = Union[SimpleQuery, List[SimpleQuery]]
|
||||
QueryVariable: TypeAlias = Union[SimpleQuery, list[SimpleQuery]]
|
||||
QueryTypes: TypeAlias = Union[
|
||||
None, str, Mapping[str, QueryVariable], List[Tuple[str, QueryVariable]]
|
||||
None, str, Mapping[str, QueryVariable], list[tuple[str, SimpleQuery]]
|
||||
]
|
||||
|
||||
HeaderTypes: TypeAlias = Union[
|
||||
None,
|
||||
CIMultiDict[str],
|
||||
Dict[str, str],
|
||||
List[Tuple[str, str]],
|
||||
dict[str, str],
|
||||
list[tuple[str, str]],
|
||||
]
|
||||
|
||||
CookieTypes: TypeAlias = Union[
|
||||
None, "Cookies", CookieJar, Dict[str, str], List[Tuple[str, str]]
|
||||
None, "Cookies", CookieJar, dict[str, str], list[tuple[str, str]]
|
||||
]
|
||||
|
||||
ContentTypes: TypeAlias = Union[str, bytes, None]
|
||||
DataTypes: TypeAlias = Union[dict, None]
|
||||
FileContent: TypeAlias = Union[IO[bytes], bytes]
|
||||
FileType: TypeAlias = Tuple[Optional[str], FileContent, Optional[str]]
|
||||
FileType: TypeAlias = tuple[Optional[str], FileContent, Optional[str]]
|
||||
FileTypes: TypeAlias = Union[
|
||||
# file (or bytes)
|
||||
FileContent,
|
||||
# (filename, file (or bytes))
|
||||
Tuple[Optional[str], FileContent],
|
||||
tuple[Optional[str], FileContent],
|
||||
# (filename, file (or bytes), content_type)
|
||||
FileType,
|
||||
]
|
||||
FilesTypes: TypeAlias = Union[Dict[str, FileTypes], List[Tuple[str, FileTypes]], None]
|
||||
FilesTypes: TypeAlias = Union[dict[str, FileTypes], list[tuple[str, FileTypes]], None]
|
||||
|
||||
|
||||
class HTTPVersion(Enum):
|
||||
@@ -119,7 +107,7 @@ class Request:
|
||||
self.content: ContentTypes = content
|
||||
self.data: DataTypes = data
|
||||
self.json: Any = json
|
||||
self.files: Optional[List[Tuple[str, FileType]]] = None
|
||||
self.files: Optional[list[tuple[str, FileType]]] = None
|
||||
if files:
|
||||
self.files = []
|
||||
files_ = files.items() if isinstance(files, dict) else files
|
||||
@@ -257,7 +245,7 @@ class Cookies(MutableMapping):
|
||||
)
|
||||
self.jar.set_cookie(cookie)
|
||||
|
||||
def get(
|
||||
def get( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self,
|
||||
name: str,
|
||||
default: Optional[str] = None,
|
||||
@@ -298,12 +286,14 @@ class Cookies(MutableMapping):
|
||||
def clear(self, domain: Optional[str] = None, path: Optional[str] = None) -> None:
|
||||
self.jar.clear(domain, path)
|
||||
|
||||
def update(self, cookies: CookieTypes = None) -> None:
|
||||
def update( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, cookies: CookieTypes = None
|
||||
) -> None:
|
||||
cookies = Cookies(cookies)
|
||||
for cookie in cookies.jar:
|
||||
self.jar.set_cookie(cookie)
|
||||
|
||||
def as_header(self, request: Request) -> Dict[str, str]:
|
||||
def as_header(self, request: Request) -> dict[str, str]:
|
||||
urllib_request = self._CookieCompatRequest(request)
|
||||
self.jar.add_cookie_header(urllib_request)
|
||||
return urllib_request.added_headers
|
||||
@@ -341,9 +331,11 @@ class Cookies(MutableMapping):
|
||||
method=request.method,
|
||||
)
|
||||
self.request = request
|
||||
self.added_headers: Dict[str, str] = {}
|
||||
self.added_headers: dict[str, str] = {}
|
||||
|
||||
def add_unredirected_header(self, key: str, value: str) -> None:
|
||||
def add_unredirected_header( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, key: str, value: str
|
||||
) -> None:
|
||||
super().add_unredirected_header(key, value)
|
||||
self.added_headers[key] = value
|
||||
|
||||
|
@@ -1,18 +1,5 @@
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
TypeVar,
|
||||
Iterator,
|
||||
KeysView,
|
||||
Optional,
|
||||
ItemsView,
|
||||
ValuesView,
|
||||
MutableMapping,
|
||||
overload,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Union, TypeVar, Optional, overload
|
||||
from collections.abc import Iterator, KeysView, ItemsView, ValuesView, MutableMapping
|
||||
|
||||
from .provider import DEFAULT_PROVIDER_CLASS, MatcherProvider
|
||||
|
||||
@@ -22,7 +9,7 @@ if TYPE_CHECKING:
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class MatcherManager(MutableMapping[int, List[Type["Matcher"]]]):
|
||||
class MatcherManager(MutableMapping[int, list[type["Matcher"]]]):
|
||||
"""事件响应器管理器
|
||||
|
||||
实现了常用字典操作,用于管理事件响应器。
|
||||
@@ -43,10 +30,10 @@ class MatcherManager(MutableMapping[int, List[Type["Matcher"]]]):
|
||||
def __len__(self) -> int:
|
||||
return len(self.provider)
|
||||
|
||||
def __getitem__(self, key: int) -> List[Type["Matcher"]]:
|
||||
def __getitem__(self, key: int) -> list[type["Matcher"]]:
|
||||
return self.provider[key]
|
||||
|
||||
def __setitem__(self, key: int, value: List[Type["Matcher"]]) -> None:
|
||||
def __setitem__(self, key: int, value: list[type["Matcher"]]) -> None:
|
||||
self.provider[key] = value
|
||||
|
||||
def __delitem__(self, key: int) -> None:
|
||||
@@ -58,41 +45,45 @@ class MatcherManager(MutableMapping[int, List[Type["Matcher"]]]):
|
||||
def keys(self) -> KeysView[int]:
|
||||
return self.provider.keys()
|
||||
|
||||
def values(self) -> ValuesView[List[Type["Matcher"]]]:
|
||||
def values(self) -> ValuesView[list[type["Matcher"]]]:
|
||||
return self.provider.values()
|
||||
|
||||
def items(self) -> ItemsView[int, List[Type["Matcher"]]]:
|
||||
def items(self) -> ItemsView[int, list[type["Matcher"]]]:
|
||||
return self.provider.items()
|
||||
|
||||
@overload
|
||||
def get(self, key: int) -> Optional[List[Type["Matcher"]]]: ...
|
||||
def get(self, key: int) -> Optional[list[type["Matcher"]]]: ...
|
||||
|
||||
@overload
|
||||
def get(self, key: int, default: T) -> Union[List[Type["Matcher"]], T]: ...
|
||||
def get(self, key: int, default: T) -> Union[list[type["Matcher"]], T]: ...
|
||||
|
||||
def get(
|
||||
self, key: int, default: Optional[T] = None
|
||||
) -> Optional[Union[List[Type["Matcher"]], T]]:
|
||||
) -> Optional[Union[list[type["Matcher"]], T]]:
|
||||
return self.provider.get(key, default)
|
||||
|
||||
def pop(self, key: int) -> List[Type["Matcher"]]:
|
||||
def pop( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, key: int
|
||||
) -> list[type["Matcher"]]:
|
||||
return self.provider.pop(key)
|
||||
|
||||
def popitem(self) -> Tuple[int, List[Type["Matcher"]]]:
|
||||
def popitem(self) -> tuple[int, list[type["Matcher"]]]:
|
||||
return self.provider.popitem()
|
||||
|
||||
def clear(self) -> None:
|
||||
self.provider.clear()
|
||||
|
||||
def update(self, __m: MutableMapping[int, List[Type["Matcher"]]]) -> None:
|
||||
def update( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, __m: MutableMapping[int, list[type["Matcher"]]]
|
||||
) -> None:
|
||||
self.provider.update(__m)
|
||||
|
||||
def setdefault(
|
||||
self, key: int, default: List[Type["Matcher"]]
|
||||
) -> List[Type["Matcher"]]:
|
||||
self, key: int, default: list[type["Matcher"]]
|
||||
) -> list[type["Matcher"]]:
|
||||
return self.provider.setdefault(key, default)
|
||||
|
||||
def set_provider(self, provider_class: Type[MatcherProvider]) -> None:
|
||||
def set_provider(self, provider_class: type[MatcherProvider]) -> None:
|
||||
"""设置事件响应器存储器
|
||||
|
||||
参数:
|
||||
|
@@ -6,19 +6,17 @@ from types import ModuleType
|
||||
from dataclasses import dataclass
|
||||
from contextvars import ContextVar
|
||||
from typing_extensions import Self
|
||||
from collections.abc import Iterable
|
||||
from datetime import datetime, timedelta
|
||||
from contextlib import AsyncExitStack, contextmanager
|
||||
from typing import (
|
||||
from typing import ( # noqa: UP035
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
TypeVar,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Iterable,
|
||||
NoReturn,
|
||||
Optional,
|
||||
overload,
|
||||
@@ -78,15 +76,15 @@ T = TypeVar("T")
|
||||
current_bot: ContextVar[Bot] = ContextVar("current_bot")
|
||||
current_event: ContextVar[Event] = ContextVar("current_event")
|
||||
current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
|
||||
current_handler: ContextVar[Dependent] = ContextVar("current_handler")
|
||||
current_handler: ContextVar[Dependent[Any]] = ContextVar("current_handler")
|
||||
|
||||
|
||||
@dataclass
|
||||
class MatcherSource:
|
||||
"""Matcher 源代码上下文信息"""
|
||||
|
||||
plugin_name: Optional[str] = None
|
||||
"""事件响应器所在插件名称"""
|
||||
plugin_id: Optional[str] = None
|
||||
"""事件响应器所在插件标识符"""
|
||||
module_name: Optional[str] = None
|
||||
"""事件响应器所在插件模块的路径名"""
|
||||
lineno: Optional[int] = None
|
||||
@@ -97,8 +95,13 @@ class MatcherSource:
|
||||
"""事件响应器所在插件"""
|
||||
from nonebot.plugin import get_plugin
|
||||
|
||||
if self.plugin_name is not None:
|
||||
return get_plugin(self.plugin_name)
|
||||
if self.plugin_id is not None:
|
||||
return get_plugin(self.plugin_id)
|
||||
|
||||
@property
|
||||
def plugin_name(self) -> Optional[str]:
|
||||
"""事件响应器所在插件名"""
|
||||
return self.plugin and self.plugin.name
|
||||
|
||||
@property
|
||||
def module(self) -> Optional[ModuleType]:
|
||||
@@ -141,7 +144,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
"""事件响应器匹配规则"""
|
||||
permission: ClassVar[Permission] = Permission()
|
||||
"""事件响应器触发权限"""
|
||||
handlers: List[Dependent[Any]] = []
|
||||
handlers: ClassVar[list[Dependent[Any]]] = []
|
||||
"""事件响应器拥有的事件处理函数列表"""
|
||||
priority: ClassVar[int] = 1
|
||||
"""事件响应器优先级"""
|
||||
@@ -160,7 +163,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
_default_permission_updater: ClassVar[Optional[Dependent[Permission]]] = None
|
||||
"""事件响应器权限更新函数"""
|
||||
|
||||
HANDLER_PARAM_TYPES: ClassVar[Tuple[Type[Param], ...]] = (
|
||||
HANDLER_PARAM_TYPES: ClassVar[tuple[Type[Param], ...]] = ( # noqa: UP006
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
@@ -171,7 +174,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.handlers = self.handlers.copy()
|
||||
self.remain_handlers: list[Dependent[Any]] = self.handlers.copy()
|
||||
self.state = self._default_state.copy()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -192,7 +195,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
type_: str = "",
|
||||
rule: Optional[Rule] = None,
|
||||
permission: Optional[Permission] = None,
|
||||
handlers: Optional[List[Union[T_Handler, Dependent[Any]]]] = None,
|
||||
handlers: Optional[list[Union[T_Handler, Dependent[Any]]]] = None,
|
||||
temp: bool = False,
|
||||
priority: int = 1,
|
||||
block: bool = False,
|
||||
@@ -206,7 +209,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
default_permission_updater: Optional[
|
||||
Union[T_PermissionUpdater, Dependent[Permission]]
|
||||
] = None,
|
||||
) -> Type[Self]:
|
||||
) -> Type[Self]: # noqa: UP006
|
||||
"""
|
||||
创建一个新的事件响应器,并存储至 `matchers <#matchers>`_
|
||||
|
||||
@@ -247,7 +250,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
)
|
||||
source = source or (
|
||||
MatcherSource(
|
||||
plugin_name=plugin and plugin.name,
|
||||
plugin_id=plugin and plugin.id_,
|
||||
module_name=module and module.__name__,
|
||||
)
|
||||
if plugin is not None or module is not None
|
||||
@@ -330,15 +333,20 @@ class Matcher(metaclass=MatcherMeta):
|
||||
return cls._source and cls._source.plugin
|
||||
|
||||
@classproperty
|
||||
def module(cls) -> Optional[ModuleType]:
|
||||
"""事件响应器所在插件模块"""
|
||||
return cls._source and cls._source.module
|
||||
def plugin_id(cls) -> Optional[str]:
|
||||
"""事件响应器所在插件标识符"""
|
||||
return cls._source and cls._source.plugin_id
|
||||
|
||||
@classproperty
|
||||
def plugin_name(cls) -> Optional[str]:
|
||||
"""事件响应器所在插件名"""
|
||||
return cls._source and cls._source.plugin_name
|
||||
|
||||
@classproperty
|
||||
def module(cls) -> Optional[ModuleType]:
|
||||
"""事件响应器所在插件模块"""
|
||||
return cls._source and cls._source.module
|
||||
|
||||
@classproperty
|
||||
def module_name(cls) -> Optional[str]:
|
||||
"""事件响应器所在插件模块路径"""
|
||||
@@ -457,7 +465,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
parameterless: 非参数类型依赖列表
|
||||
"""
|
||||
|
||||
async def _receive(event: Event, matcher: "Matcher") -> Union[None, NoReturn]:
|
||||
async def _receive(event: Event, matcher: "Matcher") -> None:
|
||||
matcher.set_target(RECEIVE_KEY.format(id=id))
|
||||
if matcher.get_target() == RECEIVE_KEY.format(id=id):
|
||||
matcher.set_receive(id, event)
|
||||
@@ -775,7 +783,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
|
||||
async def resolve_reject(self):
|
||||
handler = current_handler.get()
|
||||
self.handlers.insert(0, handler)
|
||||
self.remain_handlers.insert(0, handler)
|
||||
if REJECT_CACHE_TARGET in self.state:
|
||||
self.state[REJECT_TARGET] = self.state[REJECT_CACHE_TARGET]
|
||||
|
||||
@@ -809,8 +817,8 @@ class Matcher(metaclass=MatcherMeta):
|
||||
# Refresh preprocess state
|
||||
self.state.update(state)
|
||||
|
||||
while self.handlers:
|
||||
handler = self.handlers.pop(0)
|
||||
while self.remain_handlers:
|
||||
handler = self.remain_handlers.pop(0)
|
||||
current_handler.set(handler)
|
||||
logger.debug(f"Running handler {handler}")
|
||||
try:
|
||||
@@ -852,7 +860,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
type_,
|
||||
Rule(),
|
||||
permission,
|
||||
self.handlers,
|
||||
self.remain_handlers,
|
||||
temp=True,
|
||||
priority=0,
|
||||
block=True,
|
||||
@@ -872,7 +880,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
type_,
|
||||
Rule(),
|
||||
permission,
|
||||
self.handlers,
|
||||
self.remain_handlers,
|
||||
temp=True,
|
||||
priority=0,
|
||||
block=True,
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import abc
|
||||
from typing import TYPE_CHECKING
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, List, Type, Mapping, MutableMapping
|
||||
from collections.abc import Mapping, MutableMapping
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .matcher import Matcher
|
||||
|
||||
|
||||
class MatcherProvider(abc.ABC, MutableMapping[int, List[Type["Matcher"]]]):
|
||||
class MatcherProvider(abc.ABC, MutableMapping[int, list[type["Matcher"]]]):
|
||||
"""事件响应器存储器基类
|
||||
|
||||
参数:
|
||||
@@ -14,12 +15,12 @@ class MatcherProvider(abc.ABC, MutableMapping[int, List[Type["Matcher"]]]):
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __init__(self, matchers: Mapping[int, List[Type["Matcher"]]]):
|
||||
def __init__(self, matchers: Mapping[int, list[type["Matcher"]]]):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class _DictProvider(defaultdict, MatcherProvider):
|
||||
def __init__(self, matchers: Mapping[int, List[Type["Matcher"]]]):
|
||||
def __init__(self, matchers: Mapping[int, list[type["Matcher"]]]):
|
||||
super().__init__(list, matchers)
|
||||
|
||||
|
||||
|
@@ -1,16 +1,15 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
from typing_extensions import Self, get_args, override, get_origin
|
||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
||||
from typing_extensions import Self, Annotated, get_args, override, get_origin
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Literal,
|
||||
Callable,
|
||||
Optional,
|
||||
Annotated,
|
||||
cast,
|
||||
)
|
||||
|
||||
@@ -18,8 +17,14 @@ from pydantic.fields import FieldInfo as PydanticFieldInfo
|
||||
|
||||
from nonebot.dependencies import Param, Dependent
|
||||
from nonebot.dependencies.utils import check_field_type
|
||||
from nonebot.typing import T_State, T_Handler, T_DependencyCache
|
||||
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined, extract_field_info
|
||||
from nonebot.typing import (
|
||||
_STATE_FLAG,
|
||||
T_State,
|
||||
T_Handler,
|
||||
T_DependencyCache,
|
||||
origin_is_annotated,
|
||||
)
|
||||
from nonebot.utils import (
|
||||
get_name,
|
||||
run_sync,
|
||||
@@ -97,7 +102,7 @@ class DependParam(Param):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, *args, dependent: Dependent, use_cache: bool, **kwargs: Any
|
||||
self, *args, dependent: Dependent[Any], use_cache: bool, **kwargs: Any
|
||||
) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.dependent = dependent
|
||||
@@ -109,7 +114,7 @@ class DependParam(Param):
|
||||
@classmethod
|
||||
def _from_field(
|
||||
cls,
|
||||
sub_dependent: Dependent,
|
||||
sub_dependent: Dependent[Any],
|
||||
use_cache: bool,
|
||||
validate: Union[bool, PydanticFieldInfo],
|
||||
) -> Self:
|
||||
@@ -126,7 +131,7 @@ class DependParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
type_annotation, depends_inner = param.annotation, None
|
||||
# extract type annotation and dependency from Annotated
|
||||
@@ -166,7 +171,7 @@ class DependParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_parameterless(
|
||||
cls, value: Any, allow_types: Tuple[Type[Param], ...]
|
||||
cls, value: Any, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional["Param"]:
|
||||
if isinstance(value, DependsInner):
|
||||
assert value.dependency, "Dependency cannot be empty"
|
||||
@@ -185,7 +190,7 @@ class DependParam(Param):
|
||||
use_cache: bool = self.use_cache
|
||||
dependency_cache = {} if dependency_cache is None else dependency_cache
|
||||
|
||||
sub_dependent: Dependent = self.dependent
|
||||
sub_dependent = self.dependent
|
||||
call = cast(Callable[..., Any], sub_dependent.call)
|
||||
|
||||
# solve sub dependency with current cache
|
||||
@@ -249,7 +254,7 @@ class BotParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
from nonebot.adapters import Bot
|
||||
|
||||
@@ -266,11 +271,15 @@ class BotParam(Param):
|
||||
return cls()
|
||||
|
||||
@override
|
||||
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
||||
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, bot: "Bot", **kwargs: Any
|
||||
) -> Any:
|
||||
return bot
|
||||
|
||||
@override
|
||||
async def _check(self, bot: "Bot", **kwargs: Any) -> None:
|
||||
async def _check( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, bot: "Bot", **kwargs: Any
|
||||
) -> None:
|
||||
if self.checker is not None:
|
||||
check_field_type(self.checker, bot)
|
||||
|
||||
@@ -299,7 +308,7 @@ class EventParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
from nonebot.adapters import Event
|
||||
|
||||
@@ -316,11 +325,15 @@ class EventParam(Param):
|
||||
return cls()
|
||||
|
||||
@override
|
||||
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
||||
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, event: "Event", **kwargs: Any
|
||||
) -> Any:
|
||||
return event
|
||||
|
||||
@override
|
||||
async def _check(self, event: "Event", **kwargs: Any) -> Any:
|
||||
async def _check( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, event: "Event", **kwargs: Any
|
||||
) -> Any:
|
||||
if self.checker is not None:
|
||||
check_field_type(self.checker, event)
|
||||
|
||||
@@ -339,17 +352,21 @@ class StateParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
# param type is T_State
|
||||
if param.annotation is T_State:
|
||||
if origin_is_annotated(
|
||||
get_origin(param.annotation)
|
||||
) and _STATE_FLAG in get_args(param.annotation):
|
||||
return cls()
|
||||
# legacy: param is named "state" and has no type annotation
|
||||
elif param.annotation == param.empty and param.name == "state":
|
||||
return cls()
|
||||
|
||||
@override
|
||||
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
||||
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, state: T_State, **kwargs: Any
|
||||
) -> Any:
|
||||
return state
|
||||
|
||||
|
||||
@@ -377,7 +394,7 @@ class MatcherParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
@@ -394,11 +411,15 @@ class MatcherParam(Param):
|
||||
return cls()
|
||||
|
||||
@override
|
||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, matcher: "Matcher", **kwargs: Any
|
||||
) -> Any:
|
||||
return matcher
|
||||
|
||||
@override
|
||||
async def _check(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
async def _check( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, matcher: "Matcher", **kwargs: Any
|
||||
) -> Any:
|
||||
if self.checker is not None:
|
||||
check_field_type(self.checker, matcher)
|
||||
|
||||
@@ -455,7 +476,7 @@ class ArgParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
if isinstance(param.default, ArgInner):
|
||||
return cls(key=param.default.key or param.name, type=param.default.type)
|
||||
@@ -464,7 +485,9 @@ class ArgParam(Param):
|
||||
if isinstance(arg, ArgInner):
|
||||
return cls(key=arg.key or param.name, type=arg.type)
|
||||
|
||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self, matcher: "Matcher", **kwargs: Any
|
||||
) -> Any:
|
||||
message = matcher.get_arg(self.key)
|
||||
if message is None:
|
||||
return message
|
||||
@@ -490,7 +513,7 @@ class ExceptionParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
# param type is Exception(s) or subclass(es) of Exception or None
|
||||
if generic_check_issubclass(param.annotation, Exception):
|
||||
@@ -518,7 +541,7 @@ class DefaultParam(Param):
|
||||
@classmethod
|
||||
@override
|
||||
def _check_param(
|
||||
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
|
||||
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
||||
) -> Optional[Self]:
|
||||
if param.default != param.empty:
|
||||
return cls(default=param.default)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
from typing_extensions import Self
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import Set, Tuple, Union, NoReturn, Optional
|
||||
from typing import Union, ClassVar, NoReturn, Optional
|
||||
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.utils import run_coro_with_catch
|
||||
@@ -9,7 +9,7 @@ from nonebot.exception import SkippedException
|
||||
from nonebot.typing import T_DependencyCache, T_PermissionChecker
|
||||
|
||||
from .adapter import Bot, Event
|
||||
from .params import BotParam, EventParam, DependParam, DefaultParam
|
||||
from .params import Param, BotParam, EventParam, DependParam, DefaultParam
|
||||
|
||||
|
||||
class Permission:
|
||||
@@ -30,7 +30,7 @@ class Permission:
|
||||
|
||||
__slots__ = ("checkers",)
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
HANDLER_PARAM_TYPES: ClassVar[list[type[Param]]] = [
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
@@ -38,7 +38,7 @@ class Permission:
|
||||
]
|
||||
|
||||
def __init__(self, *checkers: Union[T_PermissionChecker, Dependent[bool]]) -> None:
|
||||
self.checkers: Set[Dependent[bool]] = {
|
||||
self.checkers: set[Dependent[bool]] = {
|
||||
(
|
||||
checker
|
||||
if isinstance(checker, Dependent)
|
||||
@@ -122,7 +122,7 @@ class User:
|
||||
__slots__ = ("users", "perm")
|
||||
|
||||
def __init__(
|
||||
self, users: Tuple[str, ...], perm: Optional[Permission] = None
|
||||
self, users: tuple[str, ...], perm: Optional[Permission] = None
|
||||
) -> None:
|
||||
self.users = users
|
||||
self.perm = perm
|
||||
@@ -146,7 +146,7 @@ class User:
|
||||
@classmethod
|
||||
def _clean_permission(cls, perm: Permission) -> Optional[Permission]:
|
||||
if len(perm.checkers) == 1 and isinstance(
|
||||
user_perm := tuple(perm.checkers)[0].call, cls
|
||||
user_perm := next(iter(perm.checkers)).call, cls
|
||||
):
|
||||
return user_perm.perm
|
||||
return perm
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import asyncio
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import Set, Union, NoReturn, Optional
|
||||
from typing import Union, ClassVar, NoReturn, Optional
|
||||
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.exception import SkippedException
|
||||
from nonebot.typing import T_State, T_RuleChecker, T_DependencyCache
|
||||
|
||||
from .adapter import Bot, Event
|
||||
from .params import BotParam, EventParam, StateParam, DependParam, DefaultParam
|
||||
from .params import Param, BotParam, EventParam, StateParam, DependParam, DefaultParam
|
||||
|
||||
|
||||
class Rule:
|
||||
@@ -28,7 +28,7 @@ class Rule:
|
||||
|
||||
__slots__ = ("checkers",)
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
HANDLER_PARAM_TYPES: ClassVar[list[type[Param]]] = [
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
@@ -37,7 +37,7 @@ class Rule:
|
||||
]
|
||||
|
||||
def __init__(self, *checkers: Union[T_RuleChecker, Dependent[bool]]) -> None:
|
||||
self.checkers: Set[Dependent[bool]] = {
|
||||
self.checkers: set[Dependent[bool]] = {
|
||||
(
|
||||
checker
|
||||
if isinstance(checker, Dependent)
|
||||
|
@@ -13,6 +13,7 @@ FrontMatter:
|
||||
"""
|
||||
|
||||
import sys
|
||||
import inspect
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -45,6 +46,7 @@ logger: "Logger" = loguru.logger
|
||||
# logger.addHandler(default_handler)
|
||||
|
||||
|
||||
# https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
|
||||
class LoguruHandler(logging.Handler): # pragma: no cover
|
||||
"""logging 与 loguru 之间的桥梁,将 logging 的日志转发到 loguru。"""
|
||||
|
||||
@@ -54,8 +56,8 @@ class LoguruHandler(logging.Handler): # pragma: no cover
|
||||
except ValueError:
|
||||
level = record.levelno
|
||||
|
||||
frame, depth = sys._getframe(6), 6
|
||||
while frame and frame.f_code.co_filename == logging.__file__:
|
||||
frame, depth = inspect.currentframe(), 0
|
||||
while frame and (depth == 0 or frame.f_code.co_filename == logging.__file__):
|
||||
frame = frame.f_back
|
||||
depth += 1
|
||||
|
||||
|
@@ -11,7 +11,7 @@ import asyncio
|
||||
import contextlib
|
||||
from datetime import datetime
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.rule import TrieRule
|
||||
@@ -46,10 +46,10 @@ from nonebot.internal.params import (
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.adapters import Bot, Event
|
||||
|
||||
_event_preprocessors: Set[Dependent[Any]] = set()
|
||||
_event_postprocessors: Set[Dependent[Any]] = set()
|
||||
_run_preprocessors: Set[Dependent[Any]] = set()
|
||||
_run_postprocessors: Set[Dependent[Any]] = set()
|
||||
_event_preprocessors: set[Dependent[Any]] = set()
|
||||
_event_postprocessors: set[Dependent[Any]] = set()
|
||||
_run_preprocessors: set[Dependent[Any]] = set()
|
||||
_run_postprocessors: set[Dependent[Any]] = set()
|
||||
|
||||
EVENT_PCS_PARAMS = (
|
||||
DependParam,
|
||||
@@ -330,7 +330,7 @@ async def _apply_run_postprocessors(
|
||||
|
||||
|
||||
async def _check_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
Matcher: type[Matcher],
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
@@ -381,7 +381,7 @@ async def _check_matcher(
|
||||
|
||||
|
||||
async def _run_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
Matcher: type[Matcher],
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
@@ -446,7 +446,7 @@ async def _run_matcher(
|
||||
|
||||
|
||||
async def check_and_run_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
Matcher: type[Matcher],
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
@@ -505,7 +505,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
if show_log:
|
||||
logger.opt(colors=True).success(log_msg)
|
||||
|
||||
state: Dict[Any, Any] = {}
|
||||
state: dict[Any, Any] = {}
|
||||
dependency_cache: T_DependencyCache = {}
|
||||
|
||||
# create event scope context
|
||||
|
@@ -5,18 +5,8 @@ FrontMatter:
|
||||
description: nonebot.params 模块
|
||||
"""
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Match,
|
||||
Tuple,
|
||||
Union,
|
||||
Literal,
|
||||
Callable,
|
||||
Optional,
|
||||
overload,
|
||||
)
|
||||
from re import Match
|
||||
from typing import Any, Union, Literal, Callable, Optional, overload
|
||||
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.matcher import Matcher
|
||||
@@ -90,7 +80,7 @@ def _command(state: T_State) -> Message:
|
||||
return state[PREFIX_KEY][CMD_KEY]
|
||||
|
||||
|
||||
def Command() -> Tuple[str, ...]:
|
||||
def Command() -> tuple[str, ...]:
|
||||
"""消息命令元组"""
|
||||
return Depends(_command)
|
||||
|
||||
@@ -140,7 +130,7 @@ def ShellCommandArgs() -> Any:
|
||||
return Depends(_shell_command_args, use_cache=False)
|
||||
|
||||
|
||||
def _shell_command_argv(state: T_State) -> List[Union[str, MessageSegment]]:
|
||||
def _shell_command_argv(state: T_State) -> list[Union[str, MessageSegment]]:
|
||||
return state[SHELL_ARGV]
|
||||
|
||||
|
||||
@@ -159,11 +149,11 @@ def RegexMatched() -> Match[str]:
|
||||
|
||||
|
||||
def _regex_str(
|
||||
groups: Tuple[Union[str, int], ...]
|
||||
) -> Callable[[T_State], Union[str, Tuple[Union[str, Any], ...], Any]]:
|
||||
groups: tuple[Union[str, int], ...]
|
||||
) -> Callable[[T_State], Union[str, tuple[Union[str, Any], ...], Any]]:
|
||||
def _regex_str_dependency(
|
||||
state: T_State,
|
||||
) -> Union[str, Tuple[Union[str, Any], ...], Any]:
|
||||
) -> Union[str, tuple[Union[str, Any], ...], Any]:
|
||||
return _regex_matched(state).group(*groups)
|
||||
|
||||
return _regex_str_dependency
|
||||
@@ -180,28 +170,28 @@ def RegexStr(__group: Union[str, int]) -> Union[str, Any]: ...
|
||||
@overload
|
||||
def RegexStr(
|
||||
__group1: Union[str, int], __group2: Union[str, int], *groups: Union[str, int]
|
||||
) -> Tuple[Union[str, Any], ...]: ...
|
||||
) -> tuple[Union[str, Any], ...]: ...
|
||||
|
||||
|
||||
def RegexStr(*groups: Union[str, int]) -> Union[str, Tuple[Union[str, Any], ...], Any]:
|
||||
def RegexStr(*groups: Union[str, int]) -> Union[str, tuple[Union[str, Any], ...], Any]:
|
||||
"""正则匹配结果文本"""
|
||||
return Depends(_regex_str(groups), use_cache=False)
|
||||
|
||||
|
||||
def _regex_group(state: T_State) -> Tuple[Any, ...]:
|
||||
def _regex_group(state: T_State) -> tuple[Any, ...]:
|
||||
return _regex_matched(state).groups()
|
||||
|
||||
|
||||
def RegexGroup() -> Tuple[Any, ...]:
|
||||
def RegexGroup() -> tuple[Any, ...]:
|
||||
"""正则匹配结果 group 元组"""
|
||||
return Depends(_regex_group, use_cache=False)
|
||||
|
||||
|
||||
def _regex_dict(state: T_State) -> Dict[str, Any]:
|
||||
def _regex_dict(state: T_State) -> dict[str, Any]:
|
||||
return _regex_matched(state).groupdict()
|
||||
|
||||
|
||||
def RegexDict() -> Dict[str, Any]:
|
||||
def RegexDict() -> dict[str, Any]:
|
||||
"""正则匹配结果 group 字典"""
|
||||
return Depends(_regex_dict, use_cache=False)
|
||||
|
||||
|
@@ -39,7 +39,7 @@ FrontMatter:
|
||||
from itertools import chain
|
||||
from types import ModuleType
|
||||
from contextvars import ContextVar
|
||||
from typing import Set, Dict, List, Type, Tuple, TypeVar, Optional
|
||||
from typing import TypeVar, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -48,10 +48,10 @@ from nonebot.compat import model_dump, type_validate_python
|
||||
|
||||
C = TypeVar("C", bound=BaseModel)
|
||||
|
||||
_plugins: Dict[str, "Plugin"] = {}
|
||||
_managers: List["PluginManager"] = []
|
||||
_current_plugin_chain: ContextVar[Tuple["Plugin", ...]] = ContextVar(
|
||||
"_current_plugin_chain", default=()
|
||||
_plugins: dict[str, "Plugin"] = {}
|
||||
_managers: list["PluginManager"] = []
|
||||
_current_plugin: ContextVar[Optional["Plugin"]] = ContextVar(
|
||||
"_current_plugin", default=None
|
||||
)
|
||||
|
||||
|
||||
@@ -59,34 +59,87 @@ def _module_name_to_plugin_name(module_name: str) -> str:
|
||||
return module_name.rsplit(".", 1)[-1]
|
||||
|
||||
|
||||
def _controlled_modules() -> dict[str, str]:
|
||||
return {
|
||||
plugin_id: module_name
|
||||
for manager in _managers
|
||||
for plugin_id, module_name in manager.controlled_modules.items()
|
||||
}
|
||||
|
||||
|
||||
def _find_parent_plugin_id(
|
||||
module_name: str, controlled_modules: Optional[dict[str, str]] = None
|
||||
) -> Optional[str]:
|
||||
if controlled_modules is None:
|
||||
controlled_modules = _controlled_modules()
|
||||
available = {
|
||||
module_name: plugin_id for plugin_id, module_name in controlled_modules.items()
|
||||
}
|
||||
while "." in module_name:
|
||||
module_name, _ = module_name.rsplit(".", 1)
|
||||
if module_name in available:
|
||||
return available[module_name]
|
||||
|
||||
|
||||
def _module_name_to_plugin_id(
|
||||
module_name: str, controlled_modules: Optional[dict[str, str]] = None
|
||||
) -> str:
|
||||
plugin_name = _module_name_to_plugin_name(module_name)
|
||||
if parent_plugin_id := _find_parent_plugin_id(module_name, controlled_modules):
|
||||
return f"{parent_plugin_id}:{plugin_name}"
|
||||
return plugin_name
|
||||
|
||||
|
||||
def _new_plugin(
|
||||
module_name: str, module: ModuleType, manager: "PluginManager"
|
||||
) -> "Plugin":
|
||||
plugin_name = _module_name_to_plugin_name(module_name)
|
||||
if plugin_name in _plugins:
|
||||
raise RuntimeError("Plugin already exists! Check your plugin name.")
|
||||
plugin = Plugin(plugin_name, module, module_name, manager)
|
||||
_plugins[plugin_name] = plugin
|
||||
plugin_id = _module_name_to_plugin_id(module_name)
|
||||
if plugin_id in _plugins:
|
||||
raise RuntimeError(
|
||||
f"Plugin {plugin_id} already exists! Check your plugin name."
|
||||
)
|
||||
|
||||
parent_plugin_id = _find_parent_plugin_id(module_name)
|
||||
if parent_plugin_id is not None and parent_plugin_id not in _plugins:
|
||||
raise RuntimeError(
|
||||
f"Parent plugin {parent_plugin_id} must "
|
||||
f"be loaded before loading {plugin_id}."
|
||||
)
|
||||
parent_plugin = _plugins[parent_plugin_id] if parent_plugin_id is not None else None
|
||||
|
||||
plugin = Plugin(
|
||||
name=_module_name_to_plugin_name(module_name),
|
||||
module=module,
|
||||
module_name=module_name,
|
||||
manager=manager,
|
||||
parent_plugin=parent_plugin,
|
||||
)
|
||||
if parent_plugin:
|
||||
parent_plugin.sub_plugins.add(plugin)
|
||||
|
||||
_plugins[plugin_id] = plugin
|
||||
return plugin
|
||||
|
||||
|
||||
def _revert_plugin(plugin: "Plugin") -> None:
|
||||
if plugin.name not in _plugins:
|
||||
if plugin.id_ not in _plugins:
|
||||
raise RuntimeError("Plugin not found!")
|
||||
del _plugins[plugin.name]
|
||||
del _plugins[plugin.id_]
|
||||
if parent_plugin := plugin.parent_plugin:
|
||||
parent_plugin.sub_plugins.remove(plugin)
|
||||
parent_plugin.sub_plugins.discard(plugin)
|
||||
|
||||
|
||||
def get_plugin(name: str) -> Optional["Plugin"]:
|
||||
def get_plugin(plugin_id: str) -> Optional["Plugin"]:
|
||||
"""获取已经导入的某个插件。
|
||||
|
||||
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
||||
|
||||
如果为嵌套的子插件,标识符为 `父插件标识符:子插件文件(夹)名`。
|
||||
|
||||
参数:
|
||||
name: 插件名,即 {ref}`nonebot.plugin.model.Plugin.name`。
|
||||
plugin_id: 插件标识符,即 {ref}`nonebot.plugin.model.Plugin.id_`。
|
||||
"""
|
||||
return _plugins.get(name)
|
||||
return _plugins.get(plugin_id)
|
||||
|
||||
|
||||
def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]:
|
||||
@@ -105,17 +158,17 @@ def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]:
|
||||
module_name, *has_parent = module_name.rsplit(".", 1)
|
||||
|
||||
|
||||
def get_loaded_plugins() -> Set["Plugin"]:
|
||||
def get_loaded_plugins() -> set["Plugin"]:
|
||||
"""获取当前已导入的所有插件。"""
|
||||
return set(_plugins.values())
|
||||
|
||||
|
||||
def get_available_plugin_names() -> Set[str]:
|
||||
"""获取当前所有可用的插件名(包含尚未加载的插件)。"""
|
||||
def get_available_plugin_names() -> set[str]:
|
||||
"""获取当前所有可用的插件标识符(包含尚未加载的插件)。"""
|
||||
return {*chain.from_iterable(manager.available_plugins for manager in _managers)}
|
||||
|
||||
|
||||
def get_plugin_config(config: Type[C]) -> C:
|
||||
def get_plugin_config(config: type[C]) -> C:
|
||||
"""从全局配置获取当前插件需要的配置项。"""
|
||||
return type_validate_python(config, model_dump(get_driver().config))
|
||||
|
||||
|
@@ -8,13 +8,14 @@ FrontMatter:
|
||||
import json
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Set, Union, Iterable, Optional
|
||||
from typing import Union, Optional
|
||||
from collections.abc import Iterable
|
||||
|
||||
from nonebot.utils import path_to_module_name
|
||||
|
||||
from .model import Plugin
|
||||
from .manager import PluginManager
|
||||
from . import _managers, get_plugin, _current_plugin_chain, _module_name_to_plugin_name
|
||||
from . import _managers, get_plugin, _module_name_to_plugin_id
|
||||
|
||||
try: # pragma: py-gte-311
|
||||
import tomllib # pyright: ignore[reportMissingImports]
|
||||
@@ -39,7 +40,7 @@ def load_plugin(module_path: Union[str, Path]) -> Optional[Plugin]:
|
||||
return manager.load_plugin(module_path)
|
||||
|
||||
|
||||
def load_plugins(*plugin_dir: str) -> Set[Plugin]:
|
||||
def load_plugins(*plugin_dir: str) -> set[Plugin]:
|
||||
"""导入文件夹下多个插件,以 `_` 开头的插件不会被导入!
|
||||
|
||||
参数:
|
||||
@@ -52,7 +53,7 @@ def load_plugins(*plugin_dir: str) -> Set[Plugin]:
|
||||
|
||||
def load_all_plugins(
|
||||
module_path: Iterable[str], plugin_dir: Iterable[str]
|
||||
) -> Set[Plugin]:
|
||||
) -> set[Plugin]:
|
||||
"""导入指定列表中的插件以及指定目录下多个插件,以 `_` 开头的插件不会被导入!
|
||||
|
||||
参数:
|
||||
@@ -64,7 +65,7 @@ def load_all_plugins(
|
||||
return manager.load_all_plugins()
|
||||
|
||||
|
||||
def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
def load_from_json(file_path: str, encoding: str = "utf-8") -> set[Plugin]:
|
||||
"""导入指定 json 文件中的 `plugins` 以及 `plugin_dirs` 下多个插件。
|
||||
以 `_` 开头的插件不会被导入!
|
||||
|
||||
@@ -95,7 +96,7 @@ def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
return load_all_plugins(set(plugins), set(plugin_dirs))
|
||||
|
||||
|
||||
def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
def load_from_toml(file_path: str, encoding: str = "utf-8") -> set[Plugin]:
|
||||
"""导入指定 toml 文件 `[tool.nonebot]` 中的
|
||||
`plugins` 以及 `plugin_dirs` 下多个插件。
|
||||
以 `_` 开头的插件不会被导入!
|
||||
@@ -139,7 +140,7 @@ def load_builtin_plugin(name: str) -> Optional[Plugin]:
|
||||
return load_plugin(f"nonebot.plugins.{name}")
|
||||
|
||||
|
||||
def load_builtin_plugins(*plugins: str) -> Set[Plugin]:
|
||||
def load_builtin_plugins(*plugins: str) -> set[Plugin]:
|
||||
"""导入多个 NoneBot 内置插件。
|
||||
|
||||
参数:
|
||||
@@ -150,41 +151,45 @@ def load_builtin_plugins(*plugins: str) -> Set[Plugin]:
|
||||
|
||||
def _find_manager_by_name(name: str) -> Optional[PluginManager]:
|
||||
for manager in reversed(_managers):
|
||||
if name in manager.plugins or name in manager.searched_plugins:
|
||||
if (
|
||||
name in manager.controlled_modules
|
||||
or name in manager.controlled_modules.values()
|
||||
):
|
||||
return manager
|
||||
|
||||
|
||||
def require(name: str) -> ModuleType:
|
||||
"""获取一个插件的导出内容。
|
||||
|
||||
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
||||
"""声明依赖插件。
|
||||
|
||||
参数:
|
||||
name: 插件名,即 {ref}`nonebot.plugin.model.Plugin.name`。
|
||||
name: 插件模块名或插件标识符,仅在已声明插件的情况下可使用标识符。
|
||||
|
||||
异常:
|
||||
RuntimeError: 插件无法加载
|
||||
"""
|
||||
plugin = get_plugin(_module_name_to_plugin_name(name))
|
||||
if "." in name:
|
||||
# name is a module name
|
||||
plugin = get_plugin(_module_name_to_plugin_id(name))
|
||||
else:
|
||||
# name is a plugin id or simple module name (equals to plugin id)
|
||||
plugin = get_plugin(name)
|
||||
|
||||
# if plugin not loaded
|
||||
if not plugin:
|
||||
# plugin already declared
|
||||
if plugin is None:
|
||||
# plugin already declared, module name / plugin id
|
||||
if manager := _find_manager_by_name(name):
|
||||
plugin = manager.load_plugin(name)
|
||||
|
||||
# plugin not declared, try to declare and load it
|
||||
else:
|
||||
# clear current plugin chain, ensure plugin loaded in a new context
|
||||
_t = _current_plugin_chain.set(())
|
||||
try:
|
||||
plugin = load_plugin(name)
|
||||
finally:
|
||||
_current_plugin_chain.reset(_t)
|
||||
if not plugin:
|
||||
plugin = load_plugin(name)
|
||||
|
||||
if plugin is None:
|
||||
raise RuntimeError(f'Cannot load plugin "{name}"!')
|
||||
return plugin.module
|
||||
|
||||
|
||||
def inherit_supported_adapters(*names: str) -> Optional[Set[str]]:
|
||||
def inherit_supported_adapters(*names: str) -> Optional[set[str]]:
|
||||
"""获取已加载插件的适配器支持状态集合。
|
||||
|
||||
如果传入了多个插件名称,返回值会自动取交集。
|
||||
@@ -196,27 +201,28 @@ def inherit_supported_adapters(*names: str) -> Optional[Set[str]]:
|
||||
RuntimeError: 插件未加载
|
||||
ValueError: 插件缺少元数据
|
||||
"""
|
||||
final_supported: Optional[Set[str]] = None
|
||||
final_supported: Optional[set[str]] = None
|
||||
|
||||
for name in names:
|
||||
plugin = get_plugin(_module_name_to_plugin_name(name))
|
||||
plugin = get_plugin(_module_name_to_plugin_id(name))
|
||||
if plugin is None:
|
||||
raise RuntimeError(f'Plugin "{name}" is not loaded!')
|
||||
raise RuntimeError(
|
||||
f'Plugin "{name}" is not loaded! You should require it first.'
|
||||
)
|
||||
meta = plugin.metadata
|
||||
if meta is None:
|
||||
raise ValueError(f'Plugin "{name}" has no metadata!')
|
||||
support = meta.supported_adapters
|
||||
if support is None:
|
||||
|
||||
if (raw := meta.supported_adapters) is None:
|
||||
continue
|
||||
|
||||
support = {
|
||||
f"nonebot.adapters.{adapter[1:]}" if adapter.startswith("~") else adapter
|
||||
for adapter in raw
|
||||
}
|
||||
|
||||
final_supported = (
|
||||
support if final_supported is None else (final_supported & support)
|
||||
)
|
||||
|
||||
return final_supported and {
|
||||
(
|
||||
f"nonebot.adapters.{adapter_name[1:]}"
|
||||
if adapter_name.startswith("~")
|
||||
else adapter_name
|
||||
)
|
||||
for adapter_name in final_supported
|
||||
}
|
||||
return final_supported
|
||||
|
@@ -12,10 +12,11 @@ import pkgutil
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from itertools import chain
|
||||
from typing import Optional
|
||||
from types import ModuleType
|
||||
from importlib.abc import MetaPathFinder
|
||||
from collections.abc import Iterable, Sequence
|
||||
from importlib.machinery import PathFinder, SourceFileLoader
|
||||
from typing import Set, Dict, List, Iterable, Optional, Sequence
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.utils import escape_tag, path_to_module_name
|
||||
@@ -25,8 +26,8 @@ from . import (
|
||||
_managers,
|
||||
_new_plugin,
|
||||
_revert_plugin,
|
||||
_current_plugin_chain,
|
||||
_module_name_to_plugin_name,
|
||||
_current_plugin,
|
||||
_module_name_to_plugin_id,
|
||||
)
|
||||
|
||||
|
||||
@@ -35,7 +36,7 @@ class PluginManager:
|
||||
|
||||
参数:
|
||||
plugins: 独立插件模块名集合。
|
||||
search_path: 插件搜索路径(文件夹)。
|
||||
search_path: 插件搜索路径(文件夹),相对于当前工作目录。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -44,60 +45,78 @@ class PluginManager:
|
||||
search_path: Optional[Iterable[str]] = None,
|
||||
):
|
||||
# simple plugin not in search path
|
||||
self.plugins: Set[str] = set(plugins or [])
|
||||
self.search_path: Set[str] = set(search_path or [])
|
||||
self.plugins: set[str] = set(plugins or [])
|
||||
self.search_path: set[str] = set(search_path or [])
|
||||
|
||||
# cache plugins
|
||||
self._third_party_plugin_names: Dict[str, str] = {}
|
||||
self._searched_plugin_names: Dict[str, Path] = {}
|
||||
self.prepare_plugins()
|
||||
self._third_party_plugin_ids: dict[str, str] = {}
|
||||
self._searched_plugin_ids: dict[str, str] = {}
|
||||
self._prepare_plugins()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"PluginManager(plugins={self.plugins}, search_path={self.search_path})"
|
||||
return f"PluginManager(available_plugins={self.controlled_modules})"
|
||||
|
||||
@property
|
||||
def third_party_plugins(self) -> Set[str]:
|
||||
"""返回所有独立插件名称。"""
|
||||
return set(self._third_party_plugin_names.keys())
|
||||
def third_party_plugins(self) -> set[str]:
|
||||
"""返回所有独立插件标识符。"""
|
||||
return set(self._third_party_plugin_ids.keys())
|
||||
|
||||
@property
|
||||
def searched_plugins(self) -> Set[str]:
|
||||
"""返回已搜索到的插件名称。"""
|
||||
return set(self._searched_plugin_names.keys())
|
||||
def searched_plugins(self) -> set[str]:
|
||||
"""返回已搜索到的插件标识符。"""
|
||||
return set(self._searched_plugin_ids.keys())
|
||||
|
||||
@property
|
||||
def available_plugins(self) -> Set[str]:
|
||||
"""返回当前插件管理器中可用的插件名称。"""
|
||||
def available_plugins(self) -> set[str]:
|
||||
"""返回当前插件管理器中可用的插件标识符。"""
|
||||
return self.third_party_plugins | self.searched_plugins
|
||||
|
||||
def _previous_plugins(self) -> Set[str]:
|
||||
_pre_managers: List[PluginManager]
|
||||
@property
|
||||
def controlled_modules(self) -> dict[str, str]:
|
||||
"""返回当前插件管理器中控制的插件标识符与模块路径映射字典。"""
|
||||
return dict(
|
||||
chain(
|
||||
self._third_party_plugin_ids.items(), self._searched_plugin_ids.items()
|
||||
)
|
||||
)
|
||||
|
||||
def _previous_controlled_modules(self) -> dict[str, str]:
|
||||
_pre_managers: list[PluginManager]
|
||||
if self in _managers:
|
||||
_pre_managers = _managers[: _managers.index(self)]
|
||||
else:
|
||||
_pre_managers = _managers[:]
|
||||
|
||||
return {
|
||||
*chain.from_iterable(manager.available_plugins for manager in _pre_managers)
|
||||
plugin_id: module_name
|
||||
for manager in _pre_managers
|
||||
for plugin_id, module_name in manager.controlled_modules.items()
|
||||
}
|
||||
|
||||
def prepare_plugins(self) -> Set[str]:
|
||||
def _prepare_plugins(self) -> set[str]:
|
||||
"""搜索插件并缓存插件名称。"""
|
||||
# get all previous ready to load plugins
|
||||
previous_plugins = self._previous_plugins()
|
||||
searched_plugins: Dict[str, Path] = {}
|
||||
third_party_plugins: Dict[str, str] = {}
|
||||
previous_plugin_ids = self._previous_controlled_modules()
|
||||
|
||||
# if self not in global managers, merge self's controlled modules
|
||||
def get_controlled_modules():
|
||||
return (
|
||||
previous_plugin_ids
|
||||
if self in _managers
|
||||
else {**previous_plugin_ids, **self.controlled_modules}
|
||||
)
|
||||
|
||||
# check third party plugins
|
||||
for plugin in self.plugins:
|
||||
name = _module_name_to_plugin_name(plugin)
|
||||
if name in third_party_plugins or name in previous_plugins:
|
||||
plugin_id = _module_name_to_plugin_id(plugin, get_controlled_modules())
|
||||
if (
|
||||
plugin_id in self._third_party_plugin_ids
|
||||
or plugin_id in previous_plugin_ids
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"Plugin already exists: {name}! Check your plugin name"
|
||||
f"Plugin already exists: {plugin_id}! Check your plugin name"
|
||||
)
|
||||
third_party_plugins[name] = plugin
|
||||
|
||||
self._third_party_plugin_names = third_party_plugins
|
||||
self._third_party_plugin_ids[plugin_id] = plugin
|
||||
|
||||
# check plugins in search path
|
||||
for module_info in pkgutil.iter_modules(self.search_path):
|
||||
@@ -105,47 +124,55 @@ class PluginManager:
|
||||
if module_info.name.startswith("_"):
|
||||
continue
|
||||
|
||||
if (
|
||||
module_info.name in searched_plugins
|
||||
or module_info.name in previous_plugins
|
||||
or module_info.name in third_party_plugins
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"Plugin already exists: {module_info.name}! Check your plugin name"
|
||||
)
|
||||
|
||||
if not (
|
||||
module_spec := module_info.module_finder.find_spec(
|
||||
module_info.name, None
|
||||
)
|
||||
):
|
||||
continue
|
||||
if not (module_path := module_spec.origin):
|
||||
continue
|
||||
searched_plugins[module_info.name] = Path(module_path).resolve()
|
||||
|
||||
self._searched_plugin_names = searched_plugins
|
||||
if not module_spec.origin:
|
||||
continue
|
||||
|
||||
# get module name from path, pkgutil does not return the actual module name
|
||||
module_path = Path(module_spec.origin).resolve()
|
||||
module_name = path_to_module_name(module_path)
|
||||
plugin_id = _module_name_to_plugin_id(module_name, get_controlled_modules())
|
||||
|
||||
if (
|
||||
plugin_id in previous_plugin_ids
|
||||
or plugin_id in self._third_party_plugin_ids
|
||||
or plugin_id in self._searched_plugin_ids
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"Plugin already exists: {plugin_id}! Check your plugin name"
|
||||
)
|
||||
|
||||
self._searched_plugin_ids[plugin_id] = module_name
|
||||
|
||||
return self.available_plugins
|
||||
|
||||
def load_plugin(self, name: str) -> Optional[Plugin]:
|
||||
"""加载指定插件。
|
||||
|
||||
对于独立插件,可以使用完整插件模块名或者插件名称。
|
||||
可以使用完整插件模块名或者插件标识符加载。
|
||||
|
||||
参数:
|
||||
name: 插件名称。
|
||||
name: 插件名称或插件标识符。
|
||||
"""
|
||||
|
||||
try:
|
||||
if name in self.plugins:
|
||||
# load using plugin id
|
||||
if name in self._third_party_plugin_ids:
|
||||
module = importlib.import_module(self._third_party_plugin_ids[name])
|
||||
elif name in self._searched_plugin_ids:
|
||||
module = importlib.import_module(self._searched_plugin_ids[name])
|
||||
# load using module name
|
||||
elif (
|
||||
name in self._third_party_plugin_ids.values()
|
||||
or name in self._searched_plugin_ids.values()
|
||||
):
|
||||
module = importlib.import_module(name)
|
||||
elif name in self._third_party_plugin_names:
|
||||
module = importlib.import_module(self._third_party_plugin_names[name])
|
||||
elif name in self._searched_plugin_names:
|
||||
module = importlib.import_module(
|
||||
path_to_module_name(self._searched_plugin_names[name])
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name")
|
||||
|
||||
@@ -154,13 +181,13 @@ class PluginManager:
|
||||
) is None or not isinstance(plugin, Plugin):
|
||||
raise RuntimeError(
|
||||
f"Module {module.__name__} is not loaded as a plugin! "
|
||||
"Make sure not to import it before loading."
|
||||
f"Make sure not to import it before loading."
|
||||
)
|
||||
logger.opt(colors=True).success(
|
||||
f'Succeeded to load plugin "<y>{escape_tag(plugin.name)}</y>"'
|
||||
f'Succeeded to load plugin "<y>{escape_tag(plugin.id_)}</y>"'
|
||||
+ (
|
||||
f' from "<m>{escape_tag(plugin.module_name)}</m>"'
|
||||
if plugin.module_name != plugin.name
|
||||
if plugin.module_name != plugin.id_
|
||||
else ""
|
||||
)
|
||||
)
|
||||
@@ -170,7 +197,7 @@ class PluginManager:
|
||||
f'<r><bg #f8bbd0>Failed to import "{escape_tag(name)}"</bg #f8bbd0></r>'
|
||||
)
|
||||
|
||||
def load_all_plugins(self) -> Set[Plugin]:
|
||||
def load_all_plugins(self) -> set[Plugin]:
|
||||
"""加载所有可用插件。"""
|
||||
|
||||
return set(
|
||||
@@ -192,21 +219,16 @@ class PluginFinder(MetaPathFinder):
|
||||
module_origin = module_spec.origin
|
||||
if not module_origin:
|
||||
return
|
||||
module_path = Path(module_origin).resolve()
|
||||
|
||||
for manager in reversed(_managers):
|
||||
# use path instead of name in case of submodule name conflict
|
||||
if (
|
||||
fullname in manager.plugins
|
||||
or module_path in manager._searched_plugin_names.values()
|
||||
):
|
||||
if fullname in manager.controlled_modules.values():
|
||||
module_spec.loader = PluginLoader(manager, fullname, module_origin)
|
||||
return module_spec
|
||||
return
|
||||
|
||||
|
||||
class PluginLoader(SourceFileLoader):
|
||||
def __init__(self, manager: PluginManager, fullname: str, path) -> None:
|
||||
def __init__(self, manager: PluginManager, fullname: str, path: str) -> None:
|
||||
self.manager = manager
|
||||
self.loaded = False
|
||||
super().__init__(fullname, path)
|
||||
@@ -226,17 +248,8 @@ class PluginLoader(SourceFileLoader):
|
||||
plugin = _new_plugin(self.name, module, self.manager)
|
||||
setattr(module, "__plugin__", plugin)
|
||||
|
||||
# detect parent plugin before entering current plugin context
|
||||
parent_plugins = _current_plugin_chain.get()
|
||||
for pre_plugin in reversed(parent_plugins):
|
||||
# ensure parent plugin is declared before current plugin
|
||||
if _managers.index(pre_plugin.manager) < _managers.index(self.manager):
|
||||
plugin.parent_plugin = pre_plugin
|
||||
pre_plugin.sub_plugins.add(plugin)
|
||||
break
|
||||
|
||||
# enter plugin context
|
||||
_plugin_token = _current_plugin_chain.set(parent_plugins + (plugin,))
|
||||
_plugin_token = _current_plugin.set(plugin)
|
||||
|
||||
try:
|
||||
super().exec_module(module)
|
||||
@@ -245,7 +258,7 @@ class PluginLoader(SourceFileLoader):
|
||||
raise
|
||||
finally:
|
||||
# leave plugin context
|
||||
_current_plugin_chain.reset(_plugin_token)
|
||||
_current_plugin.reset(_plugin_token)
|
||||
|
||||
# get plugin metadata
|
||||
metadata: Optional[PluginMetadata] = getattr(module, "__plugin_meta__", None)
|
||||
|
@@ -8,7 +8,7 @@ FrontMatter:
|
||||
import contextlib
|
||||
from types import ModuleType
|
||||
from dataclasses import field, dataclass
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
|
||||
from typing import TYPE_CHECKING, Any, Type, Optional # noqa: UP035
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -35,19 +35,19 @@ class PluginMetadata:
|
||||
"""插件类型,用于商店分类"""
|
||||
homepage: Optional[str] = None
|
||||
"""插件主页"""
|
||||
config: Optional[Type[BaseModel]] = None
|
||||
config: Optional[Type[BaseModel]] = None # noqa: UP006
|
||||
"""插件配置项"""
|
||||
supported_adapters: Optional[Set[str]] = None
|
||||
supported_adapters: Optional[set[str]] = None
|
||||
"""插件支持的适配器模块路径
|
||||
|
||||
格式为 `<module>[:<Adapter>]`,`~` 为 `nonebot.adapters.` 的缩写。
|
||||
|
||||
`None` 表示支持**所有适配器**。
|
||||
"""
|
||||
extra: Dict[Any, Any] = field(default_factory=dict)
|
||||
extra: dict[Any, Any] = field(default_factory=dict)
|
||||
"""插件额外信息,可由插件编写者自由扩展定义"""
|
||||
|
||||
def get_supported_adapters(self) -> Optional[Set[Type["Adapter"]]]:
|
||||
def get_supported_adapters(self) -> Optional[set[Type["Adapter"]]]: # noqa: UP006
|
||||
"""获取当前已安装的插件支持适配器类列表"""
|
||||
if self.supported_adapters is None:
|
||||
return None
|
||||
@@ -66,17 +66,24 @@ class Plugin:
|
||||
"""存储插件信息"""
|
||||
|
||||
name: str
|
||||
"""插件索引标识,NoneBot 使用 文件/文件夹 名称作为标识符"""
|
||||
"""插件名称,NoneBot 使用 文件/文件夹 名称作为插件名称"""
|
||||
module: ModuleType
|
||||
"""插件模块对象"""
|
||||
module_name: str
|
||||
"""点分割模块路径"""
|
||||
manager: "PluginManager"
|
||||
"""导入该插件的插件管理器"""
|
||||
matcher: Set[Type[Matcher]] = field(default_factory=set)
|
||||
matcher: set[type[Matcher]] = field(default_factory=set)
|
||||
"""插件加载时定义的 `Matcher`"""
|
||||
parent_plugin: Optional["Plugin"] = None
|
||||
"""父插件"""
|
||||
sub_plugins: Set["Plugin"] = field(default_factory=set)
|
||||
sub_plugins: set["Plugin"] = field(default_factory=set)
|
||||
"""子插件集合"""
|
||||
metadata: Optional[PluginMetadata] = None
|
||||
|
||||
@property
|
||||
def id_(self) -> str:
|
||||
"""插件索引标识"""
|
||||
return (
|
||||
f"{self.parent_plugin.id_}:{self.name}" if self.parent_plugin else self.name
|
||||
)
|
||||
|
@@ -9,8 +9,8 @@ import re
|
||||
import inspect
|
||||
import warnings
|
||||
from types import ModuleType
|
||||
from typing import Any, Union, Optional
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.permission import Permission
|
||||
@@ -31,19 +31,19 @@ from nonebot.rule import (
|
||||
)
|
||||
|
||||
from .model import Plugin
|
||||
from .manager import _current_plugin
|
||||
from . import get_plugin_by_module_name
|
||||
from .manager import _current_plugin_chain
|
||||
|
||||
|
||||
def store_matcher(matcher: Type[Matcher]) -> None:
|
||||
def store_matcher(matcher: type[Matcher]) -> None:
|
||||
"""存储一个事件响应器到插件。
|
||||
|
||||
参数:
|
||||
matcher: 事件响应器
|
||||
"""
|
||||
# only store the matcher defined when plugin loading
|
||||
if plugin_chain := _current_plugin_chain.get():
|
||||
plugin_chain[-1].matcher.add(matcher)
|
||||
if plugin := _current_plugin.get():
|
||||
plugin.matcher.add(matcher)
|
||||
|
||||
|
||||
def get_matcher_plugin(depth: int = 1) -> Optional[Plugin]: # pragma: no cover
|
||||
@@ -76,7 +76,7 @@ def get_matcher_module(depth: int = 1) -> Optional[ModuleType]: # pragma: no co
|
||||
return (source := get_matcher_source(depth + 1)) and source.module
|
||||
|
||||
|
||||
def get_matcher_source(depth: int = 1) -> Optional[MatcherSource]:
|
||||
def get_matcher_source(depth: int = 0) -> Optional[MatcherSource]:
|
||||
"""获取事件响应器定义所在源码信息。
|
||||
|
||||
参数:
|
||||
@@ -85,20 +85,25 @@ def get_matcher_source(depth: int = 1) -> Optional[MatcherSource]:
|
||||
current_frame = inspect.currentframe()
|
||||
if current_frame is None:
|
||||
return None
|
||||
frame = inspect.getouterframes(current_frame)[depth + 1].frame
|
||||
|
||||
frame = current_frame
|
||||
d = depth + 1
|
||||
while d > 0:
|
||||
frame = frame.f_back
|
||||
if frame is None:
|
||||
raise ValueError("Depth out of range")
|
||||
d -= 1
|
||||
|
||||
module_name = (module := inspect.getmodule(frame)) and module.__name__
|
||||
|
||||
plugin: Optional["Plugin"] = None
|
||||
# matcher defined when plugin loading
|
||||
if plugin_chain := _current_plugin_chain.get():
|
||||
plugin = plugin_chain[-1]
|
||||
plugin: Optional["Plugin"] = _current_plugin.get()
|
||||
# matcher defined when plugin running
|
||||
elif module_name:
|
||||
if plugin is None and module_name:
|
||||
plugin = get_plugin_by_module_name(module_name)
|
||||
|
||||
return MatcherSource(
|
||||
plugin_name=plugin and plugin.name,
|
||||
plugin_id=plugin and plugin.id_,
|
||||
module_name=module_name,
|
||||
lineno=frame.f_lineno,
|
||||
)
|
||||
@@ -109,14 +114,14 @@ def on(
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
permission: Optional[Union[Permission, T_PermissionChecker]] = None,
|
||||
*,
|
||||
handlers: Optional[List[Union[T_Handler, Dependent]]] = None,
|
||||
handlers: Optional[list[Union[T_Handler, Dependent[Any]]]] = None,
|
||||
temp: bool = False,
|
||||
expire_time: Optional[Union[datetime, timedelta]] = None,
|
||||
priority: int = 1,
|
||||
block: bool = False,
|
||||
state: Optional[T_State] = None,
|
||||
_depth: int = 0,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个基础事件响应器,可自定义类型。
|
||||
|
||||
参数:
|
||||
@@ -146,7 +151,7 @@ def on(
|
||||
return matcher
|
||||
|
||||
|
||||
def on_metaevent(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
def on_metaevent(*args, _depth: int = 0, **kwargs) -> type[Matcher]:
|
||||
"""注册一个元事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -162,7 +167,7 @@ def on_metaevent(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
return on("meta_event", *args, **kwargs, _depth=_depth + 1)
|
||||
|
||||
|
||||
def on_message(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
def on_message(*args, _depth: int = 0, **kwargs) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -179,7 +184,7 @@ def on_message(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
return on("message", *args, **kwargs, _depth=_depth + 1)
|
||||
|
||||
|
||||
def on_notice(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
def on_notice(*args, _depth: int = 0, **kwargs) -> type[Matcher]:
|
||||
"""注册一个通知事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -195,7 +200,7 @@ def on_notice(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
return on("notice", *args, **kwargs, _depth=_depth + 1)
|
||||
|
||||
|
||||
def on_request(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
def on_request(*args, _depth: int = 0, **kwargs) -> type[Matcher]:
|
||||
"""注册一个请求事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -212,12 +217,12 @@ def on_request(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
|
||||
|
||||
|
||||
def on_startswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
msg: Union[str, tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
ignorecase: bool = False,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息的**文本部分**以指定内容开头时响应。
|
||||
|
||||
参数:
|
||||
@@ -236,12 +241,12 @@ def on_startswith(
|
||||
|
||||
|
||||
def on_endswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
msg: Union[str, tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
ignorecase: bool = False,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息的**文本部分**以指定内容结尾时响应。
|
||||
|
||||
参数:
|
||||
@@ -260,12 +265,12 @@ def on_endswith(
|
||||
|
||||
|
||||
def on_fullmatch(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
msg: Union[str, tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
ignorecase: bool = False,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息的**文本部分**与指定内容完全一致时响应。
|
||||
|
||||
参数:
|
||||
@@ -284,11 +289,11 @@ def on_fullmatch(
|
||||
|
||||
|
||||
def on_keyword(
|
||||
keywords: Set[str],
|
||||
keywords: set[str],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应。
|
||||
|
||||
参数:
|
||||
@@ -306,13 +311,13 @@ def on_keyword(
|
||||
|
||||
|
||||
def on_command(
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
cmd: Union[str, tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
aliases: Optional[set[Union[str, tuple[str, ...]]]] = None,
|
||||
force_whitespace: Optional[Union[str, bool]] = None,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息以指定命令开头时响应。
|
||||
|
||||
命令匹配规则参考: `命令形式匹配 <rule.md#command-command>`_
|
||||
@@ -341,13 +346,13 @@ def on_command(
|
||||
|
||||
|
||||
def on_shell_command(
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
cmd: Union[str, tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
aliases: Optional[set[Union[str, tuple[str, ...]]]] = None,
|
||||
parser: Optional[ArgumentParser] = None,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个支持 `shell_like` 解析参数的命令消息事件响应器。
|
||||
|
||||
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
|
||||
@@ -383,7 +388,7 @@ def on_regex(
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息匹配正则表达式时响应。
|
||||
|
||||
命令匹配规则参考: `正则匹配 <rule.md#regex-regex-flags-0>`_
|
||||
@@ -404,12 +409,12 @@ def on_regex(
|
||||
|
||||
|
||||
def on_type(
|
||||
types: Union[Type[Event], Tuple[Type[Event], ...]],
|
||||
types: Union[type[Event], tuple[type[Event], ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
*,
|
||||
_depth: int = 0,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个事件响应器,并且当事件为指定类型时响应。
|
||||
|
||||
参数:
|
||||
@@ -430,14 +435,14 @@ def on_type(
|
||||
class _Group:
|
||||
def __init__(self, **kwargs):
|
||||
"""创建一个事件响应器组合,参数为默认值,与 `on` 一致"""
|
||||
self.matchers: List[Type[Matcher]] = []
|
||||
self.matchers: list[type[Matcher]] = []
|
||||
"""组内事件响应器列表"""
|
||||
self.base_kwargs: Dict[str, Any] = kwargs
|
||||
self.base_kwargs: dict[str, Any] = kwargs
|
||||
"""其他传递给 `on` 的参数默认值"""
|
||||
|
||||
def _get_final_kwargs(
|
||||
self, update: Dict[str, Any], *, exclude: Optional[Set[str]] = None
|
||||
) -> Dict[str, Any]:
|
||||
self, update: dict[str, Any], *, exclude: Optional[set[str]] = None
|
||||
) -> dict[str, Any]:
|
||||
"""获取最终传递给 `on` 的参数
|
||||
|
||||
参数:
|
||||
@@ -470,18 +475,18 @@ class CommandGroup(_Group):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, cmd: Union[str, Tuple[str, ...]], prefix_aliases: bool = False, **kwargs
|
||||
self, cmd: Union[str, tuple[str, ...]], prefix_aliases: bool = False, **kwargs
|
||||
):
|
||||
"""命令前缀"""
|
||||
super().__init__(**kwargs)
|
||||
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
|
||||
self.basecmd: tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
|
||||
self.base_kwargs.pop("aliases", None)
|
||||
self.prefix_aliases = prefix_aliases
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"CommandGroup(cmd={self.basecmd}, matchers={len(self.matchers)})"
|
||||
|
||||
def command(self, cmd: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
|
||||
def command(self, cmd: Union[str, tuple[str, ...]], **kwargs) -> type[Matcher]:
|
||||
"""注册一个新的命令。新参数将会覆盖命令组默认值
|
||||
|
||||
参数:
|
||||
@@ -509,8 +514,8 @@ class CommandGroup(_Group):
|
||||
return matcher
|
||||
|
||||
def shell_command(
|
||||
self, cmd: Union[str, Tuple[str, ...]], **kwargs
|
||||
) -> Type[Matcher]:
|
||||
self, cmd: Union[str, tuple[str, ...]], **kwargs
|
||||
) -> type[Matcher]:
|
||||
"""注册一个新的 `shell_like` 命令。新参数将会覆盖命令组默认值
|
||||
|
||||
参数:
|
||||
@@ -544,7 +549,7 @@ class MatcherGroup(_Group):
|
||||
def __repr__(self) -> str:
|
||||
return f"MatcherGroup(matchers={len(self.matchers)})"
|
||||
|
||||
def on(self, **kwargs) -> Type[Matcher]:
|
||||
def on(self, **kwargs) -> type[Matcher]:
|
||||
"""注册一个基础事件响应器,可自定义类型。
|
||||
|
||||
参数:
|
||||
@@ -562,7 +567,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_metaevent(self, **kwargs) -> Type[Matcher]:
|
||||
def on_metaevent(self, **kwargs) -> type[Matcher]:
|
||||
"""注册一个元事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -580,7 +585,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_message(self, **kwargs) -> Type[Matcher]:
|
||||
def on_message(self, **kwargs) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -598,7 +603,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_notice(self, **kwargs) -> Type[Matcher]:
|
||||
def on_notice(self, **kwargs) -> type[Matcher]:
|
||||
"""注册一个通知事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -616,7 +621,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_request(self, **kwargs) -> Type[Matcher]:
|
||||
def on_request(self, **kwargs) -> type[Matcher]:
|
||||
"""注册一个请求事件响应器。
|
||||
|
||||
参数:
|
||||
@@ -635,8 +640,8 @@ class MatcherGroup(_Group):
|
||||
return matcher
|
||||
|
||||
def on_startswith(
|
||||
self, msg: Union[str, Tuple[str, ...]], **kwargs
|
||||
) -> Type[Matcher]:
|
||||
self, msg: Union[str, tuple[str, ...]], **kwargs
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息的**文本部分**以指定内容开头时响应。
|
||||
|
||||
参数:
|
||||
@@ -656,7 +661,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_endswith(self, msg: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
|
||||
def on_endswith(self, msg: Union[str, tuple[str, ...]], **kwargs) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息的**文本部分**以指定内容结尾时响应。
|
||||
|
||||
参数:
|
||||
@@ -676,7 +681,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_fullmatch(self, msg: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
|
||||
def on_fullmatch(self, msg: Union[str, tuple[str, ...]], **kwargs) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息的**文本部分**与指定内容完全一致时响应。
|
||||
|
||||
参数:
|
||||
@@ -696,7 +701,7 @@ class MatcherGroup(_Group):
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_keyword(self, keywords: Set[str], **kwargs) -> Type[Matcher]:
|
||||
def on_keyword(self, keywords: set[str], **kwargs) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应。
|
||||
|
||||
参数:
|
||||
@@ -717,11 +722,11 @@ class MatcherGroup(_Group):
|
||||
|
||||
def on_command(
|
||||
self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
cmd: Union[str, tuple[str, ...]],
|
||||
aliases: Optional[set[Union[str, tuple[str, ...]]]] = None,
|
||||
force_whitespace: Optional[Union[str, bool]] = None,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息以指定命令开头时响应。
|
||||
|
||||
命令匹配规则参考: `命令形式匹配 <rule.md#command-command>`_
|
||||
@@ -748,11 +753,11 @@ class MatcherGroup(_Group):
|
||||
|
||||
def on_shell_command(
|
||||
self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
cmd: Union[str, tuple[str, ...]],
|
||||
aliases: Optional[set[Union[str, tuple[str, ...]]]] = None,
|
||||
parser: Optional[ArgumentParser] = None,
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个支持 `shell_like` 解析参数的命令消息事件响应器。
|
||||
|
||||
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
|
||||
@@ -780,7 +785,7 @@ class MatcherGroup(_Group):
|
||||
|
||||
def on_regex(
|
||||
self, pattern: str, flags: Union[int, re.RegexFlag] = 0, **kwargs
|
||||
) -> Type[Matcher]:
|
||||
) -> type[Matcher]:
|
||||
"""注册一个消息事件响应器,并且当消息匹配正则表达式时响应。
|
||||
|
||||
命令匹配规则参考: `正则匹配 <rule.md#regex-regex-flags-0>`_
|
||||
@@ -803,8 +808,8 @@ class MatcherGroup(_Group):
|
||||
return matcher
|
||||
|
||||
def on_type(
|
||||
self, types: Union[Type[Event], Tuple[Type[Event]]], **kwargs
|
||||
) -> Type[Matcher]:
|
||||
self, types: Union[type[Event], tuple[type[Event]]], **kwargs
|
||||
) -> type[Matcher]:
|
||||
"""注册一个事件响应器,并且当事件为指定类型时响应。
|
||||
|
||||
参数:
|
||||
|
@@ -21,7 +21,7 @@ def on(
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
*,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -32,7 +32,7 @@ def on_metaevent(
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
*,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -43,7 +43,7 @@ def on_message(
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
*,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -54,7 +54,7 @@ def on_notice(
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
*,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -65,7 +65,7 @@ def on_request(
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
*,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -78,7 +78,7 @@ def on_startswith(
|
||||
ignorecase: bool = ...,
|
||||
*,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -91,7 +91,7 @@ def on_endswith(
|
||||
ignorecase: bool = ...,
|
||||
*,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -104,7 +104,7 @@ def on_fullmatch(
|
||||
ignorecase: bool = ...,
|
||||
*,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -116,7 +116,7 @@ def on_keyword(
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
*,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -130,7 +130,7 @@ def on_command(
|
||||
force_whitespace: str | bool | None = ...,
|
||||
*,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -144,7 +144,7 @@ def on_shell_command(
|
||||
parser: ArgumentParser | None = ...,
|
||||
*,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -157,7 +157,7 @@ def on_regex(
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
*,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -169,7 +169,7 @@ def on_type(
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
*,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -194,7 +194,7 @@ class CommandGroup(_Group):
|
||||
*,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -209,7 +209,7 @@ class CommandGroup(_Group):
|
||||
aliases: set[str | tuple[str, ...]] | None = ...,
|
||||
force_whitespace: str | bool | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -224,7 +224,7 @@ class CommandGroup(_Group):
|
||||
aliases: set[str | tuple[str, ...]] | None = ...,
|
||||
parser: ArgumentParser | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -239,7 +239,7 @@ class MatcherGroup(_Group):
|
||||
type: str = ...,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -252,7 +252,7 @@ class MatcherGroup(_Group):
|
||||
type: str = ...,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -264,7 +264,7 @@ class MatcherGroup(_Group):
|
||||
*,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -276,7 +276,7 @@ class MatcherGroup(_Group):
|
||||
*,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -288,7 +288,7 @@ class MatcherGroup(_Group):
|
||||
*,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -300,7 +300,7 @@ class MatcherGroup(_Group):
|
||||
*,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -314,7 +314,7 @@ class MatcherGroup(_Group):
|
||||
ignorecase: bool = ...,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -328,7 +328,7 @@ class MatcherGroup(_Group):
|
||||
ignorecase: bool = ...,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -342,7 +342,7 @@ class MatcherGroup(_Group):
|
||||
ignorecase: bool = ...,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -355,7 +355,7 @@ class MatcherGroup(_Group):
|
||||
*,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -370,7 +370,7 @@ class MatcherGroup(_Group):
|
||||
*,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -385,7 +385,7 @@ class MatcherGroup(_Group):
|
||||
*,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -399,7 +399,7 @@ class MatcherGroup(_Group):
|
||||
*,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
@@ -412,7 +412,7 @@ class MatcherGroup(_Group):
|
||||
*,
|
||||
rule: Rule | T_RuleChecker | None = ...,
|
||||
permission: Permission | T_PermissionChecker | None = ...,
|
||||
handlers: list[T_Handler | Dependent] | None = ...,
|
||||
handlers: list[T_Handler | Dependent[Any]] | None = ...,
|
||||
temp: bool = ...,
|
||||
expire_time: datetime | timedelta | None = ...,
|
||||
priority: int = ...,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import Dict, AsyncGenerator
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.params import Depends
|
||||
@@ -15,7 +15,7 @@ __plugin_meta__ = PluginMetadata(
|
||||
supported_adapters=None,
|
||||
)
|
||||
|
||||
_running_matcher: Dict[str, int] = {}
|
||||
_running_matcher: dict[str, int] = {}
|
||||
|
||||
|
||||
async def matcher_mutex(event: Event) -> AsyncGenerator[bool, None]:
|
||||
|
@@ -15,19 +15,16 @@ from argparse import Action
|
||||
from gettext import gettext
|
||||
from argparse import ArgumentError
|
||||
from contextvars import ContextVar
|
||||
from collections.abc import Sequence
|
||||
from itertools import chain, product
|
||||
from argparse import Namespace as Namespace
|
||||
from argparse import ArgumentParser as ArgParser
|
||||
from typing import (
|
||||
IO,
|
||||
TYPE_CHECKING,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
TypeVar,
|
||||
Optional,
|
||||
Sequence,
|
||||
TypedDict,
|
||||
NamedTuple,
|
||||
cast,
|
||||
@@ -63,7 +60,7 @@ T = TypeVar("T")
|
||||
|
||||
|
||||
class CMD_RESULT(TypedDict):
|
||||
command: Optional[Tuple[str, ...]]
|
||||
command: Optional[tuple[str, ...]]
|
||||
raw_command: Optional[str]
|
||||
command_arg: Optional[Message]
|
||||
command_start: Optional[str]
|
||||
@@ -72,7 +69,7 @@ class CMD_RESULT(TypedDict):
|
||||
|
||||
class TRIE_VALUE(NamedTuple):
|
||||
command_start: str
|
||||
command: Tuple[str, ...]
|
||||
command: tuple[str, ...]
|
||||
|
||||
|
||||
parser_message: ContextVar[str] = ContextVar("parser_message")
|
||||
@@ -149,7 +146,7 @@ class StartswithRule:
|
||||
|
||||
__slots__ = ("msg", "ignorecase")
|
||||
|
||||
def __init__(self, msg: Tuple[str, ...], ignorecase: bool = False):
|
||||
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
|
||||
self.msg = msg
|
||||
self.ignorecase = ignorecase
|
||||
|
||||
@@ -181,7 +178,7 @@ class StartswithRule:
|
||||
return False
|
||||
|
||||
|
||||
def startswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
def startswith(msg: Union[str, tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
"""匹配消息纯文本开头。
|
||||
|
||||
参数:
|
||||
@@ -204,7 +201,7 @@ class EndswithRule:
|
||||
|
||||
__slots__ = ("msg", "ignorecase")
|
||||
|
||||
def __init__(self, msg: Tuple[str, ...], ignorecase: bool = False):
|
||||
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
|
||||
self.msg = msg
|
||||
self.ignorecase = ignorecase
|
||||
|
||||
@@ -236,7 +233,7 @@ class EndswithRule:
|
||||
return False
|
||||
|
||||
|
||||
def endswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
def endswith(msg: Union[str, tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
"""匹配消息纯文本结尾。
|
||||
|
||||
参数:
|
||||
@@ -259,7 +256,7 @@ class FullmatchRule:
|
||||
|
||||
__slots__ = ("msg", "ignorecase")
|
||||
|
||||
def __init__(self, msg: Tuple[str, ...], ignorecase: bool = False):
|
||||
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
|
||||
self.msg = tuple(map(str.casefold, msg) if ignorecase else msg)
|
||||
self.ignorecase = ignorecase
|
||||
|
||||
@@ -290,7 +287,7 @@ class FullmatchRule:
|
||||
return False
|
||||
|
||||
|
||||
def fullmatch(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
def fullmatch(msg: Union[str, tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
"""完全匹配消息。
|
||||
|
||||
参数:
|
||||
@@ -361,7 +358,7 @@ class CommandRule:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cmds: List[Tuple[str, ...]],
|
||||
cmds: list[tuple[str, ...]],
|
||||
force_whitespace: Optional[Union[str, bool]] = None,
|
||||
):
|
||||
self.cmds = tuple(cmds)
|
||||
@@ -380,7 +377,7 @@ class CommandRule:
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
cmd: Optional[Tuple[str, ...]] = Command(),
|
||||
cmd: Optional[tuple[str, ...]] = Command(),
|
||||
cmd_arg: Optional[Message] = CommandArg(),
|
||||
cmd_whitespace: Optional[str] = CommandWhitespace(),
|
||||
) -> bool:
|
||||
@@ -394,7 +391,7 @@ class CommandRule:
|
||||
|
||||
|
||||
def command(
|
||||
*cmds: Union[str, Tuple[str, ...]],
|
||||
*cmds: Union[str, tuple[str, ...]],
|
||||
force_whitespace: Optional[Union[str, bool]] = None,
|
||||
) -> Rule:
|
||||
"""匹配消息命令。
|
||||
@@ -424,7 +421,7 @@ def command(
|
||||
config = get_driver().config
|
||||
command_start = config.command_start
|
||||
command_sep = config.command_sep
|
||||
commands: List[Tuple[str, ...]] = []
|
||||
commands: list[tuple[str, ...]] = []
|
||||
for command in cmds:
|
||||
if isinstance(command, str):
|
||||
command = (command,)
|
||||
@@ -460,23 +457,23 @@ class ArgumentParser(ArgParser):
|
||||
self,
|
||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||
namespace: None = None,
|
||||
) -> Tuple[Namespace, List[Union[str, MessageSegment]]]: ...
|
||||
) -> tuple[Namespace, list[Union[str, MessageSegment]]]: ...
|
||||
|
||||
@overload
|
||||
def parse_known_args(
|
||||
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
|
||||
) -> Tuple[T, List[Union[str, MessageSegment]]]: ...
|
||||
) -> tuple[T, list[Union[str, MessageSegment]]]: ...
|
||||
|
||||
@overload
|
||||
def parse_known_args(
|
||||
self, *, namespace: T
|
||||
) -> Tuple[T, List[Union[str, MessageSegment]]]: ...
|
||||
) -> tuple[T, list[Union[str, MessageSegment]]]: ...
|
||||
|
||||
def parse_known_args(
|
||||
def parse_known_args( # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self,
|
||||
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
|
||||
namespace: Optional[T] = None,
|
||||
) -> Tuple[Union[Namespace, T], List[Union[str, MessageSegment]]]: ...
|
||||
) -> tuple[Union[Namespace, T], list[Union[str, MessageSegment]]]: ...
|
||||
|
||||
@overload
|
||||
def parse_args(
|
||||
@@ -506,7 +503,7 @@ class ArgumentParser(ArgParser):
|
||||
|
||||
def _parse_optional(
|
||||
self, arg_string: Union[str, MessageSegment]
|
||||
) -> Optional[Tuple[Optional[Action], str, Optional[str]]]:
|
||||
) -> Optional[tuple[Optional[Action], str, Optional[str]]]:
|
||||
return (
|
||||
super()._parse_optional(arg_string) if isinstance(arg_string, str) else None
|
||||
)
|
||||
@@ -533,7 +530,7 @@ class ShellCommandRule:
|
||||
|
||||
__slots__ = ("cmds", "parser")
|
||||
|
||||
def __init__(self, cmds: List[Tuple[str, ...]], parser: Optional[ArgumentParser]):
|
||||
def __init__(self, cmds: list[tuple[str, ...]], parser: Optional[ArgumentParser]):
|
||||
self.cmds = tuple(cmds)
|
||||
self.parser = parser
|
||||
|
||||
@@ -553,7 +550,7 @@ class ShellCommandRule:
|
||||
async def __call__(
|
||||
self,
|
||||
state: T_State,
|
||||
cmd: Optional[Tuple[str, ...]] = Command(),
|
||||
cmd: Optional[tuple[str, ...]] = Command(),
|
||||
msg: Optional[Message] = CommandArg(),
|
||||
) -> bool:
|
||||
if cmd not in self.cmds or msg is None:
|
||||
@@ -571,7 +568,7 @@ class ShellCommandRule:
|
||||
try:
|
||||
args = self.parser.parse_args(state[SHELL_ARGV])
|
||||
state[SHELL_ARGS] = args
|
||||
except ArgumentError as e: # pragma: py-gte-39
|
||||
except ArgumentError as e:
|
||||
state[SHELL_ARGS] = ParserExit(status=2, message=str(e))
|
||||
except ParserExit as e:
|
||||
state[SHELL_ARGS] = e
|
||||
@@ -581,7 +578,7 @@ class ShellCommandRule:
|
||||
|
||||
|
||||
def shell_command(
|
||||
*cmds: Union[str, Tuple[str, ...]], parser: Optional[ArgumentParser] = None
|
||||
*cmds: Union[str, tuple[str, ...]], parser: Optional[ArgumentParser] = None
|
||||
) -> Rule:
|
||||
"""匹配 `shell_like` 形式的消息命令。
|
||||
|
||||
@@ -629,7 +626,7 @@ def shell_command(
|
||||
config = get_driver().config
|
||||
command_start = config.command_start
|
||||
command_sep = config.command_sep
|
||||
commands: List[Tuple[str, ...]] = []
|
||||
commands: list[tuple[str, ...]] = []
|
||||
for command in cmds:
|
||||
if isinstance(command, str):
|
||||
command = (command,)
|
||||
@@ -740,7 +737,7 @@ class IsTypeRule:
|
||||
|
||||
__slots__ = ("types",)
|
||||
|
||||
def __init__(self, *types: Type[Event]):
|
||||
def __init__(self, *types: type[Event]):
|
||||
self.types = types
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -756,7 +753,7 @@ class IsTypeRule:
|
||||
return isinstance(event, self.types)
|
||||
|
||||
|
||||
def is_type(*types: Type[Event]) -> Rule:
|
||||
def is_type(*types: type[Event]) -> Rule:
|
||||
"""匹配事件类型。
|
||||
|
||||
参数:
|
||||
|
@@ -13,7 +13,6 @@ FrontMatter:
|
||||
import sys
|
||||
import types
|
||||
import warnings
|
||||
import contextlib
|
||||
import typing as t
|
||||
import typing_extensions as t_ext
|
||||
from typing import TYPE_CHECKING, TypeVar
|
||||
@@ -45,26 +44,38 @@ def overrides(InterfaceClass: object):
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
|
||||
def origin_is_union(origin: t.Optional[t.Type[t.Any]]) -> bool:
|
||||
def type_has_args(type_: type[t.Any]) -> bool:
|
||||
"""判断类型是否有参数"""
|
||||
return isinstance(type_, (t._GenericAlias, types.GenericAlias)) # type: ignore
|
||||
|
||||
else:
|
||||
|
||||
def type_has_args(type_: type[t.Any]) -> bool:
|
||||
return isinstance(type_, (t._GenericAlias, types.GenericAlias, types.UnionType)) # type: ignore
|
||||
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
|
||||
def origin_is_union(origin: t.Optional[type[t.Any]]) -> bool:
|
||||
"""判断是否是 Union 类型"""
|
||||
return origin is t.Union
|
||||
|
||||
else:
|
||||
|
||||
def origin_is_union(origin: t.Optional[t.Type[t.Any]]) -> bool:
|
||||
def origin_is_union(origin: t.Optional[type[t.Any]]) -> bool:
|
||||
return origin is t.Union or origin is types.UnionType
|
||||
|
||||
|
||||
def origin_is_literal(origin: t.Optional[t.Type[t.Any]]) -> bool:
|
||||
def origin_is_literal(origin: t.Optional[type[t.Any]]) -> bool:
|
||||
"""判断是否是 Literal 类型"""
|
||||
return origin is t.Literal or origin is t_ext.Literal
|
||||
|
||||
|
||||
def _literal_values(type_: t.Type[t.Any]) -> t.Tuple[t.Any, ...]:
|
||||
def _literal_values(type_: type[t.Any]) -> tuple[t.Any, ...]:
|
||||
return get_args(type_)
|
||||
|
||||
|
||||
def all_literal_values(type_: t.Type[t.Any]) -> t.List[t.Any]:
|
||||
def all_literal_values(type_: type[t.Any]) -> list[t.Any]:
|
||||
"""获取 Literal 类型包含的所有值"""
|
||||
if not origin_is_literal(get_origin(type_)):
|
||||
return [type_]
|
||||
@@ -72,11 +83,9 @@ def all_literal_values(type_: t.Type[t.Any]) -> t.List[t.Any]:
|
||||
return [x for value in _literal_values(type_) for x in all_literal_values(value)]
|
||||
|
||||
|
||||
def origin_is_annotated(origin: t.Optional[t.Type[t.Any]]) -> bool:
|
||||
def origin_is_annotated(origin: t.Optional[type[t.Any]]) -> bool:
|
||||
"""判断是否是 Annotated 类型"""
|
||||
with contextlib.suppress(TypeError):
|
||||
return origin is not None and issubclass(origin, t_ext.Annotated)
|
||||
return False
|
||||
return origin is t_ext.Annotated
|
||||
|
||||
|
||||
NONE_TYPES = {None, type(None), t.Literal[None], t_ext.Literal[None]}
|
||||
@@ -84,28 +93,31 @@ if sys.version_info >= (3, 10):
|
||||
NONE_TYPES.add(types.NoneType)
|
||||
|
||||
|
||||
def is_none_type(type_: t.Type[t.Any]) -> bool:
|
||||
def is_none_type(type_: type[t.Any]) -> bool:
|
||||
"""判断是否是 None 类型"""
|
||||
return type_ in NONE_TYPES
|
||||
|
||||
|
||||
if sys.version_info < (3, 9): # pragma: py-lt-39
|
||||
|
||||
def evaluate_forwardref(
|
||||
ref: t.ForwardRef, globalns: t.Dict[str, t.Any], localns: t.Dict[str, t.Any]
|
||||
) -> t.Any:
|
||||
return ref._evaluate(globalns, localns)
|
||||
|
||||
else: # pragma: py-gte-39
|
||||
|
||||
def evaluate_forwardref(
|
||||
ref: t.ForwardRef, globalns: t.Dict[str, t.Any], localns: t.Dict[str, t.Any]
|
||||
) -> t.Any:
|
||||
return ref._evaluate(globalns, localns, frozenset())
|
||||
def evaluate_forwardref(
|
||||
ref: t.ForwardRef, globalns: dict[str, t.Any], localns: dict[str, t.Any]
|
||||
) -> t.Any:
|
||||
# Python 3.13/3.12.4+ made `recursive_guard` a kwarg,
|
||||
# so name it explicitly to avoid:
|
||||
# TypeError: ForwardRef._evaluate()
|
||||
# missing 1 required keyword-only argument: 'recursive_guard'
|
||||
return ref._evaluate(globalns, localns, recursive_guard=frozenset())
|
||||
|
||||
|
||||
# state
|
||||
T_State: TypeAlias = t.Dict[t.Any, t.Any]
|
||||
# use annotated flag to avoid ForwardRef recreate generic type (py >= 3.11)
|
||||
class StateFlag:
|
||||
def __repr__(self) -> str:
|
||||
return "StateFlag()"
|
||||
|
||||
|
||||
_STATE_FLAG = StateFlag()
|
||||
|
||||
T_State: TypeAlias = t.Annotated[dict[t.Any, t.Any], _STATE_FLAG]
|
||||
"""事件处理状态 State 类型"""
|
||||
|
||||
_DependentCallable: TypeAlias = t.Union[
|
||||
@@ -134,11 +146,11 @@ T_BotDisconnectionHook: TypeAlias = _DependentCallable[t.Any]
|
||||
|
||||
# api hooks
|
||||
T_CallingAPIHook: TypeAlias = t.Callable[
|
||||
["Bot", str, t.Dict[str, t.Any]], t.Awaitable[t.Any]
|
||||
["Bot", str, dict[str, t.Any]], t.Awaitable[t.Any]
|
||||
]
|
||||
"""`bot.call_api` 钩子函数"""
|
||||
T_CalledAPIHook: TypeAlias = t.Callable[
|
||||
["Bot", t.Optional[Exception], str, t.Dict[str, t.Any], t.Any], t.Awaitable[t.Any]
|
||||
["Bot", t.Optional[Exception], str, dict[str, t.Any], t.Any], t.Awaitable[t.Any]
|
||||
]
|
||||
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
|
||||
|
||||
@@ -244,5 +256,5 @@ T_PermissionUpdater: TypeAlias = _DependentCallable["Permission"]
|
||||
- MatcherParam: Matcher 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_DependencyCache: TypeAlias = t.Dict[_DependentCallable[t.Any], "Task[t.Any]"]
|
||||
T_DependencyCache: TypeAlias = dict[_DependentCallable[t.Any], "Task[t.Any]"]
|
||||
"""依赖缓存, 用于存储依赖函数的返回值"""
|
||||
|
101
nonebot/utils.py
101
nonebot/utils.py
@@ -10,36 +10,23 @@ import json
|
||||
import asyncio
|
||||
import inspect
|
||||
import importlib
|
||||
import contextlib
|
||||
import dataclasses
|
||||
from pathlib import Path
|
||||
from collections import deque
|
||||
from contextvars import copy_context
|
||||
from functools import wraps, partial
|
||||
from contextlib import asynccontextmanager
|
||||
from contextlib import AbstractContextManager, asynccontextmanager
|
||||
from typing_extensions import ParamSpec, get_args, override, get_origin
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Generic,
|
||||
Mapping,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Optional,
|
||||
Sequence,
|
||||
Coroutine,
|
||||
AsyncGenerator,
|
||||
ContextManager,
|
||||
overload,
|
||||
)
|
||||
from collections.abc import Mapping, Sequence, Coroutine, AsyncGenerator
|
||||
from typing import Any, Union, Generic, TypeVar, Callable, Optional, overload
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.typing import (
|
||||
is_none_type,
|
||||
type_has_args,
|
||||
origin_is_union,
|
||||
origin_is_literal,
|
||||
all_literal_values,
|
||||
@@ -64,8 +51,8 @@ def escape_tag(s: str) -> str:
|
||||
|
||||
|
||||
def deep_update(
|
||||
mapping: Dict[K, Any], *updating_mappings: Dict[K, Any]
|
||||
) -> Dict[K, Any]:
|
||||
mapping: dict[K, Any], *updating_mappings: dict[K, Any]
|
||||
) -> dict[K, Any]:
|
||||
"""深度更新合并字典"""
|
||||
updated_mapping = mapping.copy()
|
||||
for updating_mapping in updating_mappings:
|
||||
@@ -82,7 +69,7 @@ def deep_update(
|
||||
|
||||
|
||||
def lenient_issubclass(
|
||||
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...]]
|
||||
cls: Any, class_or_tuple: Union[type[Any], tuple[type[Any], ...]]
|
||||
) -> bool:
|
||||
"""检查 cls 是否是 class_or_tuple 中的一个类型子类并忽略类型错误。"""
|
||||
try:
|
||||
@@ -92,7 +79,7 @@ def lenient_issubclass(
|
||||
|
||||
|
||||
def generic_check_issubclass(
|
||||
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...]]
|
||||
cls: Any, class_or_tuple: Union[type[Any], tuple[type[Any], ...]]
|
||||
) -> bool:
|
||||
"""检查 cls 是否是 class_or_tuple 中的一个类型子类。
|
||||
|
||||
@@ -106,46 +93,46 @@ def generic_check_issubclass(
|
||||
则会检查其 `__bound__` 或 `__constraints__`
|
||||
是否是 class_or_tuple 中一个类型的子类或 None。
|
||||
"""
|
||||
try:
|
||||
return issubclass(cls, class_or_tuple)
|
||||
except TypeError:
|
||||
origin = get_origin(cls)
|
||||
if origin_is_union(origin):
|
||||
if not type_has_args(cls):
|
||||
with contextlib.suppress(TypeError):
|
||||
return issubclass(cls, class_or_tuple)
|
||||
|
||||
origin = get_origin(cls)
|
||||
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 origin_is_literal(origin):
|
||||
return all(
|
||||
is_none_type(value) or isinstance(value, class_or_tuple)
|
||||
for value in all_literal_values(cls)
|
||||
)
|
||||
# ensure generic List, Dict can be checked
|
||||
elif origin:
|
||||
# avoid class check error (typing.Final, typing.ClassVar, etc...)
|
||||
try:
|
||||
return issubclass(origin, class_or_tuple)
|
||||
except TypeError:
|
||||
return False
|
||||
elif isinstance(cls, TypeVar):
|
||||
if cls.__constraints__:
|
||||
return all(
|
||||
is_none_type(type_) or generic_check_issubclass(type_, class_or_tuple)
|
||||
for type_ in get_args(cls)
|
||||
for type_ in cls.__constraints__
|
||||
)
|
||||
elif origin_is_literal(origin):
|
||||
return all(
|
||||
is_none_type(value) or isinstance(value, class_or_tuple)
|
||||
for value in all_literal_values(cls)
|
||||
)
|
||||
# ensure generic List, Dict can be checked
|
||||
elif origin:
|
||||
# avoid class check error (typing.Final, typing.ClassVar, etc...)
|
||||
try:
|
||||
return issubclass(origin, class_or_tuple)
|
||||
except TypeError:
|
||||
return False
|
||||
elif isinstance(cls, TypeVar):
|
||||
if cls.__constraints__:
|
||||
return all(
|
||||
is_none_type(type_)
|
||||
or generic_check_issubclass(type_, class_or_tuple)
|
||||
for type_ in cls.__constraints__
|
||||
)
|
||||
elif cls.__bound__:
|
||||
return generic_check_issubclass(cls.__bound__, class_or_tuple)
|
||||
return False
|
||||
elif cls.__bound__:
|
||||
return generic_check_issubclass(cls.__bound__, class_or_tuple)
|
||||
return False
|
||||
|
||||
|
||||
def type_is_complex(type_: Type[Any]) -> bool:
|
||||
def type_is_complex(type_: type[Any]) -> bool:
|
||||
"""检查 type_ 是否是复杂类型"""
|
||||
origin = get_origin(type_)
|
||||
return _type_is_complex_inner(type_) or _type_is_complex_inner(origin)
|
||||
|
||||
|
||||
def _type_is_complex_inner(type_: Optional[Type[Any]]) -> bool:
|
||||
def _type_is_complex_inner(type_: Optional[type[Any]]) -> bool:
|
||||
if lenient_issubclass(type_, (str, bytes)):
|
||||
return False
|
||||
|
||||
@@ -200,7 +187,7 @@ def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]:
|
||||
|
||||
@asynccontextmanager
|
||||
async def run_sync_ctx_manager(
|
||||
cm: ContextManager[T],
|
||||
cm: AbstractContextManager[T],
|
||||
) -> AsyncGenerator[T, None]:
|
||||
"""一个用于包装 sync context manager 为 async context manager 的执行函数"""
|
||||
try:
|
||||
@@ -216,7 +203,7 @@ async def run_sync_ctx_manager(
|
||||
@overload
|
||||
async def run_coro_with_catch(
|
||||
coro: Coroutine[Any, Any, T],
|
||||
exc: Tuple[Type[Exception], ...],
|
||||
exc: tuple[type[Exception], ...],
|
||||
return_on_err: None = None,
|
||||
) -> Union[T, None]: ...
|
||||
|
||||
@@ -224,14 +211,14 @@ async def run_coro_with_catch(
|
||||
@overload
|
||||
async def run_coro_with_catch(
|
||||
coro: Coroutine[Any, Any, T],
|
||||
exc: Tuple[Type[Exception], ...],
|
||||
exc: tuple[type[Exception], ...],
|
||||
return_on_err: R,
|
||||
) -> Union[T, R]: ...
|
||||
|
||||
|
||||
async def run_coro_with_catch(
|
||||
coro: Coroutine[Any, Any, T],
|
||||
exc: Tuple[Type[Exception], ...],
|
||||
exc: tuple[type[Exception], ...],
|
||||
return_on_err: Optional[R] = None,
|
||||
) -> Optional[Union[T, R]]:
|
||||
"""运行协程并当遇到指定异常时返回指定值。
|
||||
@@ -289,7 +276,7 @@ class classproperty(Generic[T]):
|
||||
def __init__(self, func: Callable[[Any], T]) -> None:
|
||||
self.func = func
|
||||
|
||||
def __get__(self, instance: Any, owner: Optional[Type[Any]] = None) -> T:
|
||||
def __get__(self, instance: Any, owner: Optional[type[Any]] = None) -> T:
|
||||
return self.func(type(instance) if owner is None else owner)
|
||||
|
||||
|
||||
|
@@ -17,7 +17,7 @@ _✨ NoneBot 本地文档插件 ✨_
|
||||
<a href="https://pypi.python.org/pypi/nonebot-plugin-docs">
|
||||
<img src="https://img.shields.io/pypi/v/nonebot-plugin-docs.svg" alt="pypi">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/python-3.8+-blue.svg" alt="python">
|
||||
<img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="python">
|
||||
</p>
|
||||
|
||||
## 使用方式
|
||||
|
@@ -12,7 +12,7 @@ include = ["nonebot_plugin_docs/dist/**/*"]
|
||||
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
python = "^3.9"
|
||||
nonebot2 = "^2.0.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
1346
poetry.lock
generated
1346
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "nonebot2"
|
||||
version = "2.2.0"
|
||||
version = "2.3.2"
|
||||
description = "An asynchronous python bot framework."
|
||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||
license = "MIT"
|
||||
@@ -25,7 +25,7 @@ include = ["nonebot/py.typed"]
|
||||
"Funding" = "https://afdian.net/@nonebot"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
python = "^3.9"
|
||||
yarl = "^1.7.2"
|
||||
pygtrie = "^2.4.1"
|
||||
loguru = ">=0.6.0,<1.0.0"
|
||||
@@ -44,7 +44,7 @@ uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
|
||||
], optional = true }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = "^0.2.0"
|
||||
ruff = "^0.4.0"
|
||||
isort = "^5.10.1"
|
||||
black = "^24.0.0"
|
||||
nonemoji = "^0.1.2"
|
||||
@@ -66,12 +66,12 @@ all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "strict"
|
||||
addopts = "--cov=nonebot --cov-append --cov-report=term-missing"
|
||||
addopts = "--cov=nonebot --cov-report=term-missing"
|
||||
filterwarnings = ["error", "ignore::DeprecationWarning"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ["py38", "py39", "py310", "py311"]
|
||||
target-version = ["py39", "py310", "py311", "py312"]
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
'''
|
||||
@@ -87,18 +87,37 @@ extra_standard_library = ["typing_extensions"]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py38"
|
||||
target-version = "py39"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "W", "F", "UP", "C", "T", "PYI", "PT", "Q"]
|
||||
ignore = ["E402", "C901", "UP037"]
|
||||
select = [
|
||||
"F", # Pyflakes
|
||||
"W", # pycodestyle warnings
|
||||
"E", # pycodestyle errors
|
||||
"UP", # pyupgrade
|
||||
"ASYNC", # flake8-async
|
||||
"C4", # flake8-comprehensions
|
||||
"T10", # flake8-debugger
|
||||
"T20", # flake8-print
|
||||
"PYI", # flake8-pyi
|
||||
"PT", # flake8-pytest-style
|
||||
"Q", # flake8-quotes
|
||||
"RUF", # Ruff-specific rules
|
||||
]
|
||||
ignore = [
|
||||
"E402", # module-import-not-at-top-of-file
|
||||
"UP037", # quoted-annotation
|
||||
"RUF001", # ambiguous-unicode-character-string
|
||||
"RUF002", # ambiguous-unicode-character-docstring
|
||||
"RUF003", # ambiguous-unicode-character-comment
|
||||
]
|
||||
|
||||
[tool.ruff.lint.flake8-pytest-style]
|
||||
fixture-parentheses = false
|
||||
mark-parentheses = false
|
||||
|
||||
[tool.pyright]
|
||||
pythonVersion = "3.8"
|
||||
pythonVersion = "3.9"
|
||||
pythonPlatform = "All"
|
||||
defineConstant = { PYDANTIC_V2 = true }
|
||||
executionEnvironments = [
|
||||
@@ -108,7 +127,7 @@ executionEnvironments = [
|
||||
{ root = "./" },
|
||||
]
|
||||
|
||||
typeCheckingMode = "basic"
|
||||
typeCheckingMode = "standard"
|
||||
reportShadowedImports = false
|
||||
disableBytesTypePromotions = true
|
||||
|
||||
|
@@ -4,4 +4,4 @@
|
||||
cd "$(dirname "$0")/../tests"
|
||||
|
||||
# Run the tests
|
||||
pytest -n auto --cov-report xml $@
|
||||
pytest -n auto --cov-append --cov-report xml $@
|
||||
|
@@ -21,8 +21,6 @@ rules =
|
||||
"sys_platform != 'win32'": py-win32
|
||||
"sys_platform != 'linux'": py-linux
|
||||
"sys_platform != 'darwin'": py-darwin
|
||||
"sys_version_info < (3, 9)": py-gte-39
|
||||
"sys_version_info >= (3, 9)": py-lt-39
|
||||
"sys_version_info < (3, 11)": py-gte-311
|
||||
"sys_version_info >= (3, 11)": py-lt-311
|
||||
"package_version('pydantic') < (2,)": pydantic-v2
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import os
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Set, Generator
|
||||
from typing import TYPE_CHECKING
|
||||
from collections.abc import Generator
|
||||
|
||||
import pytest
|
||||
from nonebug import NONEBOT_INIT_KWARGS
|
||||
@@ -38,13 +39,13 @@ def load_driver(request: pytest.FixtureRequest) -> Driver:
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def load_plugin(nonebug_init: None) -> Set["Plugin"]:
|
||||
def load_plugin(nonebug_init: None) -> set["Plugin"]:
|
||||
# preload global plugins
|
||||
return nonebot.load_plugins(str(Path(__file__).parent / "plugins"))
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def load_builtin_plugin(nonebug_init: None) -> Set["Plugin"]:
|
||||
def load_builtin_plugin(nonebug_init: None) -> set["Plugin"]:
|
||||
# preload builtin plugins
|
||||
return nonebot.load_builtin_plugins("echo", "single_session")
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import json
|
||||
import base64
|
||||
import socket
|
||||
from typing import Dict, List, Union, TypeVar
|
||||
from typing import Union, TypeVar
|
||||
|
||||
from wsproto.events import Ping
|
||||
from werkzeug import Request, Response
|
||||
@@ -31,7 +31,7 @@ def json_safe(string, content_type="application/octet-stream") -> str:
|
||||
).decode("utf-8")
|
||||
|
||||
|
||||
def flattern(d: "MultiDict[K, V]") -> Dict[K, Union[V, List[V]]]:
|
||||
def flattern(d: "MultiDict[K, V]") -> dict[K, Union[V, list[V]]]:
|
||||
return {k: v[0] if len(v) == 1 else v for k, v in d.to_dict(flat=False).items()}
|
||||
|
||||
|
||||
|
15
tests/plugins/metadata_3.py
Normal file
15
tests/plugins/metadata_3.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="测试插件3",
|
||||
description="测试继承适配器, 使用内置适配器全名",
|
||||
usage="无法使用",
|
||||
type="application",
|
||||
homepage="https://nonebot.dev",
|
||||
supported_adapters={
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"nonebot.adapters.onebot.v12",
|
||||
"~qq",
|
||||
},
|
||||
extra={"author": "NoneBot"},
|
||||
)
|
@@ -8,5 +8,5 @@ manager = PluginManager(
|
||||
_managers.append(manager)
|
||||
|
||||
# test load nested plugin with require
|
||||
manager.load_plugin("nested_subplugin")
|
||||
manager.load_plugin("nested_subplugin2")
|
||||
manager.load_plugin("plugins.nested.plugins.nested_subplugin")
|
||||
manager.load_plugin("nested:nested_subplugin2")
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing_extensions import Annotated
|
||||
from typing import Annotated
|
||||
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import Arg, ArgStr, ArgPlainText
|
||||
|
@@ -7,6 +7,10 @@ async def get_bot(b: Bot) -> Bot:
|
||||
return b
|
||||
|
||||
|
||||
async def postpone_bot(b: "Bot") -> Bot:
|
||||
return b
|
||||
|
||||
|
||||
async def legacy_bot(bot):
|
||||
return bot
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from typing import Annotated
|
||||
from dataclasses import dataclass
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
@@ -32,8 +32,8 @@ async def gen_async():
|
||||
|
||||
@dataclass
|
||||
class ClassDependency:
|
||||
x: int = Depends(gen_sync)
|
||||
y: int = Depends(gen_async)
|
||||
x: int = Depends(gen_sync) # noqa: RUF009
|
||||
y: int = Depends(gen_async) # noqa: RUF009
|
||||
|
||||
|
||||
class FooBot(Bot): ...
|
||||
|
@@ -8,6 +8,10 @@ async def event(e: Event) -> Event:
|
||||
return e
|
||||
|
||||
|
||||
async def postpone_event(e: "Event") -> Event:
|
||||
return e
|
||||
|
||||
|
||||
async def legacy_event(event):
|
||||
return event
|
||||
|
||||
|
@@ -9,6 +9,10 @@ async def matcher(m: Matcher) -> Matcher:
|
||||
return m
|
||||
|
||||
|
||||
async def postpone_matcher(m: "Matcher") -> Matcher:
|
||||
return m
|
||||
|
||||
|
||||
async def legacy_matcher(matcher):
|
||||
return matcher
|
||||
|
||||
@@ -27,7 +31,7 @@ class BarMatcher(Matcher): ...
|
||||
|
||||
|
||||
async def union_matcher(
|
||||
m: Union[FooMatcher, BarMatcher]
|
||||
m: Union[FooMatcher, BarMatcher],
|
||||
) -> Union[FooMatcher, BarMatcher]:
|
||||
return m
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import List, Match, Tuple
|
||||
from re import Match
|
||||
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters import Message
|
||||
@@ -25,6 +25,10 @@ async def state(x: T_State) -> T_State:
|
||||
return x
|
||||
|
||||
|
||||
async def postpone_state(x: "T_State") -> T_State:
|
||||
return x
|
||||
|
||||
|
||||
async def legacy_state(state):
|
||||
return state
|
||||
|
||||
@@ -32,7 +36,7 @@ async def legacy_state(state):
|
||||
async def not_legacy_state(state: int): ...
|
||||
|
||||
|
||||
async def command(cmd: Tuple[str, ...] = Command()) -> Tuple[str, ...]:
|
||||
async def command(cmd: tuple[str, ...] = Command()) -> tuple[str, ...]:
|
||||
return cmd
|
||||
|
||||
|
||||
@@ -59,8 +63,8 @@ async def shell_command_args(
|
||||
|
||||
|
||||
async def shell_command_argv(
|
||||
shell_command_argv: List[str] = ShellCommandArgv(),
|
||||
) -> List[str]:
|
||||
shell_command_argv: list[str] = ShellCommandArgv(),
|
||||
) -> list[str]:
|
||||
return shell_command_argv
|
||||
|
||||
|
||||
@@ -68,7 +72,7 @@ async def regex_dict(regex_dict: dict = RegexDict()) -> dict:
|
||||
return regex_dict
|
||||
|
||||
|
||||
async def regex_group(regex_group: Tuple = RegexGroup()) -> Tuple:
|
||||
async def regex_group(regex_group: tuple = RegexGroup()) -> tuple:
|
||||
return regex_group
|
||||
|
||||
|
||||
@@ -80,8 +84,8 @@ async def regex_str(
|
||||
entire: str = RegexStr(),
|
||||
type_: str = RegexStr("type"),
|
||||
second: str = RegexStr(2),
|
||||
groups: Tuple[str, ...] = RegexStr(1, "arg"),
|
||||
) -> Tuple[str, str, str, Tuple[str, ...]]:
|
||||
groups: tuple[str, ...] = RegexStr(1, "arg"),
|
||||
) -> tuple[str, str, str, tuple[str, ...]]:
|
||||
return entire, type_, second, groups
|
||||
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
from typing import Type
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from nonebot.adapters import Event
|
||||
@@ -52,7 +51,7 @@ matcher_on = on(
|
||||
)
|
||||
|
||||
|
||||
def matcher_on_factory() -> Type[Matcher]:
|
||||
def matcher_on_factory() -> type[Matcher]:
|
||||
return on(
|
||||
"test",
|
||||
rule=rule,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
@@ -27,7 +27,7 @@ async def test_bot_call_api(app: App):
|
||||
async def test_bot_calling_api_hook_simple(app: App):
|
||||
runned: bool = False
|
||||
|
||||
async def calling_api_hook(bot: Bot, api: str, data: Dict[str, Any]):
|
||||
async def calling_api_hook(bot: Bot, api: str, data: dict[str, Any]):
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
@@ -53,7 +53,7 @@ async def test_bot_calling_api_hook_simple(app: App):
|
||||
async def test_bot_calling_api_hook_mock(app: App):
|
||||
runned: bool = False
|
||||
|
||||
async def calling_api_hook(bot: Bot, api: str, data: Dict[str, Any]):
|
||||
async def calling_api_hook(bot: Bot, api: str, data: dict[str, Any]):
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
@@ -84,7 +84,7 @@ async def test_bot_called_api_hook_simple(app: App):
|
||||
bot: Bot,
|
||||
exception: Optional[Exception],
|
||||
api: str,
|
||||
data: Dict[str, Any],
|
||||
data: dict[str, Any],
|
||||
result: Any,
|
||||
):
|
||||
nonlocal runned
|
||||
@@ -116,7 +116,7 @@ async def test_bot_called_api_hook_mock(app: App):
|
||||
bot: Bot,
|
||||
exception: Optional[Exception],
|
||||
api: str,
|
||||
data: Dict[str, Any],
|
||||
data: dict[str, Any],
|
||||
result: Any,
|
||||
):
|
||||
nonlocal runned
|
||||
|
@@ -192,6 +192,11 @@ def test_message_contains():
|
||||
assert message.has("foo") is False
|
||||
assert "foo" not in message
|
||||
|
||||
assert not bool(FakeMessageSegment.text(""))
|
||||
msg_with_empty_seg = FakeMessage([FakeMessageSegment.text("")])
|
||||
assert msg_with_empty_seg.has("text") is True
|
||||
assert "text" in msg_with_empty_seg
|
||||
|
||||
|
||||
def test_message_only():
|
||||
message = FakeMessage(
|
||||
|
@@ -1,16 +1,18 @@
|
||||
from typing import Any
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional, Annotated
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from nonebot.compat import (
|
||||
DEFAULT_CONFIG,
|
||||
Required,
|
||||
FieldInfo,
|
||||
TypeAdapter,
|
||||
PydanticUndefined,
|
||||
model_dump,
|
||||
custom_validation,
|
||||
type_validate_json,
|
||||
type_validate_python,
|
||||
)
|
||||
|
||||
@@ -30,6 +32,21 @@ async def test_field_info():
|
||||
assert FieldInfo(test="test").extra["test"] == "test"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_type_adapter():
|
||||
t = TypeAdapter(Annotated[int, FieldInfo(ge=1)])
|
||||
|
||||
assert t.validate_python(2) == 2
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
t.validate_python(0)
|
||||
|
||||
assert t.validate_json("2") == 2
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
t.validate_json("0")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_model_dump():
|
||||
class TestModel(BaseModel):
|
||||
@@ -66,3 +83,26 @@ async def test_custom_validation():
|
||||
|
||||
assert type_validate_python(TestModel, {"test": 1}) == TestModel(test=1)
|
||||
assert called == [1, 2]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_json():
|
||||
class TestModel(BaseModel):
|
||||
test1: int
|
||||
test2: str
|
||||
test3: bool
|
||||
test4: dict
|
||||
test5: list
|
||||
test6: Optional[int]
|
||||
|
||||
assert type_validate_json(
|
||||
TestModel,
|
||||
"{"
|
||||
' "test1": 1,'
|
||||
' "test2": "2",'
|
||||
' "test3": true,'
|
||||
' "test4": {},'
|
||||
' "test5": [],'
|
||||
' "test6": null'
|
||||
"}",
|
||||
) == TestModel(test1=1, test2="2", test3=True, test4={}, test5=[], test6=None)
|
||||
|
@@ -1,9 +1,10 @@
|
||||
from typing import List, Union, Optional
|
||||
from typing import TYPE_CHECKING, Union, Optional
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
from nonebot.config import DOTENV_TYPE, BaseSettings, SettingsError
|
||||
from nonebot.compat import PYDANTIC_V2
|
||||
from nonebot.config import DOTENV_TYPE, BaseSettings, SettingsError, SettingsConfig
|
||||
|
||||
|
||||
class Simple(BaseModel):
|
||||
@@ -14,24 +15,39 @@ class Simple(BaseModel):
|
||||
|
||||
|
||||
class Example(BaseSettings):
|
||||
_env_file: Optional[DOTENV_TYPE] = ".env", ".env.example"
|
||||
_env_nested_delimiter: Optional[str] = "__"
|
||||
if TYPE_CHECKING:
|
||||
_env_file: Optional[DOTENV_TYPE] = ".env", ".env.example"
|
||||
_env_nested_delimiter: Optional[str] = "__"
|
||||
|
||||
if PYDANTIC_V2:
|
||||
model_config = SettingsConfig(
|
||||
env_file=(".env", ".env.example"), env_nested_delimiter="__"
|
||||
)
|
||||
else:
|
||||
|
||||
class Config( # pyright: ignore[reportIncompatibleVariableOverride]
|
||||
SettingsConfig
|
||||
):
|
||||
env_file = ".env", ".env.example"
|
||||
env_nested_delimiter = "__"
|
||||
|
||||
simple: str = ""
|
||||
complex: List[int] = [1]
|
||||
complex_none: Optional[List[int]] = None
|
||||
complex_union: Union[int, List[int]] = 1
|
||||
complex: list[int] = Field(default=[1])
|
||||
complex_none: Optional[list[int]] = None
|
||||
complex_union: Union[int, list[int]] = 1
|
||||
nested: Simple = Simple()
|
||||
nested_inner: Simple = Simple()
|
||||
|
||||
class Config:
|
||||
env_file = ".env", ".env.example"
|
||||
env_nested_delimiter = "__"
|
||||
|
||||
|
||||
class ExampleWithoutDelimiter(Example):
|
||||
class Config:
|
||||
env_nested_delimiter = None
|
||||
if PYDANTIC_V2:
|
||||
model_config = SettingsConfig(env_nested_delimiter=None)
|
||||
else:
|
||||
|
||||
class Config( # pyright: ignore[reportIncompatibleVariableOverride]
|
||||
SettingsConfig
|
||||
):
|
||||
env_nested_delimiter = None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
import asyncio
|
||||
from typing import Any, Set, Optional
|
||||
from typing import Any, Optional
|
||||
from http.cookies import SimpleCookie
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
@@ -306,6 +307,121 @@ async def test_http_client(driver: Driver, server_url: URL):
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
[
|
||||
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
|
||||
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
async def test_http_client_session(driver: Driver, server_url: URL):
|
||||
assert isinstance(driver, HTTPClientMixin)
|
||||
|
||||
session = driver.get_session(
|
||||
params={"session": "test"},
|
||||
headers={"X-Session": "test"},
|
||||
cookies={"session": "test"},
|
||||
)
|
||||
request = Request("GET", server_url)
|
||||
with pytest.raises(RuntimeError):
|
||||
await session.request(request)
|
||||
|
||||
with pytest.raises(RuntimeError): # noqa: PT012
|
||||
async with session:
|
||||
async with session:
|
||||
...
|
||||
|
||||
async with session as session:
|
||||
# simple post with query, headers, cookies and content
|
||||
request = Request(
|
||||
"POST",
|
||||
server_url,
|
||||
params={"param": "test"},
|
||||
headers={"X-Test": "test"},
|
||||
cookies={"cookie": "test"},
|
||||
content="test",
|
||||
)
|
||||
response = await session.request(request)
|
||||
assert response.status_code == 200
|
||||
assert response.content
|
||||
data = json.loads(response.content)
|
||||
assert data["method"] == "POST"
|
||||
assert data["args"] == {"session": "test", "param": "test"}
|
||||
assert data["headers"].get("X-Session") == "test"
|
||||
assert data["headers"].get("X-Test") == "test"
|
||||
assert {
|
||||
key: cookie.value
|
||||
for key, cookie in SimpleCookie(data["headers"].get("Cookie")).items()
|
||||
} == {
|
||||
"session": "test",
|
||||
"cookie": "test",
|
||||
}
|
||||
assert data["data"] == "test"
|
||||
|
||||
# post with data body
|
||||
request = Request("POST", server_url, data={"form": "test"})
|
||||
response = await session.request(request)
|
||||
assert response.status_code == 200
|
||||
assert response.content
|
||||
data = json.loads(response.content)
|
||||
assert data["method"] == "POST"
|
||||
assert data["args"] == {"session": "test"}
|
||||
assert data["headers"].get("X-Session") == "test"
|
||||
assert {
|
||||
key: cookie.value
|
||||
for key, cookie in SimpleCookie(data["headers"].get("Cookie")).items()
|
||||
} == {"session": "test"}
|
||||
assert data["form"] == {"form": "test"}
|
||||
|
||||
# post with json body
|
||||
request = Request("POST", server_url, json={"json": "test"})
|
||||
response = await session.request(request)
|
||||
assert response.status_code == 200
|
||||
assert response.content
|
||||
data = json.loads(response.content)
|
||||
assert data["method"] == "POST"
|
||||
assert data["args"] == {"session": "test"}
|
||||
assert data["headers"].get("X-Session") == "test"
|
||||
assert {
|
||||
key: cookie.value
|
||||
for key, cookie in SimpleCookie(data["headers"].get("Cookie")).items()
|
||||
} == {"session": "test"}
|
||||
assert data["json"] == {"json": "test"}
|
||||
|
||||
# post with files and form data
|
||||
request = Request(
|
||||
"POST",
|
||||
server_url,
|
||||
data={"form": "test"},
|
||||
files=[
|
||||
("test1", b"test"),
|
||||
("test2", ("test.txt", b"test")),
|
||||
("test3", ("test.txt", b"test", "text/plain")),
|
||||
],
|
||||
)
|
||||
response = await session.request(request)
|
||||
assert response.status_code == 200
|
||||
assert response.content
|
||||
data = json.loads(response.content)
|
||||
assert data["method"] == "POST"
|
||||
assert data["args"] == {"session": "test"}
|
||||
assert data["headers"].get("X-Session") == "test"
|
||||
assert {
|
||||
key: cookie.value
|
||||
for key, cookie in SimpleCookie(data["headers"].get("Cookie")).items()
|
||||
} == {"session": "test"}
|
||||
assert data["form"] == {"form": "test"}
|
||||
assert data["files"] == {
|
||||
"test1": "test",
|
||||
"test2": "test",
|
||||
"test3": "test",
|
||||
}, "file parsing error"
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"driver",
|
||||
@@ -363,8 +479,8 @@ async def test_combine_driver(driver: Driver, driver_type: str):
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot_connect_hook(app: App, driver: Driver):
|
||||
with pytest.MonkeyPatch.context() as m:
|
||||
conn_hooks: Set[Dependent[Any]] = set()
|
||||
disconn_hooks: Set[Dependent[Any]] = set()
|
||||
conn_hooks: set[Dependent[Any]] = set()
|
||||
disconn_hooks: set[Dependent[Any]] = set()
|
||||
m.setattr(Driver, "_bot_connection_hook", conn_hooks)
|
||||
m.setattr(Driver, "_bot_disconnection_hook", disconn_hooks)
|
||||
|
||||
@@ -409,9 +525,9 @@ async def test_bot_connect_hook(app: App, driver: Driver):
|
||||
|
||||
disconn_should_be_called = True
|
||||
|
||||
if conn_hook not in {hook.call for hook in conn_hooks}:
|
||||
if conn_hook not in {hook.call for hook in conn_hooks}: # type: ignore
|
||||
pytest.fail("on_bot_connect hook not registered")
|
||||
if disconn_hook not in {hook.call for hook in disconn_hooks}:
|
||||
if disconn_hook not in {hook.call for hook in disconn_hooks}: # type: ignore
|
||||
pytest.fail("on_bot_disconnect hook not registered")
|
||||
|
||||
async with app.test_api() as ctx:
|
||||
|
@@ -29,8 +29,10 @@ async def test_matcher_info(app: App):
|
||||
assert matcher.module is sys.modules["plugins.matcher.matcher_info"]
|
||||
assert matcher.module_name == "plugins.matcher.matcher_info"
|
||||
|
||||
assert matcher._source.plugin_id == "matcher:matcher_info"
|
||||
assert matcher._source.plugin_name == "matcher_info"
|
||||
assert matcher.plugin is get_plugin("matcher_info")
|
||||
assert matcher.plugin is get_plugin("matcher:matcher_info")
|
||||
assert matcher.plugin_id == "matcher:matcher_info"
|
||||
assert matcher.plugin_name == "matcher_info"
|
||||
|
||||
assert (
|
||||
@@ -244,7 +246,7 @@ async def test_default_permission_updater(app: App):
|
||||
matcher = test_permission_updater()
|
||||
new_perm = await matcher.update_permission(bot, event)
|
||||
assert len(new_perm.checkers) == 1
|
||||
checker = list(new_perm.checkers)[0].call
|
||||
checker = next(iter(new_perm.checkers)).call
|
||||
assert isinstance(checker, User)
|
||||
assert checker.users == ("test",)
|
||||
assert checker.perm is default_permission
|
||||
@@ -258,7 +260,7 @@ async def test_user_permission_updater(app: App):
|
||||
)
|
||||
|
||||
event = make_fake_event(_session_id="test")()
|
||||
user_permission = list(test_user_permission_updater.permission.checkers)[0].call
|
||||
user_permission = next(iter(test_user_permission_updater.permission.checkers)).call
|
||||
assert isinstance(user_permission, User)
|
||||
assert user_permission.perm is default_permission
|
||||
async with app.test_api() as ctx:
|
||||
@@ -266,7 +268,7 @@ async def test_user_permission_updater(app: App):
|
||||
matcher = test_user_permission_updater()
|
||||
new_perm = await matcher.update_permission(bot, event)
|
||||
assert len(new_perm.checkers) == 1
|
||||
checker = list(new_perm.checkers)[0].call
|
||||
checker = next(iter(new_perm.checkers)).call
|
||||
assert isinstance(checker, User)
|
||||
assert checker.users == ("test",)
|
||||
assert checker.perm is default_permission
|
||||
|
@@ -129,6 +129,7 @@ async def test_bot(app: App):
|
||||
union_bot,
|
||||
legacy_bot,
|
||||
generic_bot,
|
||||
postpone_bot,
|
||||
not_legacy_bot,
|
||||
generic_bot_none,
|
||||
)
|
||||
@@ -138,6 +139,11 @@ async def test_bot(app: App):
|
||||
ctx.pass_params(bot=bot)
|
||||
ctx.should_return(bot)
|
||||
|
||||
async with app.test_dependent(postpone_bot, allow_types=[BotParam]) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
ctx.pass_params(bot=bot)
|
||||
ctx.should_return(bot)
|
||||
|
||||
async with app.test_dependent(legacy_bot, allow_types=[BotParam]) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
ctx.pass_params(bot=bot)
|
||||
@@ -188,6 +194,7 @@ async def test_event(app: App):
|
||||
legacy_event,
|
||||
event_message,
|
||||
generic_event,
|
||||
postpone_event,
|
||||
event_plain_text,
|
||||
not_legacy_event,
|
||||
generic_event_none,
|
||||
@@ -201,6 +208,10 @@ async def test_event(app: App):
|
||||
ctx.pass_params(event=fake_event)
|
||||
ctx.should_return(fake_event)
|
||||
|
||||
async with app.test_dependent(postpone_event, allow_types=[EventParam]) as ctx:
|
||||
ctx.pass_params(event=fake_event)
|
||||
ctx.should_return(fake_event)
|
||||
|
||||
async with app.test_dependent(legacy_event, allow_types=[EventParam]) as ctx:
|
||||
ctx.pass_params(event=fake_event)
|
||||
ctx.should_return(fake_event)
|
||||
@@ -212,7 +223,7 @@ async def test_event(app: App):
|
||||
ctx.pass_params(event=fake_fooevent)
|
||||
ctx.should_return(fake_fooevent)
|
||||
|
||||
with pytest.raises(TypeMisMatch): # noqa: PT012
|
||||
with pytest.raises(TypeMisMatch):
|
||||
async with app.test_dependent(sub_event, allow_types=[EventParam]) as ctx:
|
||||
ctx.pass_params(event=fake_event)
|
||||
|
||||
@@ -273,6 +284,7 @@ async def test_state(app: App):
|
||||
legacy_state,
|
||||
command_start,
|
||||
regex_matched,
|
||||
postpone_state,
|
||||
not_legacy_state,
|
||||
command_whitespace,
|
||||
shell_command_args,
|
||||
@@ -302,6 +314,10 @@ async def test_state(app: App):
|
||||
ctx.pass_params(state=fake_state)
|
||||
ctx.should_return(fake_state)
|
||||
|
||||
async with app.test_dependent(postpone_state, allow_types=[StateParam]) as ctx:
|
||||
ctx.pass_params(state=fake_state)
|
||||
ctx.should_return(fake_state)
|
||||
|
||||
async with app.test_dependent(legacy_state, allow_types=[StateParam]) as ctx:
|
||||
ctx.pass_params(state=fake_state)
|
||||
ctx.should_return(fake_state)
|
||||
@@ -414,6 +430,7 @@ async def test_matcher(app: App):
|
||||
union_matcher,
|
||||
legacy_matcher,
|
||||
generic_matcher,
|
||||
postpone_matcher,
|
||||
not_legacy_matcher,
|
||||
generic_matcher_none,
|
||||
)
|
||||
@@ -425,6 +442,10 @@ async def test_matcher(app: App):
|
||||
ctx.pass_params(matcher=fake_matcher)
|
||||
ctx.should_return(fake_matcher)
|
||||
|
||||
async with app.test_dependent(postpone_matcher, allow_types=[MatcherParam]) as ctx:
|
||||
ctx.pass_params(matcher=fake_matcher)
|
||||
ctx.should_return(fake_matcher)
|
||||
|
||||
async with app.test_dependent(legacy_matcher, allow_types=[MatcherParam]) as ctx:
|
||||
ctx.pass_params(matcher=fake_matcher)
|
||||
ctx.should_return(fake_matcher)
|
||||
@@ -436,7 +457,7 @@ async def test_matcher(app: App):
|
||||
ctx.pass_params(matcher=foo_matcher)
|
||||
ctx.should_return(foo_matcher)
|
||||
|
||||
with pytest.raises(TypeMisMatch): # noqa: PT012
|
||||
with pytest.raises(TypeMisMatch):
|
||||
async with app.test_dependent(sub_matcher, allow_types=[MatcherParam]) as ctx:
|
||||
ctx.pass_params(matcher=fake_matcher)
|
||||
|
||||
@@ -553,7 +574,7 @@ async def test_default(app: App):
|
||||
async def test_priority():
|
||||
from plugins.param.priority import complex_priority
|
||||
|
||||
dependent = Dependent.parse(
|
||||
dependent = Dependent[None].parse(
|
||||
call=complex_priority,
|
||||
allow_types=[
|
||||
DependParam,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import Tuple, Optional
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
@@ -57,7 +57,7 @@ async def test_permission(app: App):
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("type", "expected"), [("message", True), ("notice", False)])
|
||||
async def test_message(type: str, expected: bool):
|
||||
dependent = list(MESSAGE.checkers)[0]
|
||||
dependent = next(iter(MESSAGE.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, Message)
|
||||
@@ -69,7 +69,7 @@ async def test_message(type: str, expected: bool):
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("notice", True)])
|
||||
async def test_notice(type: str, expected: bool):
|
||||
dependent = list(NOTICE.checkers)[0]
|
||||
dependent = next(iter(NOTICE.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, Notice)
|
||||
@@ -81,7 +81,7 @@ async def test_notice(type: str, expected: bool):
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(("type", "expected"), [("message", False), ("request", True)])
|
||||
async def test_request(type: str, expected: bool):
|
||||
dependent = list(REQUEST.checkers)[0]
|
||||
dependent = next(iter(REQUEST.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, Request)
|
||||
@@ -95,7 +95,7 @@ async def test_request(type: str, expected: bool):
|
||||
("type", "expected"), [("message", False), ("meta_event", True)]
|
||||
)
|
||||
async def test_metaevent(type: str, expected: bool):
|
||||
dependent = list(METAEVENT.checkers)[0]
|
||||
dependent = next(iter(METAEVENT.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, MetaEvent)
|
||||
@@ -116,7 +116,7 @@ async def test_metaevent(type: str, expected: bool):
|
||||
],
|
||||
)
|
||||
async def test_superuser(app: App, type: str, user_id: str, expected: bool):
|
||||
dependent = list(SUPERUSER.checkers)[0]
|
||||
dependent = next(iter(SUPERUSER.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, SuperUser)
|
||||
@@ -138,9 +138,9 @@ async def test_superuser(app: App, type: str, user_id: str, expected: bool):
|
||||
],
|
||||
)
|
||||
async def test_user(
|
||||
app: App, session_ids: Tuple[str, ...], session_id: Optional[str], expected: bool
|
||||
app: App, session_ids: tuple[str, ...], session_id: Optional[str], expected: bool
|
||||
):
|
||||
dependent = list(USER(*session_ids).checkers)[0]
|
||||
dependent = next(iter(USER(*session_ids).checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, User)
|
||||
|
@@ -10,18 +10,43 @@ async def test_get_plugin():
|
||||
# check simple plugin
|
||||
plugin = nonebot.get_plugin("export")
|
||||
assert plugin
|
||||
assert plugin.id_ == "export"
|
||||
assert plugin.name == "export"
|
||||
assert plugin.module_name == "plugins.export"
|
||||
|
||||
# check sub plugin
|
||||
plugin = nonebot.get_plugin("nested_subplugin")
|
||||
plugin = nonebot.get_plugin("nested:nested_subplugin")
|
||||
assert plugin
|
||||
assert plugin.id_ == "nested:nested_subplugin"
|
||||
assert plugin.name == "nested_subplugin"
|
||||
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
|
||||
|
||||
# check get plugin by module name
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_plugin_by_module_name():
|
||||
# check get plugin by exact module name
|
||||
plugin = nonebot.get_plugin_by_module_name("plugins.nested")
|
||||
assert plugin
|
||||
assert plugin.id_ == "nested"
|
||||
assert plugin.name == "nested"
|
||||
assert plugin.module_name == "plugins.nested"
|
||||
|
||||
# check get plugin by sub module name
|
||||
plugin = nonebot.get_plugin_by_module_name("plugins.nested.utils")
|
||||
assert plugin
|
||||
assert plugin.id_ == "nested"
|
||||
assert plugin.name == "nested"
|
||||
assert plugin.module_name == "plugins.nested"
|
||||
|
||||
# check get plugin by sub plugin exact module name
|
||||
plugin = nonebot.get_plugin_by_module_name(
|
||||
"plugins.nested.plugins.nested_subplugin"
|
||||
)
|
||||
assert plugin
|
||||
assert plugin.id_ == "nested:nested_subplugin"
|
||||
assert plugin.name == "nested_subplugin"
|
||||
assert plugin.module_name == "plugins.nested.plugins.nested_subplugin"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_available_plugin():
|
||||
@@ -31,8 +56,8 @@ async def test_get_available_plugin():
|
||||
_managers.append(PluginManager(["plugins.export", "plugin.require"]))
|
||||
|
||||
# check get available plugins
|
||||
plugin_names = nonebot.get_available_plugin_names()
|
||||
assert plugin_names == {"export", "require"}
|
||||
plugin_ids = nonebot.get_available_plugin_names()
|
||||
assert plugin_ids == {"export", "require"}
|
||||
finally:
|
||||
_managers.clear()
|
||||
_managers.extend(old_managers)
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import sys
|
||||
from typing import Set
|
||||
from pathlib import Path
|
||||
from dataclasses import asdict
|
||||
|
||||
@@ -22,7 +21,7 @@ async def test_load_plugin():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_plugins(load_plugin: Set[Plugin], load_builtin_plugin: Set[Plugin]):
|
||||
async def test_load_plugins(load_plugin: set[Plugin], load_builtin_plugin: set[Plugin]):
|
||||
loaded_plugins = {
|
||||
plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
|
||||
}
|
||||
@@ -30,12 +29,13 @@ async def test_load_plugins(load_plugin: Set[Plugin], load_builtin_plugin: Set[P
|
||||
|
||||
# check simple plugin
|
||||
assert "plugins.export" in sys.modules
|
||||
assert "plugin._hidden" not in sys.modules
|
||||
|
||||
# check sub plugin
|
||||
plugin = nonebot.get_plugin("nested_subplugin")
|
||||
plugin = nonebot.get_plugin("nested:nested_subplugin")
|
||||
assert plugin
|
||||
assert "plugins.nested.plugins.nested_subplugin" in sys.modules
|
||||
assert plugin.parent_plugin == nonebot.get_plugin("nested")
|
||||
assert plugin.parent_plugin is nonebot.get_plugin("nested")
|
||||
|
||||
# check load again
|
||||
with pytest.raises(RuntimeError):
|
||||
@@ -47,8 +47,8 @@ async def test_load_plugins(load_plugin: Set[Plugin], load_builtin_plugin: Set[P
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_nested_plugin():
|
||||
parent_plugin = nonebot.get_plugin("nested")
|
||||
sub_plugin = nonebot.get_plugin("nested_subplugin")
|
||||
sub_plugin2 = nonebot.get_plugin("nested_subplugin2")
|
||||
sub_plugin = nonebot.get_plugin("nested:nested_subplugin")
|
||||
sub_plugin2 = nonebot.get_plugin("nested:nested_subplugin2")
|
||||
assert parent_plugin
|
||||
assert sub_plugin
|
||||
assert sub_plugin2
|
||||
@@ -90,12 +90,16 @@ async def test_require_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||
|
||||
monkeypatch.setattr("nonebot.plugin.load._find_manager_by_name", _patched_find)
|
||||
|
||||
# require use module name
|
||||
nonebot.require("plugins.export")
|
||||
# require use plugin id
|
||||
nonebot.require("export")
|
||||
nonebot.require("nested:nested_subplugin")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||
m = PluginManager(["dynamic.require_not_loaded"])
|
||||
m = PluginManager(["dynamic.require_not_loaded"], ["dynamic/require_not_loaded/"])
|
||||
_managers.append(m)
|
||||
num_managers = len(_managers)
|
||||
|
||||
@@ -107,7 +111,11 @@ async def test_require_not_loaded(monkeypatch: pytest.MonkeyPatch):
|
||||
|
||||
monkeypatch.setattr(PluginManager, "load_plugin", _patched_load)
|
||||
|
||||
# require standalone plugin
|
||||
nonebot.require("dynamic.require_not_loaded")
|
||||
# require searched plugin
|
||||
nonebot.require("dynamic.require_not_loaded.subplugin1")
|
||||
nonebot.require("require_not_loaded:subplugin2")
|
||||
|
||||
assert len(_managers) == num_managers
|
||||
|
||||
@@ -150,32 +158,82 @@ async def test_plugin_metadata():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_inherit_supported_adapters():
|
||||
async def test_inherit_supported_adapters_not_found():
|
||||
with pytest.raises(RuntimeError):
|
||||
inherit_supported_adapters("some_plugin_not_exist")
|
||||
|
||||
with pytest.raises(ValueError, match="has no metadata!"):
|
||||
inherit_supported_adapters("export")
|
||||
|
||||
echo = nonebot.get_plugin("echo")
|
||||
assert echo
|
||||
assert echo.metadata
|
||||
assert inherit_supported_adapters("echo") is None
|
||||
|
||||
plugin_1 = nonebot.get_plugin("metadata")
|
||||
assert plugin_1
|
||||
assert plugin_1.metadata
|
||||
assert inherit_supported_adapters("metadata") == {
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"plugins.metadata:FakeAdapter",
|
||||
}
|
||||
|
||||
plugin_2 = nonebot.get_plugin("metadata_2")
|
||||
assert plugin_2
|
||||
assert plugin_2.metadata
|
||||
assert inherit_supported_adapters("metadata", "metadata_2") == {
|
||||
"nonebot.adapters.onebot.v11"
|
||||
}
|
||||
assert inherit_supported_adapters("metadata", "echo", "metadata_2") == {
|
||||
"nonebot.adapters.onebot.v11"
|
||||
}
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("inherit_plugins", "expected"),
|
||||
[
|
||||
(("echo",), None),
|
||||
(
|
||||
("metadata",),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"plugins.metadata:FakeAdapter",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata_2",),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"nonebot.adapters.onebot.v12",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata_3",),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"nonebot.adapters.onebot.v12",
|
||||
"nonebot.adapters.qq",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata", "metadata_2"),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata", "metadata_3"),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata_2", "metadata_3"),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"nonebot.adapters.onebot.v12",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata", "metadata_2", "metadata_3"),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata", "echo"),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
"plugins.metadata:FakeAdapter",
|
||||
},
|
||||
),
|
||||
(
|
||||
("metadata", "metadata_2", "echo"),
|
||||
{
|
||||
"nonebot.adapters.onebot.v11",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_inherit_supported_adapters_combine(
|
||||
inherit_plugins: tuple[str], expected: set[str]
|
||||
):
|
||||
assert inherit_supported_adapters(*inherit_plugins) == expected
|
||||
|
@@ -1,11 +1,17 @@
|
||||
import pytest
|
||||
|
||||
from nonebot.plugin import PluginManager
|
||||
from nonebot.plugin import PluginManager, _managers
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_plugin_name():
|
||||
m = PluginManager(plugins=["dynamic.manager"])
|
||||
_managers.append(m)
|
||||
|
||||
# load by plugin id
|
||||
module1 = m.load_plugin("manager")
|
||||
# load by module name
|
||||
module2 = m.load_plugin("dynamic.manager")
|
||||
assert module1
|
||||
assert module2
|
||||
assert module1 is module2
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import Type, Callable, Optional
|
||||
from typing import Callable, Optional
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -104,7 +104,7 @@ from nonebot.rule import (
|
||||
)
|
||||
async def test_on(
|
||||
matcher_name: str,
|
||||
pre_rule_factory: Optional[Callable[[Type[Event]], T_RuleChecker]],
|
||||
pre_rule_factory: Optional[Callable[[type[Event]], T_RuleChecker]],
|
||||
has_permission: bool,
|
||||
):
|
||||
import plugins.plugin.matchers as module
|
||||
@@ -145,6 +145,7 @@ async def test_on(
|
||||
assert matcher.plugin is plugin
|
||||
assert matcher in plugin.matcher
|
||||
assert matcher.module is module
|
||||
assert matcher.plugin_id == "plugin"
|
||||
assert matcher.plugin_name == "plugin"
|
||||
assert matcher.module_name == "plugins.plugin.matchers"
|
||||
|
||||
@@ -163,6 +164,7 @@ async def test_runtime_on():
|
||||
assert matcher.plugin is plugin
|
||||
assert matcher not in plugin.matcher
|
||||
assert matcher.module is module
|
||||
assert matcher.plugin_id == "plugin"
|
||||
assert matcher.plugin_name == "plugin"
|
||||
assert matcher.module_name == "plugins.plugin.matchers"
|
||||
finally:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import re
|
||||
import sys
|
||||
from typing import Match, Tuple, Union, Optional
|
||||
from re import Match
|
||||
from typing import Union, Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
@@ -163,14 +163,14 @@ async def test_trie(app: App):
|
||||
],
|
||||
)
|
||||
async def test_startswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
msg: Union[str, tuple[str, ...]],
|
||||
ignorecase: bool,
|
||||
type: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
test_startswith = startswith(msg, ignorecase)
|
||||
dependent = list(test_startswith.checkers)[0]
|
||||
dependent = next(iter(test_startswith.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
msg = (msg,) if isinstance(msg, str) else msg
|
||||
@@ -203,14 +203,14 @@ async def test_startswith(
|
||||
],
|
||||
)
|
||||
async def test_endswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
msg: Union[str, tuple[str, ...]],
|
||||
ignorecase: bool,
|
||||
type: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
test_endswith = endswith(msg, ignorecase)
|
||||
dependent = list(test_endswith.checkers)[0]
|
||||
dependent = next(iter(test_endswith.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
msg = (msg,) if isinstance(msg, str) else msg
|
||||
@@ -243,14 +243,14 @@ async def test_endswith(
|
||||
],
|
||||
)
|
||||
async def test_fullmatch(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
msg: Union[str, tuple[str, ...]],
|
||||
ignorecase: bool,
|
||||
type: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
test_fullmatch = fullmatch(msg, ignorecase)
|
||||
dependent = list(test_fullmatch.checkers)[0]
|
||||
dependent = next(iter(test_fullmatch.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
msg = (msg,) if isinstance(msg, str) else msg
|
||||
@@ -279,13 +279,13 @@ async def test_fullmatch(
|
||||
],
|
||||
)
|
||||
async def test_keyword(
|
||||
kws: Tuple[str, ...],
|
||||
kws: tuple[str, ...],
|
||||
type: str,
|
||||
text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
test_keyword = keyword(*kws)
|
||||
dependent = list(test_keyword.checkers)[0]
|
||||
dependent = next(iter(test_keyword.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, KeywordsRule)
|
||||
@@ -323,15 +323,15 @@ async def test_keyword(
|
||||
],
|
||||
)
|
||||
async def test_command(
|
||||
cmds: Tuple[Tuple[str, ...]],
|
||||
cmds: tuple[tuple[str, ...]],
|
||||
force_whitespace: Optional[Union[str, bool]],
|
||||
cmd: Tuple[str, ...],
|
||||
cmd: tuple[str, ...],
|
||||
whitespace: Optional[str],
|
||||
arg_text: Optional[str],
|
||||
expected: bool,
|
||||
):
|
||||
test_command = command(*cmds, force_whitespace=force_whitespace)
|
||||
dependent = list(test_command.checkers)[0]
|
||||
dependent = next(iter(test_command.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, CommandRule)
|
||||
@@ -352,7 +352,7 @@ async def test_shell_command():
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
test_not_cmd = shell_command(CMD)
|
||||
dependent = list(test_not_cmd.checkers)[0]
|
||||
dependent = next(iter(test_not_cmd.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
@@ -361,7 +361,7 @@ async def test_shell_command():
|
||||
assert not await dependent(event=event, state=state)
|
||||
|
||||
test_no_parser = shell_command(CMD)
|
||||
dependent = list(test_no_parser.checkers)[0]
|
||||
dependent = next(iter(test_no_parser.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
@@ -375,7 +375,7 @@ async def test_shell_command():
|
||||
parser.add_argument("-a", required=True)
|
||||
|
||||
test_simple_parser = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_simple_parser.checkers)[0]
|
||||
dependent = next(iter(test_simple_parser.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message("-a 1")
|
||||
@@ -386,7 +386,7 @@ async def test_shell_command():
|
||||
assert state[SHELL_ARGS] == Namespace(a="1")
|
||||
|
||||
test_parser_help = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_parser_help.checkers)[0]
|
||||
dependent = next(iter(test_parser_help.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message("-h")
|
||||
@@ -399,7 +399,7 @@ async def test_shell_command():
|
||||
assert state[SHELL_ARGS].message == parser.format_help()
|
||||
|
||||
test_parser_error = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_parser_error.checkers)[0]
|
||||
dependent = next(iter(test_parser_error.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
@@ -412,7 +412,7 @@ async def test_shell_command():
|
||||
assert state[SHELL_ARGS].message.startswith(parser.format_usage() + "test: error:")
|
||||
|
||||
test_parser_remain_args = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_parser_remain_args.checkers)[0]
|
||||
dependent = next(iter(test_parser_remain_args.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = MessageSegment.text("-a 1 2") + MessageSegment.image("test")
|
||||
@@ -425,7 +425,7 @@ async def test_shell_command():
|
||||
assert state[SHELL_ARGS].message.startswith(parser.format_usage() + "test: error:")
|
||||
|
||||
test_message_parser = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_message_parser.checkers)[0]
|
||||
dependent = next(iter(test_message_parser.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = MessageSegment.text("-a") + MessageSegment.image("test")
|
||||
@@ -435,21 +435,20 @@ async def test_shell_command():
|
||||
assert state[SHELL_ARGV] == ["-a", MessageSegment.image("test")]
|
||||
assert state[SHELL_ARGS] == Namespace(a=MessageSegment.image("test"))
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
parser = ArgumentParser("test", exit_on_error=False)
|
||||
parser.add_argument("-a", required=True)
|
||||
parser = ArgumentParser("test", exit_on_error=False)
|
||||
parser.add_argument("-a", required=True)
|
||||
|
||||
test_not_exit = shell_command(CMD, parser=parser)
|
||||
dependent = list(test_not_exit.checkers)[0]
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
event = make_fake_event(_message=message)()
|
||||
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||
assert await dependent(event=event, state=state)
|
||||
assert state[SHELL_ARGV] == []
|
||||
assert isinstance(state[SHELL_ARGS], ParserExit)
|
||||
assert state[SHELL_ARGS].status != 0
|
||||
test_not_exit = shell_command(CMD, parser=parser)
|
||||
dependent = next(iter(test_not_exit.checkers))
|
||||
checker = dependent.call
|
||||
assert isinstance(checker, ShellCommandRule)
|
||||
message = Message()
|
||||
event = make_fake_event(_message=message)()
|
||||
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
|
||||
assert await dependent(event=event, state=state)
|
||||
assert state[SHELL_ARGV] == []
|
||||
assert isinstance(state[SHELL_ARGS], ParserExit)
|
||||
assert state[SHELL_ARGS].status != 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -476,7 +475,7 @@ async def test_regex(
|
||||
matched: Optional[Match[str]],
|
||||
):
|
||||
test_regex = regex(pattern)
|
||||
dependent = list(test_regex.checkers)[0]
|
||||
dependent = next(iter(test_regex.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, RegexRule)
|
||||
@@ -499,7 +498,7 @@ async def test_regex(
|
||||
@pytest.mark.parametrize("expected", [True, False])
|
||||
async def test_to_me(expected: bool):
|
||||
test_to_me = to_me()
|
||||
dependent = list(test_to_me.checkers)[0]
|
||||
dependent = next(iter(test_to_me.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, ToMeRule)
|
||||
@@ -515,7 +514,7 @@ async def test_is_type():
|
||||
Event3 = make_fake_event()
|
||||
|
||||
test_type = is_type(Event1, Event2)
|
||||
dependent = list(test_type.checkers)[0]
|
||||
dependent = next(iter(test_type.checkers))
|
||||
checker = dependent.call
|
||||
|
||||
assert isinstance(checker, IsTypeRule)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import json
|
||||
from typing import Dict, List, Union, Literal, TypeVar, ClassVar
|
||||
from typing import Dict, List, Union, Literal, TypeVar, ClassVar # noqa: UP035
|
||||
|
||||
from utils import FakeMessage, FakeMessageSegment
|
||||
from nonebot.utils import (
|
||||
@@ -26,8 +26,10 @@ def test_generic_check_issubclass():
|
||||
assert generic_check_issubclass(Union[int, float, None], (int, float))
|
||||
assert generic_check_issubclass(Literal[1, 2, 3], int)
|
||||
assert not generic_check_issubclass(Literal[1, 2, "3"], int)
|
||||
assert generic_check_issubclass(List[int], list)
|
||||
assert generic_check_issubclass(Dict[str, int], dict)
|
||||
assert generic_check_issubclass(List[int], list) # noqa: UP006
|
||||
assert generic_check_issubclass(Dict[str, int], dict) # noqa: UP006
|
||||
assert generic_check_issubclass(list[int], list)
|
||||
assert generic_check_issubclass(dict[str, int], dict)
|
||||
assert not generic_check_issubclass(ClassVar[int], int)
|
||||
assert generic_check_issubclass(TypeVar("T", int, float), (int, float))
|
||||
assert generic_check_issubclass(TypeVar("T", bound=int), (int, float))
|
||||
|
@@ -1,5 +1,6 @@
|
||||
from typing import Union, Optional
|
||||
from typing_extensions import override
|
||||
from typing import Type, Union, Mapping, Iterable, Optional
|
||||
from collections.abc import Mapping, Iterable
|
||||
|
||||
from pydantic import Extra, create_model
|
||||
|
||||
@@ -76,7 +77,7 @@ class FakeMessage(Message[FakeMessageSegment]):
|
||||
|
||||
|
||||
def make_fake_event(
|
||||
_base: Optional[Type[Event]] = None,
|
||||
_base: Optional[type[Event]] = None,
|
||||
_type: str = "message",
|
||||
_name: str = "test",
|
||||
_description: str = "test",
|
||||
@@ -85,7 +86,7 @@ def make_fake_event(
|
||||
_message: Optional[Message] = None,
|
||||
_to_me: bool = True,
|
||||
**fields,
|
||||
) -> Type[Event]:
|
||||
) -> type[Event]:
|
||||
Base = _base or Event
|
||||
|
||||
class FakeEvent(Base, extra=Extra.forbid):
|
||||
|
@@ -8,7 +8,7 @@ slug: /
|
||||
|
||||
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架(下称 NoneBot),它基于 Python 的类型注解和异步优先特性(兼容同步),能够为你的需求实现提供便捷灵活的支持。同时,NoneBot 拥有大量的开发者为其开发插件,用户无需编写任何代码,仅需完成环境配置及插件安装,就可以正常使用 NoneBot。
|
||||
|
||||
需要注意的是,NoneBot 仅支持 **Python 3.8 以上版本**
|
||||
需要注意的是,NoneBot 仅支持 **Python 3.9 以上版本**
|
||||
|
||||
## 特色
|
||||
|
||||
@@ -34,7 +34,7 @@ NoneBot 采用了一套自行定义的依赖注入系统,可以让事件的处
|
||||
|
||||
#### 什么是依赖注入
|
||||
|
||||
[**『依赖注入』**](https://zh.m.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)意思是,在编程中,有一种方法可以让你的代码声明它工作和使用所需要的东西,即**『依赖』**。
|
||||
[**『依赖注入』**](https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)意思是,在编程中,有一种方法可以让你的代码声明它工作和使用所需要的东西,即**『依赖』**。
|
||||
|
||||
系统(在这里是指 NoneBot)将负责做任何需要的事情,为你的代码提供这些必要依赖(即**『注入』**依赖性)
|
||||
|
||||
|
@@ -89,7 +89,7 @@ async def _(bot): ... # 兼容性处理
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="3.9" label="Python 3.9">
|
||||
|
||||
```python
|
||||
from typing import Union
|
||||
@@ -127,7 +127,7 @@ async def _(event): ... # 兼容性处理
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="3.9" label="Python 3.9">
|
||||
|
||||
```python
|
||||
from typing import Union
|
||||
@@ -191,7 +191,7 @@ async def _(e: ActionFailed | NetworkError): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="3.9" label="Python 3.9">
|
||||
|
||||
```python {6,9}
|
||||
from typing import Union
|
||||
@@ -216,8 +216,8 @@ async def _(e: Union[ActionFailed, NetworkError]): ...
|
||||
|
||||
子依赖使用 `Depends` 标记进行定义,其参数即依赖的函数或可调用对象,同样会被解析为 `Dependent` 对象,将会在依赖注入期间执行。我们来看一个例子:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {5,15}
|
||||
from typing import Annotated
|
||||
@@ -239,7 +239,7 @@ async def _(event: Annotated[Event, Depends(check)]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3,13}
|
||||
from nonebot import on_command
|
||||
@@ -287,8 +287,8 @@ async def _():
|
||||
|
||||
NoneBot 在执行子依赖时,会将其返回值缓存起来。当我们在使用子依赖时,`Depends` 具有一个参数 `use_cache`,默认为 `True`。此时在事件处理流程中,多次使用同一个子依赖时,将会使用缓存中的结果而不会重复执行。这在很多情景中非常有用,例如:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {7}
|
||||
import random
|
||||
@@ -302,7 +302,7 @@ async def _(x: Annotated[int, Depends(random_result)]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {6}
|
||||
import random
|
||||
@@ -319,8 +319,8 @@ async def _(x: int = Depends(random_result)):
|
||||
|
||||
此时,在同一事件处理流程中,这个随机函数的返回值将会保持一致。如果我们希望每次都重新执行子依赖,可以将 `use_cache` 设置为 `False`。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {7}
|
||||
import random
|
||||
@@ -334,7 +334,7 @@ async def _(x: Annotated[int, Depends(random_result, use_cache=False)]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {6}
|
||||
import random
|
||||
@@ -357,8 +357,8 @@ async def _(x: int = Depends(random_result, use_cache=False)):
|
||||
|
||||
在依赖注入系统中,我们可以对子依赖的返回值进行自动类型转换与校验。这个功能由 Pydantic 支持,因此我们通过参数类型注解自动使用 Pydantic 支持的类型转换。例如:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {6,9}
|
||||
from typing import Annotated
|
||||
@@ -374,7 +374,7 @@ async def _(user_id: Annotated[int, Depends(get_user_id, validate=True)]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4,7}
|
||||
from nonebot.params import Depends
|
||||
@@ -392,8 +392,8 @@ async def _(user_id: int = Depends(get_user_id, validate=True)):
|
||||
|
||||
在进行类型自动转换的同时,Pydantic 还支持对数据进行更多的限制,如:大于、小于、长度等。使用方法如下:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {7,10}
|
||||
from typing import Annotated
|
||||
@@ -410,7 +410,7 @@ async def _(user_id: Annotated[int, Depends(get_user_id, validate=Field(gt=100))
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {5,8}
|
||||
from pydantic import Field
|
||||
@@ -431,8 +431,8 @@ async def _(user_id: int = Depends(get_user_id, validate=Field(gt=100))):
|
||||
|
||||
在前面的事例中,我们使用了函数作为子依赖。实际上,我们还可以使用类作为依赖。当我们在实例化一个类的时候,其实我们就在调用它,类本身也是一个可调用对象。例如:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {16}
|
||||
from typing import Annotated
|
||||
@@ -455,7 +455,7 @@ async def _(data: Annotated[ClassDependency, Depends(ClassDependency)]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {15}
|
||||
from dataclasses import dataclass
|
||||
@@ -481,8 +481,8 @@ async def _(data: ClassDependency = Depends(ClassDependency)):
|
||||
|
||||
可以看到,我们使用 `dataclass` 定义了一个类。由于这个类的 `__init__` 方法可以被依赖注入系统解析,因此,我们可以将其作为子依赖进行声明。特别地,对于类依赖,`Depends` 的参数可以为空,NoneBot 将会使用参数的类型注解进行解析与推断:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
@@ -492,7 +492,7 @@ async def _(data: Annotated[ClassDependency, Depends()]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python
|
||||
async def _(data: ClassDependency = Depends()):
|
||||
@@ -510,11 +510,12 @@ NoneBot 的依赖注入支持依赖项在事件处理流程结束后进行一些
|
||||
|
||||
我们可以看下述代码段, 使用 `httpx.AsyncClient` 异步网络 IO,并在事件处理流程中共用一个 client:
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {15}
|
||||
from typing import Annotated, AsyncGenerator
|
||||
from typing import Annotated
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
import httpx
|
||||
from nonebot.params import Depends
|
||||
@@ -533,10 +534,10 @@ async def _(x: Annotated[httpx.AsyncClient, Depends(get_client)]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {15}
|
||||
from typing import AsyncGenerator
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
import httpx
|
||||
from nonebot.params import Depends
|
||||
@@ -566,11 +567,10 @@ async def _(x: httpx.AsyncClient = Depends(get_client)):
|
||||
在 Python 里,为类定义 `__call__` 方法就可以使得这个类的实例成为一个可调用对象。因此,我们也可以将定义了 `__call__` 方法的类的实例作为依赖。事实上,NoneBot 的[内置响应规则](./matcher.md#内置响应规则)就广泛使用了这种方式,以 `is_type` 规则为例:
|
||||
|
||||
```python
|
||||
from typing import Type
|
||||
from nonebot.adapters import Event
|
||||
|
||||
class IsTypeRule:
|
||||
def __init__(self, *types: Type[Event]):
|
||||
def __init__(self, *types: type[Event]):
|
||||
self.types = types
|
||||
|
||||
async def __call__(self, event: Event) -> bool:
|
||||
@@ -587,8 +587,8 @@ class IsTypeRule:
|
||||
|
||||
获取当前事件的类型。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -598,7 +598,7 @@ async def _(foo: Annotated[str, EventType()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import EventType
|
||||
@@ -613,8 +613,8 @@ async def _(foo: str = EventType()): ...
|
||||
|
||||
获取当前事件的消息。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {5}
|
||||
from typing import Annotated
|
||||
@@ -625,7 +625,7 @@ async def _(foo: Annotated[Message, EventMessage()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4}
|
||||
from nonebot.adapters import Message
|
||||
@@ -641,8 +641,8 @@ async def _(foo: Message = EventMessage()): ...
|
||||
|
||||
获取当前事件的消息纯文本部分。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -652,7 +652,7 @@ async def _(foo: Annotated[str, EventPlainText()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import EventPlainText
|
||||
@@ -667,8 +667,8 @@ async def _(foo: str = EventPlainText()): ...
|
||||
|
||||
获取当前事件是否与机器人相关。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -678,7 +678,7 @@ async def _(foo: Annotated[bool, EventToMe()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import EventToMe
|
||||
@@ -693,8 +693,8 @@ async def _(foo: bool = EventToMe()): ...
|
||||
|
||||
获取当前命令型消息的元组形式命令名。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -704,13 +704,12 @@ async def _(foo: Annotated[tuple[str, ...], Command()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4}
|
||||
from typing import Tuple
|
||||
from nonebot.params import Command
|
||||
|
||||
async def _(foo: Tuple[str, ...] = Command()): ...
|
||||
async def _(foo: tuple[str, ...] = Command()): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -724,8 +723,8 @@ async def _(foo: Tuple[str, ...] = Command()): ...
|
||||
|
||||
获取当前命令型消息的文本形式命令名。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -735,7 +734,7 @@ async def _(foo: Annotated[str, RawCommand()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import RawCommand
|
||||
@@ -754,8 +753,8 @@ async def _(foo: str = RawCommand()): ...
|
||||
|
||||
获取命令型消息命令后跟随的参数。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {5}
|
||||
from typing import Annotated
|
||||
@@ -766,7 +765,7 @@ async def _(foo: Annotated[Message, CommandArg()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4}
|
||||
from nonebot.adapters import Message
|
||||
@@ -786,8 +785,8 @@ async def _(foo: Message = CommandArg()): ...
|
||||
|
||||
获取命令型消息命令前缀。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -797,7 +796,7 @@ async def _(foo: Annotated[str, CommandStart()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import CommandStart
|
||||
@@ -816,8 +815,8 @@ async def _(foo: str = CommandStart()): ...
|
||||
|
||||
获取命令型消息命令与参数间空白符。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -827,7 +826,7 @@ async def _(foo: Annotated[str, CommandWhitespace()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import CommandWhitespace
|
||||
@@ -854,10 +853,16 @@ from typing import Annotated
|
||||
from nonebot.params import ShellCommandArgs
|
||||
|
||||
async def _(foo: Annotated[list[str | MessageSegment], ShellCommandArgv()]): ...
|
||||
```
|
||||
|
||||
```python {4}
|
||||
from nonebot.params import ShellCommandArgs
|
||||
|
||||
async def _(foo: list[str | MessageSegment] = ShellCommandArgv()): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.9" label="Python 3.9+">
|
||||
<TabItem value="3.9" label="Python 3.9">
|
||||
|
||||
```python {4}
|
||||
from typing import Union, Annotated
|
||||
@@ -866,14 +871,11 @@ from nonebot.params import ShellCommandArgs
|
||||
async def _(foo: Annotated[list[Union[str, MessageSegment]], ShellCommandArgv()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
|
||||
```python {4}
|
||||
from typing import List, Union
|
||||
from typing import Union
|
||||
from nonebot.params import ShellCommandArgs
|
||||
|
||||
async def _(foo: List[Union[str, MessageSegment]] = ShellCommandArgv()): ...
|
||||
async def _(foo: list[Union[str, MessageSegment]] = ShellCommandArgv()): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -889,8 +891,8 @@ async def _(foo: List[Union[str, MessageSegment]] = ShellCommandArgv()): ...
|
||||
由于 `ArgumentParser` 在解析到 `--help` 参数时也会抛出异常,这种情况下错误码为 `0` 且错误信息即为帮助信息。
|
||||
:::
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {14,22}
|
||||
from typing import Annotated
|
||||
@@ -919,7 +921,7 @@ async def _(foo: Annotated[Namespace, ShellCommandArgs()]):
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {12,20}
|
||||
from nonebot import on_shell_command
|
||||
@@ -948,12 +950,40 @@ async def _(foo: Namespace = ShellCommandArgs()):
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### RegexMatched
|
||||
|
||||
获取正则匹配结果的对象。
|
||||
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {5}
|
||||
from re import Match
|
||||
from typing import Annotated
|
||||
from nonebot.params import RegexMatched
|
||||
|
||||
async def _(foo: Annotated[Match[str], RegexMatched()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4}
|
||||
from re import Match
|
||||
from nonebot.params import RegexMatched
|
||||
|
||||
async def _(foo: Match[str] = RegexMatched()): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### RegexStr
|
||||
|
||||
获取正则匹配结果的文本。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -963,7 +993,7 @@ async def _(foo: Annotated[str, RegexStr()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import RegexStr
|
||||
@@ -978,8 +1008,8 @@ async def _(foo: str = RegexStr()): ...
|
||||
|
||||
获取正则匹配结果的 group 元组。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Any, Annotated
|
||||
@@ -989,13 +1019,13 @@ async def _(foo: Annotated[tuple[Any, ...], RegexGroup()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4}
|
||||
from typing import Tuple, Any
|
||||
from typing import Any
|
||||
from nonebot.params import RegexGroup
|
||||
|
||||
async def _(foo: Tuple[Any, ...] = RegexGroup()): ...
|
||||
async def _(foo: tuple[Any, ...] = RegexGroup()): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -1005,8 +1035,8 @@ async def _(foo: Tuple[Any, ...] = RegexGroup()): ...
|
||||
|
||||
获取正则匹配结果的 group 字典。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Any, Annotated
|
||||
@@ -1016,13 +1046,13 @@ async def _(foo: Annotated[dict[str, Any], RegexDict()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4}
|
||||
from typing import Any, Dict
|
||||
from typing import Any
|
||||
from nonebot.params import RegexDict
|
||||
|
||||
async def _(foo: Dict[str, Any] = RegexDict()): ...
|
||||
async def _(foo: dict[str, Any] = RegexDict()): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -1032,8 +1062,8 @@ async def _(foo: Dict[str, Any] = RegexDict()): ...
|
||||
|
||||
获取触发响应器的消息前缀字符串。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -1043,7 +1073,7 @@ async def _(foo: Annotated[str, Startswith()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import Startswith
|
||||
@@ -1058,8 +1088,8 @@ async def _(foo: str = Startswith()): ...
|
||||
|
||||
获取触发响应器的消息后缀字符串。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -1069,7 +1099,7 @@ async def _(foo: Annotated[str, Endswith()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import Endswith
|
||||
@@ -1084,8 +1114,8 @@ async def _(foo: str = Endswith()): ...
|
||||
|
||||
获取触发响应器的消息字符串。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -1095,7 +1125,7 @@ async def _(foo: Annotated[str, Fullmatch()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import Fullmatch
|
||||
@@ -1110,8 +1140,8 @@ async def _(foo: str = Fullmatch()): ...
|
||||
|
||||
获取触发响应器的关键字字符串。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {4}
|
||||
from typing import Annotated
|
||||
@@ -1121,7 +1151,7 @@ async def _(foo: Annotated[str, Keyword()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {3}
|
||||
from nonebot.params import Keyword
|
||||
@@ -1136,8 +1166,8 @@ async def _(foo: str = Keyword()): ...
|
||||
|
||||
获取某次 `receive` 接收的事件。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {7}
|
||||
from typing import Annotated
|
||||
@@ -1150,7 +1180,7 @@ async def _(foo: Annotated[Event, Received("id")]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {5}
|
||||
from nonebot.adapters import Event
|
||||
@@ -1167,8 +1197,8 @@ async def _(foo: Event = Received("id")): ...
|
||||
|
||||
获取最近一次 `receive` 接收的事件。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {7}
|
||||
from typing import Annotated
|
||||
@@ -1181,7 +1211,7 @@ async def _(foo: Annotated[Event, LastReceived()]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {5}
|
||||
from nonebot.adapters import Event
|
||||
@@ -1198,8 +1228,8 @@ async def _(foo: Event = LastReceived()): ...
|
||||
|
||||
获取某次 `got` 接收的参数。如果 `Arg` 参数留空,则使用函数的参数名作为要获取的参数。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {7,8}
|
||||
from typing import Annotated
|
||||
@@ -1213,7 +1243,7 @@ async def _(foo: Annotated[Message, Arg("key")]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {5,6}
|
||||
from nonebot.params import Arg
|
||||
@@ -1231,8 +1261,8 @@ async def _(foo: Message = Arg("key")): ...
|
||||
|
||||
获取某次 `got` 接收的参数,并转换为字符串。如果 `Arg` 参数留空,则使用函数的参数名作为要获取的参数。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {6,7}
|
||||
from typing import Annotated
|
||||
@@ -1245,7 +1275,7 @@ async def _(foo: Annotated[str, ArgStr("key")]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4,5}
|
||||
from nonebot.params import ArgStr
|
||||
@@ -1262,8 +1292,8 @@ async def _(foo: str = ArgStr("key")): ...
|
||||
|
||||
获取某次 `got` 接收的参数的纯文本部分。如果 `Arg` 参数留空,则使用函数的参数名作为要获取的参数。
|
||||
|
||||
<Tabs groupId="python">
|
||||
<TabItem value="3.9" label="Python 3.9+" default>
|
||||
<Tabs groupId="annotated">
|
||||
<TabItem value="annotated" label="Use Annotated" default>
|
||||
|
||||
```python {6,7}
|
||||
from typing import Annotated
|
||||
@@ -1276,7 +1306,7 @@ async def _(foo: Annotated[str, ArgPlainText("key")]): ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="3.8" label="Python 3.8+">
|
||||
<TabItem value="no-annotated" label="Without Annotated">
|
||||
|
||||
```python {4,5}
|
||||
from nonebot.params import ArgPlainText
|
||||
|
@@ -154,7 +154,7 @@ rule = keyword("hello", "hi")
|
||||
```python
|
||||
from nonebot import on_keyword
|
||||
|
||||
matcher = on_keyword("hello", "hi")
|
||||
matcher = on_keyword({"hello", "hi"})
|
||||
```
|
||||
|
||||
### `command`
|
||||
|
@@ -73,10 +73,12 @@ CUSTOM_CONFIG=config in dotenv
|
||||
同时,设置环境变量:
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set CUSTOM_CONFIG "config in environment variables"
|
||||
# windows cmd
|
||||
set CUSTOM_CONFIG 'config in environment variables'
|
||||
# windows powershell
|
||||
$Env:CUSTOM_CONFIG='config in environment variables'
|
||||
# linux/macOS
|
||||
export CUSTOM_CONFIG="config in environment variables"
|
||||
export CUSTOM_CONFIG='config in environment variables'
|
||||
```
|
||||
|
||||
那最终 NoneBot 所读取的内容为环境变量中的内容,即 `config in environment variables`。
|
||||
@@ -295,8 +297,10 @@ DRIVER=~fastapi+~httpx+~websockets
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
# windows cmd
|
||||
set DRIVER '~fastapi+~httpx+~websockets'
|
||||
# windows powershell
|
||||
$Env:DRIVER='~fastapi+~httpx+~websockets'
|
||||
# linux/macOS
|
||||
export DRIVER='~fastapi+~httpx+~websockets'
|
||||
```
|
||||
@@ -331,8 +335,10 @@ HOST=127.0.0.1
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
# windows cmd
|
||||
set HOST '127.0.0.1'
|
||||
# windows powershell
|
||||
$Env:HOST='127.0.0.1'
|
||||
# linux/macOS
|
||||
export HOST='127.0.0.1'
|
||||
```
|
||||
@@ -367,8 +373,10 @@ PORT=8080
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
# windows cmd
|
||||
set PORT '8080'
|
||||
# windows powershell
|
||||
$Env:PORT='8080'
|
||||
# linux/macOS
|
||||
export PORT='8080'
|
||||
```
|
||||
@@ -407,8 +415,10 @@ LOG_LEVEL=DEBUG
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
# windows cmd
|
||||
set LOG_LEVEL 'DEBUG'
|
||||
# windows powershell
|
||||
$Env:LOG_LEVEL='DEBUG'
|
||||
# linux/macOS
|
||||
export LOG_LEVEL='DEBUG'
|
||||
```
|
||||
@@ -443,8 +453,10 @@ API_TIMEOUT=10.0
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
# windows cmd
|
||||
set API_TIMEOUT '10.0'
|
||||
# windows powershell
|
||||
$Env:API_TIMEOUT='10.0'
|
||||
# linux/macOS
|
||||
export API_TIMEOUT='10.0'
|
||||
```
|
||||
@@ -479,8 +491,10 @@ SUPERUSERS=["123123123"]
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
# windows cmd
|
||||
set SUPERUSERS '["123123123"]'
|
||||
# windows powershell
|
||||
$Env:SUPERUSERS='["123123123"]'
|
||||
# linux/macOS
|
||||
export SUPERUSERS='["123123123"]'
|
||||
```
|
||||
@@ -515,8 +529,10 @@ NICKNAME=["bot"]
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
# windows cmd
|
||||
set NICKNAME '["bot"]'
|
||||
# windows powershell
|
||||
$Env:NICKNAME='["bot"]'
|
||||
# linux/macOS
|
||||
export NICKNAME='["bot"]'
|
||||
```
|
||||
@@ -554,9 +570,12 @@ COMMAND_SEP=[".", " "]
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
# windows cmd
|
||||
set COMMAND_START '["/", ""]'
|
||||
set COMMAND_SEP '[".", " "]'
|
||||
# windows powershell
|
||||
$Env:COMMAND_START='["/", ""]'
|
||||
$Env:COMMAND_SEP='[".", " "]'
|
||||
# linux/macOS
|
||||
export COMMAND_START='["/", ""]'
|
||||
export COMMAND_SEP='[".", " "]'
|
||||
@@ -579,23 +598,25 @@ nonebot.init(command_start={"/", ""}, command_sep={".", " "})
|
||||
- **类型**: `timedelta`
|
||||
- **默认值**: `timedelta(minutes=2)`
|
||||
|
||||
用户会话超时时间,配置格式参考 [Datetime Types](https://docs.pydantic.dev/usage/types/#datetime-types),可以为单位为秒的 `int | float` 等。
|
||||
用户会话超时时间,配置格式参考 [Datetime Types](https://docs.pydantic.dev/latest/api/standard_library_types/#datetimetimedelta)。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
||||
```dotenv
|
||||
SESSION_EXPIRE_TIMEOUT=120
|
||||
SESSION_EXPIRE_TIMEOUT=00:02:00
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set SESSION_EXPIRE_TIMEOUT '120'
|
||||
# windows cmd
|
||||
set SESSION_EXPIRE_TIMEOUT '00:02:00'
|
||||
# windows powershell
|
||||
$Env:SESSION_EXPIRE_TIMEOUT='00:02:00'
|
||||
# linux/macOS
|
||||
export SESSION_EXPIRE_TIMEOUT='120'
|
||||
export SESSION_EXPIRE_TIMEOUT='00:02:00'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
@@ -21,7 +21,7 @@ options:
|
||||
`RuleChecker` 是一个返回值为 `bool` 类型的依赖函数,即 `RuleChecker` 支持依赖注入。我们可以根据上一节中添加的[配置项](./config.mdx#插件配置),在 `weather` 插件目录中编写一个响应规则:
|
||||
|
||||
```python {3,4} title=weather/__init__.py
|
||||
plugin_config = Config.parse_obj(get_driver().config)
|
||||
plugin_config = get_plugin_config(Config)
|
||||
|
||||
async def is_enable() -> bool:
|
||||
return plugin_config.weather_plugin_enabled
|
||||
@@ -57,7 +57,7 @@ weather = on_command("天气", rule=rule)
|
||||
```python {10} title=weather/__init__.py
|
||||
from nonebot.rule import to_me
|
||||
|
||||
plugin_config = Config.parse_obj(get_driver().config)
|
||||
plugin_config = get_plugin_config(Config)
|
||||
|
||||
async def is_enable() -> bool:
|
||||
return plugin_config.weather_plugin_enabled
|
||||
|
@@ -102,17 +102,16 @@ async def got_location(location: str = ArgPlainText()):
|
||||
|
||||
</details>
|
||||
|
||||
```python {5-10,14-16,18-19}
|
||||
```python {5-9,13-15,17-18}
|
||||
from nonebot.rule import to_me
|
||||
from arclet.alconna import Alconna, Args
|
||||
from nonebot_plugin_alconna import Match, on_alconna
|
||||
|
||||
weather = on_alconna(
|
||||
Alconna("天气", Args["location?", str]),
|
||||
aliases={"weather", "天气预报"},
|
||||
rule=to_me(),
|
||||
)
|
||||
weather.shortcut("weather", {"command": "天气"})
|
||||
weather.shortcut("天气预报", {"command": "天气"})
|
||||
|
||||
|
||||
@weather.handle()
|
||||
@@ -133,7 +132,7 @@ async def got_location(location: str):
|
||||
或阅读 [Alconna 基本介绍](./command.md) 一节。
|
||||
|
||||
关于更多 `on_alconna` 的使用方法,可参考 [插件文档](https://github.com/nonebot/plugin-alconna/blob/master/docs.md),
|
||||
或阅读 [响应规则的使用](./matcher.md) 一节。
|
||||
或阅读 [响应规则的使用](./matcher.mdx) 一节。
|
||||
|
||||
## 交流与反馈
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user