Compare commits

..

1 Commits

Author SHA1 Message Date
N791 d8c36e8eff 🍻 publish plugin nonebot-plugin-ehentai-search (#2884) 2024-08-17 10:48:42 +08:00
570 changed files with 28974 additions and 41462 deletions
+11 -9
View File
@@ -1,21 +1,21 @@
{
"name": "Ubuntu",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"name": "Default Linux Universal",
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
"features": {
"ghcr.io/jsburckhardt/devcontainer-features/uv:1": {},
"ghcr.io/devcontainers/features/node:2": {},
"ghcr.io/meaningful-ooo/devcontainer-features/fish:2": {}
"ghcr.io/devcontainers-contrib/features/poetry:2": {}
},
"postCreateCommand": "corepack enable && COREPACK_ENABLE_DOWNLOAD_PROMPT=0 ./scripts/setup-envs.sh",
"postCreateCommand": "./scripts/setup-envs.sh",
"customizations": {
"vscode": {
"settings": {
"python.analysis.diagnosticMode": "workspace",
"python.analysis.typeCheckingMode": "basic",
"ruff.organizeImports": false,
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports": "explicit"
"source.fixAll.ruff": true,
"source.organizeImports": true
}
},
"[javascript]": {
@@ -44,6 +44,8 @@
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.isort",
"ms-python.black-formatter",
"charliermarsh.ruff",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode",
+38 -356
View File
@@ -1,10 +1,3 @@
const OFF = 0;
const WARNING = 1;
const ERROR = 2;
// Prevent importing lodash, usually for browser bundle size reasons
const LodashImportPatterns = ["lodash", "lodash.**", "lodash/**"];
module.exports = {
root: true,
env: {
@@ -13,391 +6,80 @@ module.exports = {
node: true,
},
parser: "@typescript-eslint/parser",
parserOptions: {},
parserOptions: {
tsconfigRootDir: __dirname,
project: ["./tsconfig.json", "./website/tsconfig.json"],
},
globals: {
JSX: true,
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"airbnb",
"plugin:@typescript-eslint/recommended",
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
// 'plugin:@typescript-eslint/strict',
"plugin:import/recommended",
"plugin:regexp/recommended",
"prettier",
"plugin:@docusaurus/all",
"plugin:prettier/recommended",
],
settings: {
"import/resolver": {
node: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
},
typescript: true,
},
react: {
version: "detect",
},
},
reportUnusedDisableDirectives: true,
plugins: ["react-hooks", "@typescript-eslint", "regexp", "@docusaurus"],
overrides: [
{
files: ["*.ts", "*.tsx"],
rules: {
"import/no-unresolved": "off",
},
},
{
files: ["*.js", "*.cjs"],
rules: {
"@typescript-eslint/no-var-requires": "off",
},
},
],
plugins: ["@typescript-eslint"],
rules: {
"react/jsx-uses-react": OFF, // JSX runtime: automatic
"react/react-in-jsx-scope": OFF, // JSX runtime: automatic
"array-callback-return": WARNING,
camelcase: WARNING,
"class-methods-use-this": OFF, // It's a way of allowing private variables.
curly: [WARNING, "all"],
"global-require": WARNING,
"lines-between-class-members": OFF,
"max-classes-per-file": OFF,
"max-len": [
WARNING,
{
code: Infinity, // Code width is already enforced by Prettier
tabWidth: 2,
comments: 80,
ignoreUrls: true,
ignorePattern: "(eslint-disable|@)",
},
],
"arrow-body-style": OFF,
"no-await-in-loop": OFF,
"no-case-declarations": WARNING,
"no-console": OFF,
"no-constant-binary-expression": ERROR,
"no-continue": OFF,
"no-control-regex": WARNING,
"no-else-return": OFF,
"no-empty": [WARNING, { allowEmptyCatch: true }],
"no-lonely-if": WARNING,
"no-nested-ternary": WARNING,
"no-param-reassign": [WARNING, { props: false }],
"no-prototype-builtins": WARNING,
"no-restricted-exports": OFF,
"no-restricted-properties": [
ERROR,
.../** @type {[string, string][]} */ ([
// TODO: TS doesn't make Boolean a narrowing function yet,
// so filter(Boolean) is problematic type-wise
// ['compact', 'Array#filter(Boolean)'],
["concat", "Array#concat"],
["drop", "Array#slice(n)"],
["dropRight", "Array#slice(0, -n)"],
["fill", "Array#fill"],
["filter", "Array#filter"],
["find", "Array#find"],
["findIndex", "Array#findIndex"],
["first", "foo[0]"],
["flatten", "Array#flat"],
["flattenDeep", "Array#flat(Infinity)"],
["flatMap", "Array#flatMap"],
["fromPairs", "Object.fromEntries"],
["head", "foo[0]"],
["indexOf", "Array#indexOf"],
["initial", "Array#slice(0, -1)"],
["join", "Array#join"],
// Unfortunately there's no great alternative to _.last yet
// Candidates: foo.slice(-1)[0]; foo[foo.length - 1]
// Array#at is ES2022; could replace _.nth as well
// ['last'],
["map", "Array#map"],
["reduce", "Array#reduce"],
["reverse", "Array#reverse"],
["slice", "Array#slice"],
["take", "Array#slice(0, n)"],
["takeRight", "Array#slice(-n)"],
["tail", "Array#slice(1)"],
]).map(([property, alternative]) => ({
object: "_",
property,
message: `Use ${alternative} instead.`,
})),
...[
"readdirSync",
"readFileSync",
"statSync",
"lstatSync",
"existsSync",
"pathExistsSync",
"realpathSync",
"mkdirSync",
"mkdirpSync",
"mkdirsSync",
"writeFileSync",
"writeJsonSync",
"outputFileSync",
"outputJsonSync",
"moveSync",
"copySync",
"copyFileSync",
"ensureFileSync",
"ensureDirSync",
"ensureLinkSync",
"ensureSymlinkSync",
"unlinkSync",
"removeSync",
"emptyDirSync",
].map((property) => ({
object: "fs",
property,
message: "Do not use sync fs methods.",
})),
],
"no-restricted-syntax": [
WARNING,
// Copied from airbnb, removed for...of statement, added export all
{
selector: "ForInStatement",
message:
"for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.",
},
{
selector: "LabeledStatement",
message:
"Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.",
},
{
selector: "WithStatement",
message:
"`with` is disallowed in strict mode because it makes code impossible to predict and optimize.",
},
{
selector: "ExportAllDeclaration",
message:
"Export all does't work well if imported in ESM due to how they are transpiled, and they can also lead to unexpected exposure of internal methods.",
},
// TODO make an internal plugin to ensure this
// {
// selector:
// @ 'ExportDefaultDeclaration > Identifier, ExportNamedDeclaration[source=null] > ExportSpecifier',
// message: 'Export in one statement'
// },
...["path", "fs-extra", "webpack", "lodash"].map((m) => ({
selector: `ImportDeclaration[importKind=value]:has(Literal[value=${m}]) > ImportSpecifier[importKind=value]`,
message:
"Default-import this, both for readability and interoperability with ESM",
})),
],
"no-template-curly-in-string": WARNING,
"no-unused-expressions": [
WARNING,
{ allowTaggedTemplates: true, allowShortCircuit: true },
],
"no-useless-escape": WARNING,
"no-void": [ERROR, { allowAsStatement: true }],
"prefer-destructuring": WARNING,
"prefer-named-capture-group": WARNING,
"prefer-template": WARNING,
yoda: WARNING,
"import/extensions": OFF,
// This rule doesn't yet support resolving .js imports when the actual file
// is .ts. Plus it's not all that useful when our code is fully TS-covered.
"import/no-unresolved": [
OFF,
{
// Ignore certain webpack aliases because they can't be resolved
ignore: [
"^@theme",
"^@docusaurus",
"^@generated",
"^@site",
"^@testing-utils",
],
},
],
"linebreak-style": ["error", "unix"],
quotes: ["error", "double", { avoidEscape: true }],
semi: ["error", "always"],
"@typescript-eslint/no-non-null-assertion": "off",
"import/order": [
WARNING,
"error",
{
groups: [
"builtin",
"external",
"internal",
["parent", "sibling", "index"],
"type",
"parent",
"sibling",
"index",
],
"newlines-between": "always",
pathGroups: [
// always put css import to the last, ref:
// https://github.com/import-js/eslint-plugin-import/issues/1239
{
pattern: "*.+(css|sass|less|scss|pcss|styl)",
group: "unknown",
patternOptions: { matchBase: true },
position: "after",
},
{ pattern: "react", group: "builtin", position: "before" },
{ pattern: "react-dom", group: "builtin", position: "before" },
{ pattern: "react-dom/**", group: "builtin", position: "before" },
{ pattern: "stream", group: "builtin", position: "before" },
{ pattern: "fs-extra", group: "builtin" },
{ pattern: "lodash", group: "external", position: "before" },
{ pattern: "clsx", group: "external", position: "before" },
// 'Bit weird to not use the `import/internal-regex` option, but this
// way, we can make `import type { Props } from "@theme/*"` appear
// before `import styles from "styles.module.css"`, which is what we
// always did. This should be removable once we stop using ambient
// module declarations for theme aliases.
{ pattern: "@theme/**", group: "internal" },
{ pattern: "@site/**", group: "internal" },
{ pattern: "@theme-init/**", group: "internal" },
{ pattern: "@theme-original/**", group: "internal" },
{ pattern: "@/components/**", group: "internal" },
{ pattern: "@/libs/**", group: "internal" },
{ pattern: "@/types/**", group: "type" },
],
pathGroupsExcludedImportTypes: [],
// example: let `import './nprogress.css';` after importing others
// in `packages/docusaurus-theme-classic/src/nprogress.ts`
// see more: https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md#warnonunassignedimports-truefalse
warnOnUnassignedImports: true,
},
],
"import/prefer-default-export": OFF,
"jsx-a11y/click-events-have-key-events": WARNING,
"jsx-a11y/no-noninteractive-element-interactions": WARNING,
"jsx-a11y/html-has-lang": OFF,
"react-hooks/rules-of-hooks": ERROR,
"react-hooks/exhaustive-deps": ERROR,
// Sometimes we do need the props as a whole, e.g. when spreading
"react/destructuring-assignment": OFF,
"react/function-component-definition": [
WARNING,
{
namedComponents: "function-declaration",
unnamedComponents: "arrow-function",
},
],
"react/jsx-filename-extension": OFF,
"react/jsx-key": [ERROR, { checkFragmentShorthand: true }],
"react/jsx-no-useless-fragment": [ERROR, { allowExpressions: true }],
"react/jsx-props-no-spreading": OFF,
"react/no-array-index-key": OFF, // We build a static site, and nearly all components don't change.
"react/no-unstable-nested-components": [WARNING, { allowAsProps: true }],
"react/prefer-stateless-function": WARNING,
"react/prop-types": OFF,
"react/require-default-props": [
ERROR,
{ ignoreFunctionalComponents: true },
],
"@typescript-eslint/consistent-type-definitions": OFF,
"@typescript-eslint/require-await": OFF,
"@typescript-eslint/ban-ts-comment": [
ERROR,
{ "ts-expect-error": "allow-with-description" },
],
"@typescript-eslint/consistent-indexed-object-style": OFF,
"@typescript-eslint/consistent-type-imports": [
WARNING,
{ disallowTypeAnnotations: false },
],
"@typescript-eslint/explicit-module-boundary-types": WARNING,
"@typescript-eslint/method-signature-style": ERROR,
"@typescript-eslint/no-empty-function": OFF,
"@typescript-eslint/no-empty-interface": [
ERROR,
{
allowSingleExtends: true,
},
],
"@typescript-eslint/no-inferrable-types": OFF,
"@typescript-eslint/no-namespace": [WARNING, { allowDeclarations: true }],
"no-use-before-define": OFF,
"@typescript-eslint/no-use-before-define": [
ERROR,
{ functions: false, classes: false, variables: true },
],
"@typescript-eslint/no-non-null-assertion": OFF,
"no-redeclare": OFF,
"@typescript-eslint/no-redeclare": ERROR,
"no-shadow": OFF,
"@typescript-eslint/no-shadow": ERROR,
"no-unused-vars": OFF,
// We don't provide any escape hatches for this rule. Rest siblings and
// function placeholder params are always ignored, and any other unused
// locals must be justified with a disable comment.
"@typescript-eslint/no-unused-vars": [ERROR, { ignoreRestSiblings: true }],
"@typescript-eslint/prefer-optional-chain": ERROR,
"@docusaurus/no-html-links": ERROR,
"@docusaurus/prefer-docusaurus-heading": ERROR,
"@docusaurus/no-untranslated-text": [
WARNING,
{
ignoredStrings: [
"·",
"-",
"—",
"×",
"", // zwj: ​
"@",
"WebContainers",
"Twitter",
"GitHub",
"Dev.to",
"1.x",
],
"newlines-between": "always",
alphabetize: {
order: "asc",
},
},
],
},
overrides: [
{
files: ["packages/*/src/theme/**/*.{js,ts,tsx}"],
excludedFiles: "*.test.{js,ts,tsx}",
rules: {
"no-restricted-imports": [
"error",
{
patterns: LodashImportPatterns.concat(
// Prevents relative imports between React theme components
[
"../**",
"./**",
// Allows relative styles module import with consistent filename
"!./styles.module.css",
// Allows relative tailwind css import with consistent filename
"!./styles.css",
]
),
},
],
},
},
{
files: ["packages/*/src/theme/**/*.{js,ts,tsx}"],
rules: {
"import/no-named-export": ERROR,
},
},
{
files: ["*.d.ts"],
rules: {
"import/no-duplicates": OFF,
},
},
{
files: ["*.{ts,tsx}"],
rules: {
"no-undef": OFF,
"import/no-import-module-exports": OFF,
},
},
{
files: ["*.{js,mjs,cjs}"],
rules: {
// Make JS code directly runnable in Node.
"@typescript-eslint/no-var-requires": OFF,
"@typescript-eslint/explicit-module-boundary-types": OFF,
},
},
{
// Internal files where extraneous deps don't matter much at long as
// they run
files: ["website/**"],
rules: {
"import/no-extraneous-dependencies": OFF,
},
},
],
};
+1 -1
View File
@@ -1,2 +1,2 @@
open_collective: nonebot
custom: ["https://afdian.com/@nonebot"]
custom: ["https://afdian.net/@nonebot"]
+1 -8
View File
@@ -1,15 +1,8 @@
name: 发布适配器
title: "Adapter: {name}"
description: 发布适配器到 NoneBot 官方商店
labels: ["Adapter", "Publish"]
labels: ["Adapter"]
body:
- type: markdown
attributes:
value: |
# 发布须知
非特殊情况下,请通过 [NoneBot 适配器商店](https://nonebot.dev/store/adapters) 的发布表单进行插件发布信息填写。
- type: input
id: name
attributes:
+1 -8
View File
@@ -1,15 +1,8 @@
name: 发布机器人
title: "Bot: {name}"
description: 发布机器人到 NoneBot 官方商店
labels: ["Bot", "Publish"]
labels: ["Bot"]
body:
- type: markdown
attributes:
value: |
# 发布须知
非特殊情况下,请通过 [NoneBot 机器人商店](https://nonebot.dev/store/bots) 的发布表单进行插件发布信息填写。
- type: input
id: name
attributes:
+4 -12
View File
@@ -1,16 +1,8 @@
name: 发布插件
title: "Plugin: {name}"
description: 发布插件到 NoneBot 官方商店
labels: ["Plugin", "Publish"]
labels: ["Plugin"]
body:
- type: markdown
attributes:
value: |
# 发布须知
非特殊情况下,请通过 [NoneBot 插件商店](https://nonebot.dev/store/plugins) 的发布表单进行插件发布信息填写。
在发布前请阅读 [NoneBot 插件发布流程指导](https://nonebot.dev/docs/developer/plugin-publishing) 并确保满足其中所述条件。
- type: input
id: pypi
attributes:
@@ -23,9 +15,9 @@ body:
- type: input
id: module
attributes:
label: 插件模块
description: 加载插件时所使用的模块名称
placeholder: e.g. nonebot_plugin_apscheduler
label: 插件 import 包
description: 插件 import 包名
placeholder: e.g. nonebot_plugin_xxx
validations:
required: true
+6 -1
View File
@@ -5,5 +5,10 @@ runs:
using: "composite"
steps:
- run: |
uv run --no-sync bash ./scripts/build-api-docs.sh
poetry run nb-autodoc nonebot \
-s nonebot.plugins \
-u nonebot.internal \
-u nonebot.internal.*
cp -r ./build/nonebot/* ./website/docs/api/
yarn prettier
shell: bash
+3 -3
View File
@@ -4,10 +4,10 @@ description: Setup Node
runs:
using: "composite"
steps:
- uses: actions/setup-node@v6
- uses: actions/setup-node@v4
with:
node-version: lts/*
cache: yarn
node-version: "18"
cache: "yarn"
- run: yarn install --frozen-lockfile
shell: bash
+23 -7
View File
@@ -5,20 +5,36 @@ inputs:
python-version:
description: Python version
required: false
default: "3.12"
env-group:
description: Environment group
default: "3.10"
env-dir:
description: Environment directory
required: false
default: "pydantic-v2"
default: "."
no-root:
description: Do not install package in the environment
required: false
default: "false"
runs:
using: "composite"
steps:
- uses: astral-sh/setup-uv@v7
- name: Install poetry
run: pipx install poetry
shell: bash
- uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
cache-suffix: ${{ inputs.env-group }}
cache: "poetry"
cache-dependency-path: |
./poetry.lock
${{ inputs.env-dir }}/poetry.lock
- run: |
uv sync --all-extras --locked --group ${{ inputs.env-group }}
cd ${{ inputs.env-dir }}
if [ "${{ inputs.no-root }}" = "true" ]; then
poetry install --all-extras --no-root
else
poetry install --all-extras
fi
shell: bash
-9
View File
@@ -35,12 +35,3 @@ updates:
actions:
patterns:
- "*"
- package-ecosystem: devcontainers
directory: "/"
schedule:
interval: daily
groups:
devcontainers:
patterns:
- "*"
+8 -16
View File
@@ -13,7 +13,7 @@ on:
- ".github/actions/setup-python/**"
- ".github/workflows/codecov.yml"
- "pyproject.toml"
- "uv.lock"
- "poetry.lock"
jobs:
test:
@@ -25,7 +25,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12"]
os: [ubuntu-latest, windows-latest, macos-latest]
env: [pydantic-v1, pydantic-v2]
env:
@@ -34,33 +34,25 @@ jobs:
PYDANTIC_VERSION: ${{ matrix.env }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Setup Python environment
uses: ./.github/actions/setup-python
with:
python-version: ${{ matrix.python-version }}
env-group: ${{ matrix.env }}
env-dir: ./envs/${{ matrix.env }}
no-root: true
- name: Run Pytest
run: |
uv run --no-sync bash ./scripts/run-tests.sh
- name: Upload test results
uses: codecov/test-results-action@v1
with:
env_vars: OS,PYTHON_VERSION,PYDANTIC_VERSION
files: ./tests/junit.xml
flags: unittests
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
cd ./envs/${{ matrix.env }}
poetry run bash "../../scripts/run-tests.sh"
- name: Upload coverage report
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@v4
with:
env_vars: OS,PYTHON_VERSION,PYDANTIC_VERSION
files: ./tests/coverage.xml
flags: unittests
fail_ci_if_error: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+86 -18
View File
@@ -3,21 +3,21 @@ name: NoneFlow
on:
issues:
types: [opened, reopened, edited]
issue_comment:
types: [created]
pull_request_target:
types: [closed]
issue_comment:
types: [created]
pull_request_review:
types: [submitted]
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number && format('publish/issue{0}', github.event.issue.number) || github.head_ref || github.run_id }}
cancel-in-progress: ${{ startsWith(github.head_ref, 'publish/issue')}}
group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}
cancel-in-progress: false
jobs:
noneflow:
check:
runs-on: ubuntu-latest
name: noneflow
name: check
# do not run on forked PRs, do not run on not related issues, do not run on pr comments
if: |
!(
@@ -36,6 +36,70 @@ 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:
result: ${{ steps.plugin-test.outputs.RESULT }}
output: ${{ steps.plugin-test.outputs.OUTPUT }}
metadata: ${{ steps.plugin-test.outputs.METADATA }}
steps:
- name: Install Poetry
if: ${{ !startsWith(github.event_name, 'pull_request') }}
run: pipx install poetry
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Test Plugin
id: plugin-test
run: |
curl -sSL https://github.com/nonebot/noneflow/releases/latest/download/plugin_test.py | python -
noneflow:
runs-on: ubuntu-latest
name: noneflow
needs: plugin_test
steps:
- name: Generate token
id: generate-token
@@ -45,29 +109,33 @@ jobs:
private_key: ${{ secrets.APP_KEY }}
- name: Checkout Code
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Cache pre-commit hooks
uses: actions/cache@v4
with:
path: .cache/.pre-commit
key: noneflow-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
- name: NoneFlow
uses: docker://ghcr.io/nonebot/noneflow:latest
with:
config: >
{
"base": "master",
"plugin_path": "assets/plugins.json5",
"bot_path": "assets/bots.json5",
"adapter_path": "assets/adapters.json5",
"registry_repository": "nonebot/registry",
"artifact_path": "artifact"
"plugin_path": "assets/plugins.json",
"bot_path": "assets/bots.json",
"adapter_path": "assets/adapters.json"
}
env:
PLUGIN_TEST_RESULT: ${{ needs.plugin_test.outputs.result }}
PLUGIN_TEST_OUTPUT: ${{ needs.plugin_test.outputs.output }}
PLUGIN_TEST_METADATA: ${{ needs.plugin_test.outputs.metadata }}
APP_ID: ${{ secrets.APP_ID }}
PRIVATE_KEY: ${{ secrets.APP_KEY }}
PRE_COMMIT_HOME: /github/workspace/.cache/.pre-commit
- name: Upload Artifact
uses: actions/upload-artifact@v7
with:
name: noneflow
path: artifact/*
if-no-files-found: ignore
- name: Fix permission
run: sudo chown -R $(whoami):$(id -ng) .cache/.pre-commit
+6 -7
View File
@@ -13,7 +13,7 @@ on:
- ".github/actions/setup-python/**"
- ".github/workflows/pyright.yml"
- "pyproject.toml"
- "uv.lock"
- "poetry.lock"
jobs:
pyright:
@@ -28,21 +28,20 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Setup Python environment
uses: ./.github/actions/setup-python
with:
env-group: ${{ matrix.env }}
env-dir: ./envs/${{ matrix.env }}
no-root: true
- run: |
echo "$(dirname $(uv python find))" >> $GITHUB_PATH
(cd ./envs/${{ matrix.env }} && echo "$(poetry env info --path)/bin" >> $GITHUB_PATH)
if [ "${{ matrix.env }}" = "pydantic-v1" ]; then
sed -i 's/PYDANTIC_V2 = true/PYDANTIC_V2 = false/g' ./pyproject.toml
fi
shell: bash
- name: Run Pyright
uses: jakebailey/pyright-action@v3
with:
pylance-version: latest-release
uses: jakebailey/pyright-action@v2
+23 -14
View File
@@ -2,14 +2,17 @@ name: Release Drafter
on:
push:
branches:
- master
tags:
- v*
pull_request_target:
branches:
- master
types:
- closed
jobs:
update-release-draft:
if: github.ref == 'refs/heads/master'
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
concurrency:
group: pull-request-changelog
@@ -22,14 +25,14 @@ jobs:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_KEY }}
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Setup Node Environment
uses: ./.github/actions/setup-node
- uses: release-drafter/release-drafter@v6.0.0
- uses: release-drafter/release-drafter@v6
id: release-drafter
env:
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
@@ -37,7 +40,7 @@ jobs:
- name: Update Changelog
uses: docker://ghcr.io/nonebot/auto-changelog:master
with:
changelog_file: website/src/changelog/changelog.md
changelog_file: website/src/pages/changelog.md
latest_changes_position: '# 更新日志\n\n'
latest_changes_title: "## 最近更新"
replace_regex: '(?<=## 最近更新\n)[\s\S]*?(?=\n## )'
@@ -67,7 +70,7 @@ jobs:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_KEY }}
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Setup Python Environment
uses: ./.github/actions/setup-python
@@ -81,7 +84,7 @@ jobs:
- name: Get Version
id: version
run: |
echo "VERSION=$(uv version --short)" >> $GITHUB_OUTPUT
echo "VERSION=$(poetry version -s)" >> $GITHUB_OUTPUT
echo "TAG_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
@@ -89,7 +92,7 @@ jobs:
if: steps.version.outputs.VERSION != steps.version.outputs.TAG_VERSION
run: exit 1
- uses: release-drafter/release-drafter@v6.0.0
- uses: release-drafter/release-drafter@v6
with:
name: Release ${{ steps.version.outputs.TAG_NAME }} 🌈
tag: ${{ steps.version.outputs.TAG_NAME }}
@@ -99,8 +102,10 @@ jobs:
- name: Build Package
run: |
uv build
uv publish
poetry build
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
- name: Publish package to GitHub
run: |
@@ -112,9 +117,13 @@ jobs:
run: |
yarn build:plugin --out-dir ../packages/nonebot-plugin-docs/nonebot_plugin_docs/dist
cd packages/nonebot-plugin-docs/
uv version ${{ steps.version.outputs.VERSION }}
uv build
uv publish
poetry version ${{ steps.version.outputs.VERSION }}
poetry build
- name: Publish Doc Package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: packages/nonebot-plugin-docs/dist/
- name: Publish Doc Package to GitHub
run: |
+5 -5
View File
@@ -14,7 +14,7 @@ jobs:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_KEY }}
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
@@ -27,19 +27,19 @@ jobs:
- name: Build API Doc
uses: ./.github/actions/build-api-doc
- run: echo "TAG_NAME=v$(uv version --short)" >> $GITHUB_ENV
- run: echo "TAG_NAME=v$(poetry version -s)" >> $GITHUB_ENV
- name: Archive Changelog
uses: docker://ghcr.io/nonebot/auto-changelog:master
with:
changelog_file: website/src/changelog/changelog.md
changelog_file: website/src/pages/changelog.md
archive_regex: '(?<=## )最近更新(?=\n)'
archive_title: ${{ env.TAG_NAME }}
commit_and_push: false
- name: Archive Files
run: |
yarn archive $(uv version --short)
yarn archive $(poetry version -s)
yarn prettier
- name: Push Tag
@@ -47,6 +47,6 @@ jobs:
git config user.name noneflow[bot]
git config user.email 129742071+noneflow[bot]@users.noreply.github.com
git add .
git commit -m ":bookmark: Release $(uv version --short)"
git commit -m ":bookmark: Release $(poetry version -s)"
git tag ${{ env.TAG_NAME }}
git push && git push --tags
+4 -4
View File
@@ -13,18 +13,18 @@ on:
- ".github/actions/setup-python/**"
- ".github/workflows/ruff.yml"
- "pyproject.toml"
- "uv.lock"
- "poetry.lock"
jobs:
ruff:
name: Ruff Lint
runs-on: ubuntu-latest
concurrency:
group: ruff-${{ github.ref }}
group: pyright-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Run Ruff Lint
uses: astral-sh/ruff-action@v3
uses: chartboost/ruff-action@v1
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
cancel-in-progress: true
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
fetch-depth: 0
+6 -11
View File
@@ -24,7 +24,7 @@ jobs:
steps:
- name: Set Commit Status
uses: actions/github-script@v9
uses: actions/github-script@v7
with:
script: |
github.rest.repos.createCommitStatus({
@@ -37,7 +37,7 @@ jobs:
})
- name: Download Artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@v4
with:
name: website-preview
github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -45,16 +45,11 @@ jobs:
- name: Restore Context
run: |
PR_NUMBER=$(cat ./pr-number)
if ! [[ "${PR_NUMBER}" =~ ^[0-9]+$ ]]; then
echo "Invalid PR number: ${PR_NUMBER}"
exit 1
fi
echo "PR_NUMBER=${PR_NUMBER}" >> "${GITHUB_ENV}"
cat action.env >> $GITHUB_ENV
- name: Set Deploy Name
run: |
echo "DEPLOY_NAME=deploy-preview-${PR_NUMBER}" >> "${GITHUB_ENV}"
echo "DEPLOY_NAME=deploy-preview-${{ env.PR_NUMBER }}" >> $GITHUB_ENV
- name: Deploy to Netlify
id: deploy
@@ -70,7 +65,7 @@ jobs:
# 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@v3
uses: marocchino/sticky-pull-request-comment@v2
with:
header: website
number: ${{ env.PR_NUMBER }}
@@ -78,7 +73,7 @@ jobs:
:rocket: Deployed to ${{ steps.deploy.outputs.deploy-url }}
- name: Set Commit Status
uses: actions/github-script@v9
uses: actions/github-script@v7
if: always()
with:
script: |
+4 -4
View File
@@ -11,7 +11,7 @@ jobs:
cancel-in-progress: true
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
@@ -30,13 +30,13 @@ jobs:
- name: Export Context
run: |
echo "${{ github.event.pull_request.number }}" > ./pr-number
echo "PR_NUMBER=${{ github.event.number }}" >> ./action.env
- name: Upload Artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: website-preview
path: |
./website/build
./pr-number
./action.env
retention-days: 1
-1
View File
@@ -7,7 +7,6 @@ docs_build/_build
!tests/.env
.docusaurus
website/docs/api/**/*.md
website/src/pages/changelog/**/*
# Created by https://www.toptal.com/developers/gitignore/api/python,node,visualstudiocode,jetbrains,macos,windows,linux
# Edit at https://www.toptal.com/developers/gitignore?templates=python,node,visualstudiocode,jetbrains,macos,windows,linux
+23 -6
View File
@@ -7,13 +7,30 @@ ci:
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.12
rev: v0.5.6
hooks:
- id: ruff-check
args: [--fix]
stages: [pre-commit]
- id: ruff-format
stages: [pre-commit]
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
stages: [commit]
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
stages: [commit]
- repo: https://github.com/psf/black
rev: 24.8.0
hooks:
- id: black
stages: [commit]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
stages: [commit]
- repo: https://github.com/nonebot/nonemoji
rev: v0.1.4
-2
View File
@@ -1,3 +1 @@
.github/**/*.md
website/docs/tutorial/application.mdx
website/versioned_docs/*/tutorial/application.mdx
+1 -1
View File
@@ -1,3 +1,3 @@
# Changelog
See [changelog.md](./website/src/changelog/changelog.md) or <https://nonebot.dev/changelog>
See [changelog.md](./website/src/pages/changelog.md) or <https://nonebot.dev/changelog>
+4 -4
View File
@@ -18,15 +18,15 @@
## Pull Request
NoneBot 使用 [uv](https://docs.astral.sh/uv/) 管理项目依赖,由于 pre-commit 也经其管理,所以在此一并说明。
NoneBot 使用 [poetry](https://python-poetry.org/) 管理项目依赖,由于 pre-commit 也经其管理,所以在此一并说明。
下面的命令能在已安装 uv 和 yarn 的情况下帮你快速配置开发环境。
下面的命令能在已安装 poetry 和 yarn 的情况下帮你快速配置开发环境。
```bash
# 安装 python 依赖
uv sync --all-extras
poetry install
# 安装 pre-commit git hook
uv run pre-commit install
pre-commit install
```
### 使用 GitHub CodespacesDev Container
+5 -11
View File
@@ -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.10+-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>
@@ -118,28 +118,22 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
| 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/) | ✅ | QQNT 协议 |
| 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) 协议,QQNT 机器人接口标准 |
| 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 协议 |
| Milky[仓库](https://github.com/nonebot/adapter-milky)[协议](https://milky.ntqqrev.org/)) | ✅ | QQNT 机器人应用接口标准 |
| 钉钉([仓库](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 协议,由社区贡献 |
| Mail[仓库](https://github.com/mobyw/nonebot-adapter-mail)) | ↗️ | 邮件收发协议,由社区贡献 |
| 黑盒语音([仓库](https://github.com/lclbm/adapter-heybox)[协议](https://github.com/QingFengOpen/HeychatDoc) | ↗️ | 黑盒语音机器人协议,由社区贡献 |
| 微信公众平台([仓库](https://github.com/YangRucheng/nonebot-adapter-wxmp)[协议](https://developers.weixin.qq.com/doc/)| ↗️ | 微信公众平台协议,由社区贡献 |
| Gewechat[仓库](https://github.com/Shine-Light/nonebot-adapter-gewechat)[协议](https://github.com/Devo919/Gewechat)| ❌ | Gewechat 微信协议,Gewechat不再维护及可用 |
| EFChat[仓库](https://github.com/molanp/nonebot_adapter_efchat)[协议](https://irinu-live.melon.fish/efc-help/) | ↗️ | 恒五聊平台协议,由社区贡献 |
| VoceChat [仓库](https://github.com/5656565566/nonebot-adapter-vocechat)[协议](https://doc.voce.chat/zh-cn/bot/bot-and-webhook) | ↗️ | VoceChat 平台协议,由社区贡献 |
| B站直播间([仓库](https://github.com/MingxuanGame/nonebot-adapter-bilibili-live)[Web API 协议](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/live)[开放平台协议](https://open-live.bilibili.com/document) | ↗️ | B站直播间(Web API/开放平台)协议,由社区贡献 |
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
+33 -142
View File
@@ -4,7 +4,7 @@
"project_link": "nonebot-adapter-onebot",
"name": "OneBot V11",
"desc": "OneBot V11 协议",
"author_id": 42488585,
"author": "yanyongyu",
"homepage": "https://onebot.adapters.nonebot.dev/",
"tags": [],
"is_official": true
@@ -14,7 +14,7 @@
"project_link": "nonebot-adapter-ding",
"name": "钉钉",
"desc": "钉钉协议",
"author_id": 1184028,
"author": "Artin",
"homepage": "https://github.com/nonebot/adapter-ding",
"tags": [],
"is_official": true
@@ -24,7 +24,7 @@
"project_link": "nonebot-adapter-feishu",
"name": "飞书",
"desc": "飞书协议",
"author_id": 14922941,
"author": "StarHeartHunt",
"homepage": "https://github.com/nonebot/adapter-feishu",
"tags": [],
"is_official": true
@@ -34,7 +34,7 @@
"project_link": "nonebot-adapter-telegram",
"name": "Telegram",
"desc": "Telegram 协议",
"author_id": 50312681,
"author": "j1g5awi",
"homepage": "https://github.com/nonebot/adapter-telegram",
"tags": [],
"is_official": true
@@ -44,7 +44,7 @@
"project_link": "nonebot-adapter-qq",
"name": "QQ",
"desc": "QQ 官方机器人",
"author_id": 42488585,
"author": "yanyongyu",
"homepage": "https://github.com/nonebot/adapter-qq",
"tags": [],
"is_official": true
@@ -54,7 +54,7 @@
"project_link": "nonebot-adapter-kaiheila",
"name": "开黑啦",
"desc": "开黑啦协议适配",
"author_id": 37477320,
"author": "Tian-que",
"homepage": "https://github.com/Tian-que/nonebot-adapter-kaiheila",
"tags": [],
"is_official": false
@@ -64,17 +64,27 @@
"project_link": "nonebot-adapter-mirai",
"name": "Mirai",
"desc": "mirai-api-http v2 协议适配",
"author_id": 42648639,
"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",
"name": "mirai2",
"desc": "为 nonebot2 添加 mirai_api_http2.x的兼容适配器",
"author": "ieew",
"homepage": "https://github.com/ieew/nonebot_adapter_mirai2",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot.adapters.onebot.v12",
"project_link": "nonebot-adapter-onebot",
"name": "OneBot V12",
"desc": "OneBot V12 协议",
"author_id": 42488585,
"author": "yanyongyu",
"homepage": "https://onebot.adapters.nonebot.dev/",
"tags": [],
"is_official": true
@@ -84,7 +94,7 @@
"project_link": "nonebot-adapter-console",
"name": "Console",
"desc": "基于终端的交互式适配器",
"author_id": 50488999,
"author": "Melodyknit",
"homepage": "https://github.com/nonebot/adapter-console",
"tags": [],
"is_official": true
@@ -94,7 +104,7 @@
"project_link": "nonebot-adapter-github",
"name": "GitHub",
"desc": "GitHub APP & OAuth APP integration",
"author_id": 42488585,
"author": "yanyongyu",
"homepage": "https://github.com/nonebot/adapter-github",
"tags": [],
"is_official": true
@@ -104,7 +114,7 @@
"project_link": "nonebot-adapter-ntchat",
"name": "Ntchat",
"desc": "pc hook的微信客户端适配",
"author_id": 37363867,
"author": "JustUndertaker",
"homepage": "https://github.com/JustUndertaker/adapter-ntchat",
"tags": [
{
@@ -119,7 +129,7 @@
"project_link": "nonebot-adapter-minecraft",
"name": "Minecraft",
"desc": "MineCraft通信适配,支持Rcon",
"author_id": 54731914,
"author": "17TheWord",
"homepage": "https://github.com/17TheWord/nonebot-adapter-minecraft",
"tags": [
{
@@ -134,7 +144,7 @@
"project_link": "nonebot-adapter-bilibili",
"name": "BilibiliLive",
"desc": "b站直播间ws协议",
"author_id": 39620657,
"author": "wwweww",
"homepage": "https://github.com/wwweww/adapter-bilibili",
"tags": [],
"is_official": false
@@ -144,7 +154,7 @@
"project_link": "nonebot-adapter-walleq",
"name": "Walle-Q",
"desc": "内置 QQ 协议实现",
"author_id": 18395948,
"author": "abrahum",
"homepage": "https://github.com/onebot-walle/nonebot_adapter_walleq",
"tags": [
{
@@ -159,7 +169,7 @@
"project_link": "nonebot-adapter-villa",
"name": "大别野",
"desc": "米游社大别野官方Bot适配",
"author_id": 63870437,
"author": "CMHopeSunshine",
"homepage": "https://github.com/CMHopeSunshine/nonebot-adapter-villa",
"tags": [
{
@@ -174,7 +184,7 @@
"project_link": "nonebot-adapter-red",
"name": "RedProtocol",
"desc": "QQNT RedProtocol 适配",
"author_id": 55650833,
"author": "zhaomaoniu",
"homepage": "https://github.com/nonebot/adapter-red",
"tags": [],
"is_official": true
@@ -184,7 +194,7 @@
"project_link": "nonebot-adapter-discord",
"name": "Discord",
"desc": "Discord 官方 Bot 协议适配",
"author_id": 63870437,
"author": "CMHopeSunshine",
"homepage": "https://github.com/nonebot/adapter-discord",
"tags": [],
"is_official": true
@@ -194,7 +204,7 @@
"project_link": "nonebot-adapter-satori",
"name": "Satori",
"desc": "Satori 协议适配器",
"author_id": 42648639,
"author": "RF-Tar-Railt",
"homepage": "https://github.com/nonebot/adapter-satori",
"tags": [
{
@@ -209,7 +219,7 @@
"project_link": "nonebot-adapter-dodo",
"name": "DoDo",
"desc": "DoDo Bot 协议适配器",
"author_id": 63870437,
"author": "CMHopeSunshine",
"homepage": "https://github.com/nonebot/adapter-dodo",
"tags": [],
"is_official": true
@@ -219,7 +229,7 @@
"project_link": "nonebot-adapter-rocketchat",
"name": "RocketChat",
"desc": "RocketChat adapter for nonebot2",
"author_id": 78360471,
"author": "IllTamer",
"homepage": "https://github.com/IUnlimit/nonebot-adapter-rocketchat",
"tags": [],
"is_official": false
@@ -229,7 +239,7 @@
"project_link": "nonebot-adapter-kritor",
"name": "Kritor",
"desc": "Kritor 协议适配",
"author_id": 42648639,
"author": "RF-Tar-Railt",
"homepage": "https://github.com/nonebot/adapter-kritor",
"tags": [
{
@@ -244,128 +254,9 @@
"project_link": "nonebot-adapter-tailchat",
"name": "Tailchat",
"desc": "Tailchat 适配器",
"author_id": 61458340,
"author": "eya46",
"homepage": "https://github.com/eya46/nonebot-adapter-tailchat",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot.adapters.mail",
"project_link": "nonebot-adapter-mail",
"name": "Mail",
"desc": "邮件收发协议",
"author_id": 44370805,
"homepage": "https://github.com/mobyw/nonebot-adapter-mail",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot.adapters.heybox",
"project_link": "nonebot-adapter-heybox",
"name": "黑盒语音",
"desc": "黑盒语音机器人适配",
"author_id": 54730982,
"homepage": "https://github.com/lclbm/adapter-heybox",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot.adapters.wxmp",
"project_link": "nonebot-adapter-wxmp",
"name": "WXMP",
"desc": "微信公众平台 客服适配器",
"author_id": 60175467,
"homepage": "https://github.com/YangRucheng/nonebot-adapter-wxmp",
"tags": [
{
"label": "微信公众平台",
"color": "#843dbc"
}
],
"is_official": false
},
{
"module_name": "nonebot.adapters.milky",
"project_link": "nonebot-adapter-milky",
"name": "nonebot-adapter-milky",
"desc": "Milky 协议适配器",
"author_id": 42648639,
"homepage": "https://github.com/nonebot/adapter-milky",
"tags": [],
"is_official": true
},
{
"module_name": "nonebot.adapters.efchat",
"project_link": "nonebot-adapter-efchat",
"name": "nonebot-adapter-efchat",
"desc": "适用于EFChat(恒五聊)聊天室的nonebot适配器",
"author_id": 104612722,
"homepage": "https://github.com/molanp/nonebot_adapter_efchat",
"tags": [
{
"label": "EFChat",
"color": "#ac6161"
},
{
"label": "恒五聊",
"color": "#cc99ff"
}
],
"is_official": false
},
{
"module_name": "nonebot.adapters.vocechat",
"project_link": "nonebot-adapter-vocechat",
"name": "nonebot-adapter-vocechat",
"desc": "Vocechat 协议适配器",
"author_id": 56059687,
"homepage": "https://github.com/5656565566/nonebot-adapter-vocechat",
"tags": [],
"is_official": false
},
{
"module_name": "nonebot.adapters.bilibili_live",
"project_link": "nonebot-adapter-bilibili-live",
"name": "B站直播间",
"desc": "B 站直播间协议(Web API/开放平台)支持",
"author_id": 68982190,
"homepage": "https://github.com/MingxuanGame/nonebot-adapter-bilibili-live",
"tags": [
{
"label": "bilibili",
"color": "#ff6699"
}
],
"is_official": false
},
{
"module_name": "nonebot.adapters.yunhu",
"project_link": "nonebot-adapter-yunhu",
"name": "云湖适配器",
"desc": "云湖的NoneBot适配器",
"author_id": 104612722,
"homepage": "https://github.com/molanp/nonebot-adapter-yunhu",
"tags": [
{
"label": "云湖",
"color": "#8a74eb"
}
],
"is_official": false
},
{
"module_name": "nonebot.adapters.wxclaw",
"project_link": "nonebot-adapter-wxclaw",
"name": "WxClaw",
"desc": "基于 openclaw-weixin 协议的 NoneBot2 微信智能体适配器",
"author_id": 51957264,
"homepage": "https://github.com/shoucandanghehe/nonebot-adapter-wxclaw",
"tags": [
{
"label": "微信",
"color": "#1aad19"
}
],
"is_official": false
},
}
]
+59 -203
View File
@@ -2,7 +2,7 @@
{
"name": "HarukaBot",
"desc": "将B站UP主的动态和直播信息推送至QQ",
"author_id": 36433929,
"author": "SK-415",
"homepage": "https://github.com/SK-415/HarukaBot",
"tags": [],
"is_official": false
@@ -10,7 +10,7 @@
{
"name": "Omega Miya",
"desc": "B站推送Pixiv搜图识番求签抽卡表情包还有其他杂七杂八的功能",
"author_id": 41713304,
"author": "Ailitonia",
"homepage": "https://github.com/Ailitonia/omega-miya",
"tags": [],
"is_official": false
@@ -18,7 +18,7 @@
{
"name": "Github Bot",
"desc": "在QQ获取/处理Github repo/pr/issue",
"author_id": 42488585,
"author": "yanyongyu",
"homepage": "https://github.com/cscs181/QQ-GitHub-Bot",
"tags": [],
"is_official": false
@@ -26,7 +26,7 @@
{
"name": "YanXiBot",
"desc": "动漫资源查找与娱乐机器人",
"author_id": 50488999,
"author": "Melodyknit",
"homepage": "https://github.com/Melodyknit/YanXiBot",
"tags": [],
"is_official": false
@@ -34,7 +34,7 @@
{
"name": "绪山真寻bot",
"desc": "含有不少的娱乐功能同时稍稍有一些实用的功能 :P",
"author_id": 45528451,
"author": "HibiKier",
"homepage": "https://github.com/HibiKier/zhenxun_bot",
"tags": [],
"is_official": false
@@ -42,7 +42,7 @@
{
"name": "ATRI",
"desc": "高性能文爱萝卜子,糅杂了各类有趣小功能",
"author_id": 37587870,
"author": "Kyomotoi",
"homepage": "https://github.com/Kyomotoi/ATRI",
"tags": [],
"is_official": false
@@ -50,7 +50,7 @@
{
"name": "dumbot傻瓜机器人",
"desc": "猜一猜游戏、新闻一览、英文每日一词一短语等等,含一键启动及docker容器部署就绪",
"author_id": 52522252,
"author": "ffreemt",
"homepage": "https://github.com/ffreemt/koyeb-nb2",
"tags": [],
"is_official": false
@@ -58,7 +58,7 @@
{
"name": "DicePP",
"desc": "TRPG骰娘, 带先攻, 查询等功能, 主要面向DND5E. 面对骰主推出的船新版本, 内置Windows/Linux详细部署指南以及方便的自定义骰娘方法, 从回复文本到查询资料库都可轻松配置~",
"author_id": 88259371,
"author": "pear-studio",
"homepage": "https://github.com/pear-studio/nonebot-dicepp",
"tags": [],
"is_official": false
@@ -66,7 +66,7 @@
{
"name": "SetuBot",
"desc": "每个群配置文件独立,可以控制频率,socks http代理,R18开关,支持多tag,自建API lolicon Pixiv热度榜",
"author_id": 39484884,
"author": "yuban10703",
"homepage": "https://github.com/yuban10703/setu-nonebot2",
"tags": [],
"is_official": false
@@ -74,7 +74,7 @@
{
"name": "剑网三bot",
"desc": "网络游戏《剑侠情缘三》的群聊机器人,数据使用:www.jx3api.com",
"author_id": 37363867,
"author": "JustUndertaker",
"homepage": "https://github.com/JustUndertaker/mini_jx3_bot",
"tags": [
{
@@ -87,7 +87,7 @@
{
"name": "PixivBot",
"desc": "顾名思义是Pixiv的bot(随机推荐插画、随机指定关键词插画、随机书签、查看排行榜、查看指定id插画)",
"author_id": 17331698,
"author": "ssttkkl",
"homepage": "https://github.com/ssttkkl/PixivBot",
"tags": [],
"is_official": false
@@ -95,7 +95,7 @@
{
"name": "SeaBot_QQ",
"desc": "一个能够获取新闻资讯并推送至QQ的群聊机器人。",
"author_id": 31682561,
"author": "B1ue1nWh1te",
"homepage": "https://github.com/B1ue1nWh1te/SeaBot_QQ",
"tags": [],
"is_official": false
@@ -103,7 +103,7 @@
{
"name": "琪露诺Bot",
"desc": "用QQ机器人控制Minecraft服务器!服务器状态查询/服务器白名单/插件列表/玩家查询/转发服务器消息/执行指令... 其他实用娱乐功能,三步即可成功部署的QQ bot",
"author_id": 56951617,
"author": "summerkirakira",
"homepage": "https://github.com/summerkirakira/CirnoBot",
"tags": [
{
@@ -116,7 +116,7 @@
{
"name": "Inkar Suki",
"desc": "一个十分方便的Bot,支持包括Webhook、群管、剑网3等一系列功能,持续更新中……",
"author_id": 68726147,
"author": "HornCopper",
"homepage": "https://github.com/HornCopper/Inkar-Suki",
"tags": [
{
@@ -137,7 +137,7 @@
{
"name": "屑岛风Bot",
"desc": "自家用屑Bot",
"author_id": 71873002,
"author": "kexue-z",
"homepage": "https://github.com/kexue-z/Dao-bot",
"tags": [],
"is_official": false
@@ -145,7 +145,7 @@
{
"name": "LiteyukiBot-轻雪机器人",
"desc": "一个有各种琐事功能的bot,有AI接口,能陪聊",
"author_id": 79104275,
"author": "snowyfirefly",
"homepage": "https://github.com/snowyfirefly/Liteyuki",
"tags": [
{
@@ -162,7 +162,7 @@
{
"name": "nya_bot",
"desc": "喵服——战魂铭人联机服务器兼机器人",
"author_id": 31379266,
"author": "nikissXI",
"homepage": "https://github.com/nikissXI/nya_bot",
"tags": [
{
@@ -175,7 +175,7 @@
{
"name": "真宵Bot",
"desc": "专注群聊的QQ机器人",
"author_id": 71173418,
"author": "Shine-Light",
"homepage": "https://github.com/Shine-Light/Nonebot_Bot_MayaFey",
"tags": [
{
@@ -196,7 +196,7 @@
{
"name": "SkadiBot",
"desc": "明日方舟主题机器人—斯卡蒂",
"author_id": 101615359,
"author": "yuyuziYYZ",
"homepage": "https://github.com/yuyuziYYZ/skadi_bot",
"tags": [
{
@@ -217,7 +217,7 @@
{
"name": "小白机器人",
"desc": "一个高度依赖数据库的群管理机器人",
"author_id": 69745333,
"author": "SDIJF1521",
"homepage": "https://github.com/SDIJF1521/qqai",
"tags": [
{
@@ -234,7 +234,7 @@
{
"name": "LittlePaimon",
"desc": "小派蒙,多功能原神机器人。",
"author_id": 63870437,
"author": "CMHopeSunshine",
"homepage": "https://github.com/CMHopeSunshine/LittlePaimon",
"tags": [
{
@@ -247,7 +247,7 @@
{
"name": "IdhagnBot",
"desc": "🐱🤖 一个以娱乐功能为主的缝合怪(划掉)QQ机器人,包含一定Furry要素但是不会卖萌(就是逊啦!)",
"author_id": 17371317,
"author": "su226",
"homepage": "https://github.com/su226/IdhagnBot",
"tags": [],
"is_official": false
@@ -255,7 +255,7 @@
{
"name": "hsbot",
"desc": "服务于《炉石传说》玩家的机器人,上线至今已有加入十余个个炉石相关群聊,上千名用户使用,响应请求数万次。 数据使用:HSreplay, Fbigame, Hearthstone API",
"author_id": 67055520,
"author": "gzy02",
"homepage": "https://github.com/gzy02/hsbot",
"tags": [
{
@@ -268,7 +268,7 @@
{
"name": "Bread Dog Bot",
"desc": "Terraria TShock QQ 机器人",
"author_id": 160252668,
"author": "Qianyiovo",
"homepage": "https://github.com/Qianyiovo/bread_dog_bot",
"tags": [
{
@@ -289,7 +289,7 @@
{
"name": "RanBot",
"desc": "不@会很安静的Bot",
"author_id": 88923783,
"author": "IAXRetailer",
"homepage": "https://github.com/Hecatia-Hell-Workshop/RanBot",
"tags": [],
"is_official": false
@@ -297,7 +297,7 @@
{
"name": "辞辞(cici)Bot",
"desc": "一个集成娱乐和群管为一体的机器人",
"author_id": 90902259,
"author": "mengxinyuan638",
"homepage": "https://github.com/mengxinyuan638/cici-bot",
"tags": [
{
@@ -318,7 +318,7 @@
{
"name": "SuzunoBot",
"desc": "多功能音游bot,主要服务maimaiDX、Arcaea",
"author_id": 29980586,
"author": "Rinfair-CSP-A016",
"homepage": "https://github.com/Rinfair-CSP-A016/SuzunoBot-AGLAS",
"tags": [
{
@@ -339,7 +339,7 @@
{
"name": "青岚",
"desc": "基于NoneBot的与Minecraft Server互通消息的机器人",
"author_id": 54731914,
"author": "17TheWord",
"homepage": "https://github.com/17TheWord/qinglan_bot",
"tags": [
{
@@ -352,7 +352,7 @@
{
"name": "ChensQBOTv2",
"desc": "多功能QQ群机器人,权限管理/联ban/社工等等等等,以及拥有一个强大的开发者",
"author_id": 116929900,
"author": "cnchens",
"homepage": "https://github.com/cnchens/ChensQBOTv2",
"tags": [],
"is_official": false
@@ -360,7 +360,7 @@
{
"name": "koishi",
"desc": "支持爬取 codeforces, atcoder, 牛客上程序设计赛事的 bot。",
"author_id": 71639222,
"author": "CupidsBow",
"homepage": "https://github.com/CupidsBow/koishi",
"tags": [
{
@@ -381,7 +381,7 @@
{
"name": "脑积水",
"desc": "一个超级缝合怪...",
"author_id": 66541860,
"author": "zhulinyv",
"homepage": "https://github.com/zhulinyv/NJS",
"tags": [
{
@@ -394,7 +394,7 @@
{
"name": "LOVE酱",
"desc": "为铁锈战争游戏群服务的虚拟少女,内置了爬取铁锈房间列表功能,以及游戏内单位查询功能,并制作了教学系统以及铁锈相关游戏群的收集功能。",
"author_id": 106828088,
"author": "allureluoli",
"homepage": "https://github.com/allureluoli/LOVE-",
"tags": [
{
@@ -411,7 +411,7 @@
{
"name": "fubot",
"desc": "基于nonebot与go-cqhttp的QQ娱乐bot,提供群日常娱乐功能与舞萌DX游戏相关的信息查询功能。",
"author_id": 54059896,
"author": "HCskia",
"homepage": "https://github.com/HCskia/fu-Bot",
"tags": [
{
@@ -424,7 +424,7 @@
{
"name": "桃桃酱",
"desc": "一个会拆家的高性能缝合萝卜子",
"author_id": 107618388,
"author": "tkgs0",
"homepage": "https://github.com/tkgs0/Momoko",
"tags": [],
"is_official": false
@@ -432,7 +432,7 @@
{
"name": "CoolQBot",
"desc": "基于 NoneBot2 的聊天机器人",
"author_id": 5219550,
"author": "he0119",
"homepage": "https://github.com/he0119/CoolQBot",
"tags": [],
"is_official": false
@@ -440,7 +440,7 @@
{
"name": "XDbot2",
"desc": "简单的QQ功能型机器人",
"author_id": 104149371,
"author": "This-is-XiaoDeng",
"homepage": "https://github.com/ITCraftDevelopmentTeam/XDbot2",
"tags": [],
"is_official": false
@@ -448,7 +448,7 @@
{
"name": "March7th",
"desc": "三月七 - 崩坏:星穹铁道机器人",
"author_id": 44370805,
"author": "mobyw",
"homepage": "https://github.com/Mar-7th/March7th",
"tags": [
{
@@ -465,7 +465,7 @@
{
"name": "ay机器人",
"desc": "codeforces和洛谷卷王监视、股票监控、ai聊天",
"author_id": 77315378,
"author": "863109569",
"homepage": "https://github.com/863109569/qqbot",
"tags": [
{
@@ -486,7 +486,7 @@
{
"name": "狐尾",
"desc": "一个整合了兽云祭api的机器人,支持账号令牌操作,以及上传兽图",
"author_id": 99388013,
"author": "bingqiu456",
"homepage": "https://github.com/bingqiu456/shouyun",
"tags": [
{
@@ -499,7 +499,7 @@
{
"name": "ReimeiBot-黎明机器人",
"desc": "流星飞逝,黎明终将到来。",
"author_id": 65395090,
"author": "ThirdBlood",
"homepage": "https://github.com/3rdBit/ReimeiBot",
"tags": [],
"is_official": false
@@ -507,7 +507,7 @@
{
"name": "web_bot",
"desc": "把机器人搬到网络上",
"author_id": 63489103,
"author": "wsdtl",
"homepage": "https://github.com/wsdtl/web_bot",
"tags": [
{
@@ -520,7 +520,7 @@
{
"name": "林汐",
"desc": "多平台功能型Bot",
"author_id": 110453675,
"author": "mute23-code",
"homepage": "https://github.com/netsora/SoraBot",
"tags": [
{
@@ -537,7 +537,7 @@
{
"name": "米缸",
"desc": "基于nonebot2的米缸Bot",
"author_id": 13503375,
"author": "LambdaYH",
"homepage": "https://github.com/LambdaYH/MigangBot",
"tags": [],
"is_official": false
@@ -545,7 +545,7 @@
{
"name": "不正经的妹妹",
"desc": "一款功能丰富、简单易用、自定义性强、扩展性强的可爱的QQ娱乐机器人",
"author_id": 104713034,
"author": "itsevin",
"homepage": "https://github.com/itsevin/sister_bot",
"tags": [],
"is_official": false
@@ -553,7 +553,7 @@
{
"name": "星见Kirami",
"desc": "🌟 读作 Kirami,写作星见,简明轻快的聊天机器人应用。",
"author_id": 66513481,
"author": "A-kirami",
"homepage": "https://kiramibot.dev/",
"tags": [],
"is_official": false
@@ -561,7 +561,7 @@
{
"name": "OCNbot",
"desc": "OI Contest Notifier bot,一个可以推送洛谷、cf、atcoder、牛客比赛通知的bot",
"author_id": 91535478,
"author": "ACnoway",
"homepage": "https://github.com/ACnoway/OCNbot",
"tags": [
{
@@ -578,7 +578,7 @@
{
"name": "妃爱",
"desc": "超可爱的妃爱QQ群聊机器人",
"author_id": 52267304,
"author": "jiangyuxiaoxiao",
"homepage": "https://github.com/jiangyuxiaoxiao/Hiyori",
"tags": [],
"is_official": false
@@ -586,7 +586,7 @@
{
"name": "芙芙",
"desc": "供 Mooncell Wiki 协作使用的跨平台机器人",
"author_id": 14922941,
"author": "StarHeartHunt",
"homepage": "https://github.com/MooncellWiki/BotFooChan",
"tags": [],
"is_official": false
@@ -594,7 +594,7 @@
{
"name": "Sakiko",
"desc": "基于 LiteLoaderBDS 的 Minecraft 基岩版 Bot",
"author_id": 55650833,
"author": "zhaomaoniu",
"homepage": "https://github.com/zhaomaoniu/Sakiko",
"tags": [
{
@@ -608,10 +608,18 @@
],
"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_id": 90964775,
"author": "Lonely-Sails",
"homepage": "https://github.com/Minecraft-QQBot/BotServer",
"tags": [
{
@@ -624,157 +632,5 @@
}
],
"is_official": false
},
{
"name": "小安提Bot",
"desc": "服务于音游 舞萌DX 的多功能Bot",
"author_id": 186144551,
"homepage": "https://github.com/Ant1816/Ant1Bot",
"tags": [
{
"label": "maimaiDX",
"color": "#52ea9a"
},
{
"label": "音游",
"color": "#f74b18"
}
],
"is_official": false
},
{
"name": "CanrotBot",
"desc": "有很多实用功能的bot,也有很多没什么用的娱乐功能;接入了大模型,并且有一部分功能可以被大模型调用。主打一个全都有(",
"author_id": 18070676,
"homepage": "https://github.com/wangyw15/CanrotBot",
"tags": [],
"is_official": false
},
{
"name": "Mio澪",
"desc": "超可爱多功能Qbot",
"author_id": 50508678,
"homepage": "https://github.com/EienSakura/mio",
"tags": [
{
"label": "娱乐",
"color": "#ea5252"
}
],
"is_official": false
},
{
"name": "AntiFraudBot",
"desc": "反诈机器人",
"author_id": 104713034,
"homepage": "https://github.com/itsevin/AntiFraudBot",
"tags": [
{
"label": "反诈",
"color": "#ea5252"
}
],
"is_official": false
},
{
"name": "Nekro Agent Bot",
"desc": "基于生成式人工智能与沙盒技术的 Nekro Agent 代理执行 AI 机器人,支持聊天、识图、通用文件处理等扩展能力,提供了 WebUI 维护界面、一键部署脚本",
"author_id": 57167362,
"homepage": "https://github.com/KroMiose/nekro-agent",
"tags": [
{
"label": "聊天",
"color": "#c65856"
},
{
"label": "大模型",
"color": "#46a34c"
},
{
"label": "WebUI",
"color": "#4a96c6"
}
],
"is_official": false
},
{
"name": "PickStarsBot",
"desc": "欢迎使用PickStarsBot!这是一款基于NoneBot2构建的智能QQ机器人,提供丰富的功能,包括一言、历史上的今天、60秒早报等,快来试试吧!",
"author_id": 183461085,
"homepage": "https://github.com/PickStars308/PickStarsBot",
"tags": [],
"is_official": false
},
{
"name": "LiteBot",
"desc": "Web功能/MC/数据功能Bot",
"author_id": 67693593,
"homepage": "https://github.com/LiteSuggarDEV/LiteBot-NEO/",
"tags": [
{
"label": "Minecraft",
"color": "#ea5252"
},
{
"label": "Web",
"color": "#ea5252"
}
],
"is_official": false
},
{
"name": "Muicebot",
"desc": "Muice-Chatbot 的 Nonebot2 实现,支持调用主流大模型,支持 Function Call 和内置 MCP Host 实现",
"author_id": 72406624,
"homepage": "https://github.com/Moemu/MuiceBot",
"tags": [
{
"label": "LLM",
"color": "#3e97ff"
}
],
"is_official": false
},
{
"name": "nsybot",
"desc": "定时获取推特/bilibili等平台用户文章并推送到QQ群",
"author_id": 148176849,
"homepage": "https://github.com/AhsokaTano26/nsybot",
"tags": [],
"is_official": false
},
{
"name": "Amrita",
"desc": "LLM聊天机器人框架",
"author_id": 67693593,
"homepage": "https://github.com/LiteSuggarDEV/Amrita",
"tags": [
{
"label": "聊天",
"color": "#ea5252"
},
{
"label": "LLM",
"color": "#5c86db"
},
{
"label": "快捷部署",
"color": "#eebe0b"
}
],
"is_official": false
},
{
"name": "Rosmontis.io",
"desc": "简单的机器人",
"author_id": 225668725,
"homepage": "https://github.com/com-wuqi/Rosmontis.io",
"tags": [
{
"label": "可爱",
"color": "#ea5252"
}
],
"is_official": false
},
}
]
+7 -7
View File
@@ -4,7 +4,7 @@
"project_link": "",
"name": "None",
"desc": "None 驱动器",
"author_id": 42488585,
"author": "yanyongyu",
"homepage": "/docs/advanced/driver",
"tags": [],
"is_official": true
@@ -14,7 +14,7 @@
"project_link": "nonebot2[fastapi]",
"name": "FastAPI",
"desc": "FastAPI 驱动器",
"author_id": 42488585,
"author": "yanyongyu",
"homepage": "/docs/advanced/driver",
"tags": [],
"is_official": true
@@ -24,7 +24,7 @@
"project_link": "nonebot2[quart]",
"name": "Quart",
"desc": "Quart 驱动器",
"author_id": 42488585,
"author": "yanyongyu",
"homepage": "/docs/advanced/driver",
"tags": [],
"is_official": true
@@ -34,7 +34,7 @@
"project_link": "nonebot2[httpx]",
"name": "HTTPX",
"desc": "HTTPX 驱动器",
"author_id": 42488585,
"author": "yanyongyu",
"homepage": "/docs/advanced/driver",
"tags": [],
"is_official": true
@@ -44,7 +44,7 @@
"project_link": "nonebot2[websockets]",
"name": "websockets",
"desc": "websockets 驱动器",
"author_id": 42488585,
"author": "yanyongyu",
"homepage": "/docs/advanced/driver",
"tags": [],
"is_official": true
@@ -54,9 +54,9 @@
"project_link": "nonebot2[aiohttp]",
"name": "AIOHTTP",
"desc": "AIOHTTP 驱动器",
"author_id": 42488585,
"author": "yanyongyu",
"homepage": "/docs/advanced/driver",
"tags": [],
"is_official": true
},
}
]
File diff suppressed because it is too large Load Diff
+2491
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
[tool.poetry]
name = "nonebot-pydantic-v1"
version = "0.1.0"
description = "Private pydantic v1 test env for nonebot"
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
[tool.poetry.dependencies]
python = "^3.9"
[tool.poetry.group.dev.dependencies]
pydantic = "^1.0.0"
nonebot-test = { path = "../test/", develop = false }
nonebot2 = { path = "../../", extras = ["all"], develop = true }
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
+2566
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
[tool.poetry]
name = "nonebot-pydantic-v2"
version = "0.1.0"
description = "Private pydantic v2 test env for nonebot"
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
[tool.poetry.dependencies]
python = "^3.9"
[tool.poetry.group.dev.dependencies]
pydantic = "^2.0.0"
nonebot-test = { path = "../test/", develop = false }
nonebot2 = { path = "../../", extras = ["all"], develop = true }
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
+1
View File
@@ -0,0 +1 @@
# fake file to make project installable
+1039
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
[tool.poetry]
name = "nonebot-test"
version = "0.1.0"
description = "Private test env for nonebot"
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
packages = [{ include = "nonebot-test.py" }]
[tool.poetry.dependencies]
python = "^3.9"
nonebug = "^0.3.7"
wsproto = "^1.2.0"
pytest-cov = "^5.0.0"
pytest-xdist = "^3.0.2"
pytest-asyncio = "^0.23.2"
werkzeug = ">=2.3.6,<4.0.0"
coverage-conditional-plugin = "^0.9.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
+37 -39
View File
@@ -39,24 +39,22 @@
- `require` => {ref}``require` <nonebot.plugin.load.require>`
FrontMatter:
mdx:
format: md
sidebar_position: 0
description: nonebot 模块
"""
from importlib.metadata import version
import os
from typing import Any, TypeVar, overload
from importlib.metadata import version
from typing import Any, Union, TypeVar, Optional, overload
import loguru
from nonebot.adapters import Adapter, Bot
from nonebot.compat import model_dump
from nonebot.config import DOTENV_TYPE, Config, Env
from nonebot.drivers import ASGIMixin, Driver, combine_driver
from nonebot.log import logger as logger
from nonebot.adapters import Bot, Adapter
from nonebot.config import DOTENV_TYPE, Env, Config
from nonebot.utils import escape_tag, resolve_dot_notation
from nonebot.drivers import Driver, ASGIMixin, combine_driver
try:
__version__ = version("nonebot2")
@@ -65,7 +63,7 @@ except Exception: # pragma: no cover
A = TypeVar("A", bound=Adapter)
_driver: Driver | None = None
_driver: Optional[Driver] = None
def get_driver() -> Driver:
@@ -112,7 +110,7 @@ def get_adapter(name: type[A]) -> A:
"""
def get_adapter(name: str | type[Adapter]) -> Adapter:
def get_adapter(name: Union[str, type[Adapter]]) -> Adapter:
"""获取已注册的 {ref}`nonebot.adapters.Adapter` 实例。
异常:
@@ -190,13 +188,13 @@ def get_asgi() -> Any:
```
"""
driver = get_driver()
assert isinstance(driver, ASGIMixin), (
"asgi object is only available for asgi driver"
)
assert isinstance(
driver, ASGIMixin
), "asgi object is only available for asgi driver"
return driver.asgi
def get_bot(self_id: str | None = None) -> Bot:
def get_bot(self_id: Optional[str] = None) -> Bot:
"""获取一个连接到 NoneBot 的 {ref}`nonebot.adapters.Bot` 对象。
当提供 `self_id` 时,此函数是 `get_bots()[self_id]` 的简写;
@@ -277,7 +275,7 @@ def _log_patcher(record: "loguru.Record"):
)
def init(*, _env_file: DOTENV_TYPE | None = None, **kwargs: Any) -> None:
def init(*, _env_file: Optional[DOTENV_TYPE] = None, **kwargs: Any) -> None:
"""初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。
NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。
@@ -337,31 +335,31 @@ def run(*args: Any, **kwargs: Any) -> None:
get_driver().run(*args, **kwargs)
from nonebot.plugin import CommandGroup as CommandGroup
from nonebot.plugin import MatcherGroup as MatcherGroup
from nonebot.plugin import get_available_plugin_names as get_available_plugin_names
from nonebot.plugin import get_loaded_plugins as get_loaded_plugins
from nonebot.plugin import get_plugin as get_plugin
from nonebot.plugin import get_plugin_by_module_name as get_plugin_by_module_name
from nonebot.plugin import get_plugin_config as get_plugin_config
from nonebot.plugin import load_all_plugins as load_all_plugins
from nonebot.plugin import load_builtin_plugin as load_builtin_plugin
from nonebot.plugin import load_builtin_plugins as load_builtin_plugins
from nonebot.plugin import load_from_json as load_from_json
from nonebot.plugin import load_from_toml as load_from_toml
from nonebot.plugin import load_plugin as load_plugin
from nonebot.plugin import load_plugins as load_plugins
from nonebot.plugin import on as on
from nonebot.plugin import on_command as on_command
from nonebot.plugin import on_endswith as on_endswith
from nonebot.plugin import on_fullmatch as on_fullmatch
from nonebot.plugin import on_keyword as on_keyword
from nonebot.plugin import on_message as on_message
from nonebot.plugin import on_metaevent as on_metaevent
from nonebot.plugin import on_notice as on_notice
from nonebot.plugin import on_regex as on_regex
from nonebot.plugin import on_request as on_request
from nonebot.plugin import on_shell_command as on_shell_command
from nonebot.plugin import on_startswith as on_startswith
from nonebot.plugin import on_type as on_type
from nonebot.plugin import require as require
from nonebot.plugin import on_regex as on_regex
from nonebot.plugin import on_notice as on_notice
from nonebot.plugin import get_plugin as get_plugin
from nonebot.plugin import on_command as on_command
from nonebot.plugin import on_keyword as on_keyword
from nonebot.plugin import on_message as on_message
from nonebot.plugin import on_request as on_request
from nonebot.plugin import load_plugin as load_plugin
from nonebot.plugin import on_endswith as on_endswith
from nonebot.plugin import CommandGroup as CommandGroup
from nonebot.plugin import MatcherGroup as MatcherGroup
from nonebot.plugin import load_plugins as load_plugins
from nonebot.plugin import on_fullmatch as on_fullmatch
from nonebot.plugin import on_metaevent as on_metaevent
from nonebot.plugin import on_startswith as on_startswith
from nonebot.plugin import load_from_json as load_from_json
from nonebot.plugin import load_from_toml as load_from_toml
from nonebot.plugin import load_all_plugins as load_all_plugins
from nonebot.plugin import on_shell_command as on_shell_command
from nonebot.plugin import get_plugin_config as get_plugin_config
from nonebot.plugin import get_loaded_plugins as get_loaded_plugins
from nonebot.plugin import load_builtin_plugin as load_builtin_plugin
from nonebot.plugin import load_builtin_plugins as load_builtin_plugins
from nonebot.plugin import get_plugin_by_module_name as get_plugin_by_module_name
from nonebot.plugin import get_available_plugin_names as get_available_plugin_names
+1 -3
View File
@@ -3,15 +3,13 @@
使用 {ref}`nonebot.drivers.Driver.register_adapter` 注册适配器。
FrontMatter:
mdx:
format: md
sidebar_position: 0
description: nonebot.adapters 模块
"""
from nonebot.internal.adapter import Adapter as Adapter
from nonebot.internal.adapter import Bot as Bot
from nonebot.internal.adapter import Event as Event
from nonebot.internal.adapter import Adapter as Adapter
from nonebot.internal.adapter import Message as Message
from nonebot.internal.adapter import MessageSegment as MessageSegment
from nonebot.internal.adapter import MessageTemplate as MessageTemplate
+56 -151
View File
@@ -3,37 +3,32 @@
为兼容 Pydantic V1 与 V2 版本,定义了一系列兼容函数与类供使用。
FrontMatter:
mdx:
format: md
sidebar_position: 16
description: nonebot.compat 模块
"""
from collections.abc import Callable, Generator
from collections.abc import Generator
from functools import cached_property
from dataclasses import dataclass, is_dataclass
from functools import cached_property, wraps
from typing_extensions import Self, get_args, get_origin, is_typeddict
from typing import (
TYPE_CHECKING,
Annotated,
Any,
Union,
Generic,
Literal,
Protocol,
TypeAlias,
TypeVar,
cast,
get_args,
get_origin,
Callable,
Optional,
Protocol,
Annotated,
overload,
)
from typing_extensions import ParamSpec, Self, is_typeddict
from pydantic import VERSION, BaseModel
from nonebot.typing import origin_is_annotated
T = TypeVar("T")
P = ParamSpec("P")
PYDANTIC_V2 = int(VERSION.split(".", 1)[0]) == 2
@@ -46,35 +41,22 @@ if TYPE_CHECKING:
CVC = TypeVar("CVC", bound=_CustomValidationClass)
ModelDumpIncEx: TypeAlias = (
set[int]
| set[str]
| dict[int, "ModelDumpIncEx"]
| dict[str, "ModelDumpIncEx"]
| None
)
"""Common include/exclude shape accepted by all supported pydantic versions."""
__all__ = (
"DEFAULT_CONFIG",
"PYDANTIC_V2",
"ConfigDict",
"FieldInfo",
"LegacyUnionField",
"ModelField",
"Required",
"PydanticUndefined",
"PydanticUndefinedType",
"Required",
"ConfigDict",
"DEFAULT_CONFIG",
"FieldInfo",
"ModelField",
"TypeAdapter",
"custom_validation",
"field_validator",
"extract_field_info",
"model_fields",
"model_config",
"model_dump",
"model_fields",
"model_validator",
"type_validate_json",
"type_validate_python",
"type_validate_json",
"custom_validation",
)
__autodoc__ = {
@@ -84,13 +66,11 @@ __autodoc__ = {
if PYDANTIC_V2: # pragma: pydantic-v2
from pydantic import Field, GetCoreSchemaHandler
from pydantic import GetCoreSchemaHandler
from pydantic import TypeAdapter as TypeAdapter
from pydantic import field_validator as field_validator
from pydantic import model_validator as model_validator
from pydantic_core import CoreSchema, core_schema
from pydantic._internal._repr import display_as_type
from pydantic.fields import FieldInfo as BaseFieldInfo
from pydantic_core import CoreSchema, core_schema
Required = Ellipsis
"""Alias of Ellipsis for compatibility with pydantic v1"""
@@ -107,18 +87,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
DEFAULT_CONFIG = ConfigDict(extra="allow", arbitrary_types_allowed=True)
"""Default config for validations"""
def _get_legacy_union_field(func: Callable[P, T]) -> Callable[P, T]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
kwargs["union_mode"] = "left_to_right"
return func(*args, **kwargs)
return wrapper
LegacyUnionField = _get_legacy_union_field(Field)
LegacyUnionField.__doc__ = "Mark field to use legacy left to right union mode"
class FieldInfo(BaseFieldInfo): # pyright: ignore[reportGeneralTypeIssues]
class FieldInfo(BaseFieldInfo):
"""FieldInfo class with extra property for compatibility with pydantic v1"""
# make default can be positional argument
@@ -138,20 +107,6 @@ if PYDANTIC_V2: # pragma: pydantic-v2
slots = super().__slots__
return {k: v for k, v in self._attributes_set.items() if k not in slots}
@classmethod
def _inherit_construct(
cls, field_info: BaseFieldInfo | None = None, **kwargs: Any
) -> Self:
init_kwargs = {}
if field_info:
init_kwargs.update(field_info._attributes_set)
init_kwargs.update(kwargs)
instance = cls(**init_kwargs)
if field_info:
instance.metadata = field_info.metadata
return instance
@dataclass
class ModelField:
"""ModelField class for compatibility with pydantic v1"""
@@ -169,7 +124,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
@classmethod
def construct(
cls, name: str, annotation: Any, field_info: FieldInfo | None = None
cls, name: str, annotation: Any, field_info: Optional[FieldInfo] = None
) -> Self:
"""Construct a ModelField from given infos."""
return cls._construct(name, annotation, field_info or FieldInfo())
@@ -224,6 +179,13 @@ if PYDANTIC_V2: # pragma: pydantic-v2
"""Validate the value pass to the field."""
return self.type_adapter.validate_python(value)
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_fields(model: type[BaseModel]) -> list[ModelField]:
"""Get field list of a model."""
@@ -231,7 +193,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
ModelField._construct(
name=name,
annotation=field_info.rebuild_annotation(),
field_info=FieldInfo._inherit_construct(field_info),
field_info=FieldInfo(**extract_field_info(field_info)),
)
for name, field_info in model.model_fields.items()
]
@@ -242,17 +204,16 @@ if PYDANTIC_V2: # pragma: pydantic-v2
def model_dump(
model: BaseModel,
include: ModelDumpIncEx = None,
exclude: ModelDumpIncEx = None,
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(
# Nested types cannot be inferred correctly
include=cast(Any, include),
exclude=cast(Any, exclude),
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
@@ -263,7 +224,7 @@ if PYDANTIC_V2: # pragma: pydantic-v2
"""Validate data with given type."""
return TypeAdapter(type_).validate_python(data)
def type_validate_json(type_: type[T], data: str | bytes) -> T:
def type_validate_json(type_: type[T], data: Union[str, bytes]) -> T:
"""Validate JSON with given type."""
return TypeAdapter(type_).validate_json(data)
@@ -290,8 +251,9 @@ if PYDANTIC_V2: # pragma: pydantic-v2
return class_
else: # pragma: pydantic-v1
from pydantic import Extra
from pydantic import parse_obj_as, parse_raw_as
from pydantic import BaseConfig as PydanticConfig
from pydantic import Extra, parse_obj_as, parse_raw_as, root_validator, validator
from pydantic.fields import FieldInfo as BaseFieldInfo
from pydantic.fields import ModelField as BaseModelField
from pydantic.schema import get_annotation_from_field_info
@@ -317,8 +279,6 @@ else: # pragma: pydantic-v1
extra = Extra.allow
arbitrary_types_allowed = True
from pydantic.fields import Field as LegacyUnionField
class FieldInfo(BaseFieldInfo):
def __init__(self, default: Any = PydanticUndefined, **kwargs: Any):
# preprocess default value to make it compatible with pydantic v2
@@ -327,22 +287,6 @@ else: # pragma: pydantic-v1
default = PydanticUndefined
super().__init__(default, **kwargs)
@classmethod
def _inherit_construct(
cls, field_info: BaseFieldInfo | None = None, **kwargs: Any
):
if field_info:
init_kwargs = {
s: getattr(field_info, s)
for s in field_info.__slots__
if s != "extra"
}
init_kwargs.update(field_info.extra)
else:
init_kwargs = {}
init_kwargs.update(kwargs)
return cls(**init_kwargs)
class ModelField(BaseModelField):
@classmethod
def _construct(cls, name: str, annotation: Any, field_info: FieldInfo) -> Self:
@@ -362,7 +306,7 @@ else: # pragma: pydantic-v1
@classmethod
def construct(
cls, name: str, annotation: Any, field_info: FieldInfo | None = None
cls, name: str, annotation: Any, field_info: Optional[FieldInfo] = None
) -> Self:
"""Construct a ModelField from given infos.
@@ -387,7 +331,7 @@ else: # pragma: pydantic-v1
self,
type: type[T],
*,
config: ConfigDict | None = ...,
config: Optional[ConfigDict] = ...,
) -> None: ...
@overload
@@ -395,14 +339,14 @@ else: # pragma: pydantic-v1
self,
type: Any,
*,
config: ConfigDict | None = ...,
config: Optional[ConfigDict] = ...,
) -> None: ...
def __init__(
self,
type: Any,
*,
config: ConfigDict | None = None,
config: Optional[ConfigDict] = None,
) -> None:
self.type = type
self.config = config
@@ -410,46 +354,17 @@ else: # pragma: pydantic-v1
def validate_python(self, value: Any) -> T:
return type_validate_python(self.type, value)
def validate_json(self, value: str | bytes) -> T:
def validate_json(self, value: Union[str, bytes]) -> T:
return type_validate_json(self.type, value)
@overload
def field_validator(
field: str,
/,
*fields: str,
mode: Literal["before"],
check_fields: bool | None = None,
): ...
def extract_field_info(field_info: BaseFieldInfo) -> dict[str, Any]:
"""Get FieldInfo init kwargs from a FieldInfo instance."""
@overload
def field_validator(
field: str,
/,
*fields: str,
mode: Literal["after"] = ...,
check_fields: bool | None = None,
): ...
def field_validator(
field: str,
/,
*fields: str,
mode: Literal["before", "after"] = "after",
check_fields: bool | None = None,
):
if mode == "before":
return validator(
field,
*fields,
pre=True,
check_fields=check_fields or True,
allow_reuse=True,
)
else:
return validator(
field, *fields, check_fields=check_fields or True, allow_reuse=True
)
kwargs = {
s: getattr(field_info, s) for s in field_info.__slots__ if s != "extra"
}
kwargs.update(field_info.extra)
return kwargs
def model_fields(model: type[BaseModel]) -> list[ModelField]:
"""Get field list of a model."""
@@ -459,7 +374,9 @@ else: # pragma: pydantic-v1
ModelField._construct(
name=model_field.name,
annotation=model_field.annotation,
field_info=FieldInfo._inherit_construct(model_field.field_info),
field_info=FieldInfo(
**extract_field_info(model_field.field_info),
),
)
for model_field in model.__fields__.values()
]
@@ -470,39 +387,27 @@ else: # pragma: pydantic-v1
def model_dump(
model: BaseModel,
include: ModelDumpIncEx = None,
exclude: ModelDumpIncEx = None,
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=cast(Any, include),
exclude=cast(Any, exclude),
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
)
@overload
def model_validator(*, mode: Literal["before"]): ...
@overload
def model_validator(*, mode: Literal["after"]): ...
def model_validator(*, mode: Literal["before", "after"]):
if mode == "before":
return root_validator(pre=True, allow_reuse=True)
else:
return root_validator(skip_on_failure=True, allow_reuse=True)
def type_validate_python(type_: type[T], data: Any) -> T:
"""Validate data with given type."""
return parse_obj_as(type_, data)
def type_validate_json(type_: type[T], data: str | bytes) -> T:
def type_validate_json(type_: type[T], data: Union[str, bytes]) -> T:
"""Validate JSON with given type."""
return parse_raw_as(type_, data)
+72 -97
View File
@@ -7,40 +7,40 @@ NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及
详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
FrontMatter:
mdx:
format: md
sidebar_position: 1
description: nonebot.config 模块
"""
import os
import abc
from collections.abc import Mapping
import json
from pathlib import Path
from datetime import timedelta
from ipaddress import IPv4Address
import json
import os
from pathlib import Path
from typing import TYPE_CHECKING, Any, TypeAlias, get_args, get_origin
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, Union, Optional
from typing_extensions import TypeAlias, get_args, get_origin
from dotenv import dotenv_values
from pydantic import BaseModel, Field
from pydantic import Field, BaseModel
from pydantic.networks import IPvAnyAddress
from nonebot.log import logger
from nonebot.typing import origin_is_union
from nonebot.utils import deep_update, type_is_complex, lenient_issubclass
from nonebot.compat import (
PYDANTIC_V2,
ConfigDict,
LegacyUnionField,
ModelField,
PydanticUndefined,
PydanticUndefinedType,
model_config,
model_fields,
)
from nonebot.log import logger
from nonebot.typing import origin_is_union
from nonebot.utils import deep_update, lenient_issubclass, type_is_complex
DOTENV_TYPE: TypeAlias = Path | str | list[Path | str] | tuple[Path | str, ...]
DOTENV_TYPE: TypeAlias = Union[
Path, str, list[Union[Path, str]], tuple[Union[Path, str], ...]
]
ENV_FILE_SENTINEL = Path("")
@@ -49,7 +49,7 @@ class SettingsError(ValueError): ...
class BaseSettingsSource(abc.ABC):
def __init__(self, settings_cls: type[BaseModel]) -> None:
def __init__(self, settings_cls: type["BaseSettings"]) -> None:
self.settings_cls = settings_cls
@property
@@ -65,7 +65,7 @@ class InitSettingsSource(BaseSettingsSource):
__slots__ = ("init_kwargs",)
def __init__(
self, settings_cls: type[BaseModel], 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)
@@ -80,17 +80,33 @@ class InitSettingsSource(BaseSettingsSource):
class DotEnvSettingsSource(BaseSettingsSource):
def __init__(
self,
settings_cls: type[BaseModel],
env_file: DOTENV_TYPE | None,
env_file_encoding: str,
case_sensitive: bool | None = False,
env_nested_delimiter: str | None = None,
settings_cls: type["BaseSettings"],
env_file: Optional[DOTENV_TYPE] = ENV_FILE_SENTINEL,
env_file_encoding: Optional[str] = None,
case_sensitive: Optional[bool] = None,
env_nested_delimiter: Optional[str] = None,
) -> None:
super().__init__(settings_cls)
self.env_file = env_file
self.env_file_encoding = env_file_encoding
self.case_sensitive = case_sensitive
self.env_nested_delimiter = env_nested_delimiter
self.env_file = (
env_file
if env_file is not ENV_FILE_SENTINEL
else self.config.get("env_file", (".env",))
)
self.env_file_encoding = (
env_file_encoding
if env_file_encoding is not None
else self.config.get("env_file_encoding", "utf-8")
)
self.case_sensitive = (
case_sensitive
if case_sensitive is not None
else self.config.get("case_sensitive", False)
)
self.env_nested_delimiter = (
env_nested_delimiter
if env_nested_delimiter is not None
else self.config.get("env_nested_delimiter", None)
)
def _apply_case_sensitive(self, var_name: str) -> str:
return var_name if self.case_sensitive else var_name.lower()
@@ -105,17 +121,17 @@ class DotEnvSettingsSource(BaseSettingsSource):
return False, False
def _parse_env_vars(
self, env_vars: Mapping[str, str | None]
) -> dict[str, str | None]:
self, env_vars: Mapping[str, Optional[str]]
) -> dict[str, Optional[str]]:
return {
self._apply_case_sensitive(key): value for key, value in env_vars.items()
}
def _read_env_file(self, file_path: Path) -> dict[str, str | None]:
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, str | None]:
def _read_env_files(self) -> dict[str, Optional[str]]:
env_files = self.env_file
if env_files is None:
return {}
@@ -123,14 +139,16 @@ class DotEnvSettingsSource(BaseSettingsSource):
if isinstance(env_files, (str, os.PathLike)):
env_files = [env_files]
dotenv_vars: dict[str, str | None] = {}
dotenv_vars: dict[str, Optional[str]] = {}
for env_file in env_files:
env_path = Path(env_file).expanduser()
if env_path.is_file():
dotenv_vars.update(self._read_env_file(env_path))
return dotenv_vars
def _next_field(self, field: ModelField | None, key: str) -> ModelField | None:
def _next_field(
self, field: Optional[ModelField], key: str
) -> Optional[ModelField]:
if not field or origin_is_union(get_origin(field.annotation)):
return None
elif field.annotation and lenient_issubclass(field.annotation, BaseModel):
@@ -142,8 +160,8 @@ class DotEnvSettingsSource(BaseSettingsSource):
def _explode_env_vars(
self,
field: ModelField,
env_vars: dict[str, str | None],
env_file_vars: dict[str, str | None],
env_vars: dict[str, Optional[str]],
env_file_vars: dict[str, Optional[str]],
) -> dict[str, Any]:
if self.env_nested_delimiter is None:
return {}
@@ -155,11 +173,12 @@ class DotEnvSettingsSource(BaseSettingsSource):
continue
# delete from file vars when used
env_file_vars.pop(env_name, None)
if env_name in env_file_vars:
del env_file_vars[env_name]
_, *keys, last_key = env_name.split(self.env_nested_delimiter)
env_var = result
target_field: ModelField | None = field
target_field: Optional[ModelField] = field
for key in keys:
target_field = self._next_field(target_field, key)
env_var = env_var.setdefault(key, {})
@@ -192,33 +211,12 @@ class DotEnvSettingsSource(BaseSettingsSource):
for field in model_fields(self.settings_cls):
field_name = field.name
env_name = self._apply_case_sensitive(field_name)
alias_name = field.field_info.alias
alias_env_name = (
None if alias_name is None else self._apply_case_sensitive(alias_name)
)
# pydantic use alias name to validate if exist
if alias_name is not None:
field_name = alias_name
# try get values from env vars
env_val = env_vars.get(env_name, PydanticUndefined)
alias_env_val = (
PydanticUndefined
if alias_env_name is None
else env_vars.get(alias_env_name, PydanticUndefined)
)
# alias env value has higher priority
env_val = (
env_val
if isinstance(alias_env_val, PydanticUndefinedType)
else alias_env_val
)
# delete from file vars when used
if env_name in env_file_vars:
del env_file_vars[env_name]
if alias_env_name is not None and alias_env_name in env_file_vars:
del env_file_vars[alias_env_name]
is_complex, allow_parse_failure = self._field_is_complex(field)
if is_complex:
@@ -288,18 +286,18 @@ class DotEnvSettingsSource(BaseSettingsSource):
if PYDANTIC_V2: # pragma: pydantic-v2
class SettingsConfig(ConfigDict, total=False):
env_file: DOTENV_TYPE | None
env_file: Optional[DOTENV_TYPE]
env_file_encoding: str
case_sensitive: bool
env_nested_delimiter: str | None
env_nested_delimiter: Optional[str]
else: # pragma: pydantic-v1
class SettingsConfig(ConfigDict):
env_file: DOTENV_TYPE | None
env_file: Optional[DOTENV_TYPE]
env_file_encoding: str
case_sensitive: bool
env_nested_delimiter: str | None
env_nested_delimiter: Optional[str]
class BaseSettings(BaseModel):
@@ -327,53 +325,30 @@ class BaseSettings(BaseModel):
def __init__(
__settings_self__, # pyright: ignore[reportSelfClsParameterName]
_env_file: DOTENV_TYPE | None = ENV_FILE_SENTINEL,
_env_file_encoding: str | None = None,
_env_nested_delimiter: str | None = None,
_env_file: Optional[DOTENV_TYPE] = ENV_FILE_SENTINEL,
_env_file_encoding: Optional[str] = None,
_env_nested_delimiter: Optional[str] = None,
**values: Any,
) -> None:
settings_config = model_config(__settings_self__.__class__)
env_file = (
_env_file
if _env_file is not ENV_FILE_SENTINEL
else settings_config.get("env_file", (".env",))
)
env_file_encoding = (
_env_file_encoding
if _env_file_encoding is not None
else settings_config.get("env_file_encoding", "utf-8")
)
env_nested_delimiter = (
_env_nested_delimiter
if _env_nested_delimiter is not None
else settings_config.get("env_nested_delimiter", None)
)
super().__init__(
**__settings_self__._settings_build_values(
__settings_self__.__class__,
values,
env_file=env_file,
env_file_encoding=env_file_encoding,
env_nested_delimiter=env_nested_delimiter,
env_file=_env_file,
env_file_encoding=_env_file_encoding,
env_nested_delimiter=_env_nested_delimiter,
)
)
__settings_self__._env_file = env_file
__settings_self__._env_file_encoding = env_file_encoding
__settings_self__._env_nested_delimiter = env_nested_delimiter
@staticmethod
def _settings_build_values(
settings_cls: type[BaseModel],
self,
init_kwargs: dict[str, Any],
env_file: DOTENV_TYPE | None,
env_file_encoding: str,
env_nested_delimiter: str | None,
env_file: Optional[DOTENV_TYPE] = None,
env_file_encoding: Optional[str] = None,
env_nested_delimiter: Optional[str] = None,
) -> dict[str, Any]:
init_settings = InitSettingsSource(settings_cls, init_kwargs=init_kwargs)
init_settings = InitSettingsSource(self.__class__, init_kwargs=init_kwargs)
env_settings = DotEnvSettingsSource(
settings_cls,
self.__class__,
env_file=env_file,
env_file_encoding=env_file_encoding,
env_nested_delimiter=env_nested_delimiter,
@@ -404,7 +379,7 @@ class Config(BaseSettings):
"""
if TYPE_CHECKING:
_env_file: DOTENV_TYPE | None = ".env", ".env.prod"
_env_file: Optional[DOTENV_TYPE] = ".env", ".env.prod"
# nonebot configs
driver: str = "~fastapi"
@@ -420,7 +395,7 @@ class Config(BaseSettings):
"""NoneBot {ref}`nonebot.drivers.ReverseDriver` 服务端监听的 IP/主机名。"""
port: int = Field(default=8080, ge=1, le=65535)
"""NoneBot {ref}`nonebot.drivers.ReverseDriver` 服务端监听的端口。"""
log_level: int | str = LegacyUnionField(default="INFO")
log_level: Union[int, str] = "INFO"
"""NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称。
参考 [记录日志](https://nonebot.dev/docs/appendices/log)[loguru 日志等级](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
@@ -437,7 +412,7 @@ class Config(BaseSettings):
"""
# bot connection configs
api_timeout: float | None = 30.0
api_timeout: Optional[float] = 30.0
"""API 请求超时时间,单位: 秒。"""
# bot runtime configs
-6
View File
@@ -1,8 +1,6 @@
"""本模块包含了 NoneBot 事件处理过程中使用到的常量。
FrontMatter:
mdx:
format: md
sidebar_position: 9
description: nonebot.consts 模块
"""
@@ -22,10 +20,6 @@ REJECT_TARGET: Literal["_current_target"] = "_current_target"
"""当前 `reject` 目标存储 key"""
REJECT_CACHE_TARGET: Literal["_next_target"] = "_next_target"
"""下一个 `reject` 目标存储 key"""
PAUSE_PROMPT_RESULT_KEY: Literal["_pause_result"] = "_pause_result"
"""`pause` prompt 发送结果存储 key"""
REJECT_PROMPT_RESULT_KEY: Literal["_reject_{key}_result"] = "_reject_{key}_result"
"""`reject` prompt 发送结果存储 key"""
# used by Rule
PREFIX_KEY: Literal["_prefix"] = "_prefix"
+22 -59
View File
@@ -1,32 +1,22 @@
"""本模块模块实现了依赖注入的定义与处理。
FrontMatter:
mdx:
format: md
sidebar_position: 0
description: nonebot.dependencies 模块
"""
import abc
from collections.abc import Awaitable, Callable, Iterable
from dataclasses import dataclass, field
from functools import partial
import asyncio
import inspect
from typing import Any, Generic, TypeVar, cast
from dataclasses import field, dataclass
from collections.abc import Iterable, Awaitable
from typing import Any, Generic, TypeVar, Callable, Optional, cast
import anyio
from exceptiongroup import BaseExceptionGroup, catch
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
from nonebot.exception import SkippedException
from nonebot.log import logger
from nonebot.typing import _DependentCallable
from nonebot.utils import (
flatten_exception_group,
is_coroutine_callable,
run_coro_with_shield,
run_sync,
)
from nonebot.exception import SkippedException
from nonebot.utils import run_sync, is_coroutine_callable
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
from .utils import check_field_type, get_typed_signature
@@ -47,13 +37,13 @@ class Param(abc.ABC, FieldInfo):
@classmethod
def _check_param(
cls, param: inspect.Parameter, allow_types: tuple[type["Param"], ...]
) -> "Param | None":
) -> Optional["Param"]:
return
@classmethod
def _check_parameterless(
cls, value: Any, allow_types: tuple[type["Param"], ...]
) -> "Param | None":
) -> Optional["Param"]:
return
@abc.abstractmethod
@@ -92,16 +82,7 @@ class Dependent(Generic[R]):
)
async def __call__(self, **kwargs: Any) -> R:
exception: BaseExceptionGroup[SkippedException] | None = None
def _handle_skipped(exc_group: BaseExceptionGroup[SkippedException]):
nonlocal exception
exception = exc_group
# raise one of the exceptions instead
excs = list(flatten_exception_group(exc_group))
logger.trace(f"{self} skipped due to {excs}")
with catch({SkippedException: _handle_skipped}):
try:
# do pre-check
await self.check(**kwargs)
@@ -113,8 +94,9 @@ class Dependent(Generic[R]):
return await cast(Callable[..., Awaitable[R]], self.call)(**values)
else:
return await run_sync(cast(Callable[..., R], self.call))(**values)
raise exception
except SkippedException as e:
logger.trace(f"{self} skipped due to {e}")
raise
@staticmethod
def parse_params(
@@ -167,7 +149,7 @@ class Dependent(Generic[R]):
cls,
*,
call: _DependentCallable[R],
parameterless: Iterable[Any] | None = None,
parameterless: Optional[Iterable[Any]] = None,
allow_types: Iterable[type[Param]],
) -> "Dependent[R]":
allow_types = tuple(allow_types)
@@ -182,17 +164,10 @@ class Dependent(Generic[R]):
return cls(call, params, parameterless_params)
async def check(self, **params: Any) -> None:
if self.parameterless:
async with anyio.create_task_group() as tg:
for param in self.parameterless:
tg.start_soon(partial(param._check, **params))
if self.params:
async with anyio.create_task_group() as tg:
for param in self.params:
tg.start_soon(
partial(cast(Param, param.field_info)._check, **params)
)
await asyncio.gather(*(param._check(**params) for param in self.parameterless))
await asyncio.gather(
*(cast(Param, param.field_info)._check(**params) for param in self.params)
)
async def _solve_field(self, field: ModelField, params: dict[str, Any]) -> Any:
param = cast(Param, field.field_info)
@@ -208,22 +183,10 @@ class Dependent(Generic[R]):
await param._solve(**params)
# solve param values
result: dict[str, Any] = {}
if not self.params:
return result
async def _solve_field(field: ModelField, params: dict[str, Any]) -> None:
value = await self._solve_field(field, params)
result[field.name] = value
async with anyio.create_task_group() as tg:
for field in self.params:
# shield the task to prevent cancellation
# when one of the tasks raises an exception
# this will improve the dependency cache reusability
tg.start_soon(run_coro_with_shield, _solve_field(field, params))
return result
values = await asyncio.gather(
*(self._solve_field(field, params) for field in self.params)
)
return {field.name: value for field, value in zip(self.params, values)}
__autodoc__ = {"CustomConfig": False}
+2 -9
View File
@@ -1,21 +1,17 @@
"""
FrontMatter:
mdx:
format: md
sidebar_position: 1
description: nonebot.dependencies.utils 模块
"""
from collections.abc import Callable
import inspect
from typing import Any, ForwardRef, cast
from typing_extensions import TypeAliasType
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, is_type_alias_type
from nonebot.typing import evaluate_forwardref
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
@@ -48,9 +44,6 @@ def get_typed_annotation(param: inspect.Parameter, globalns: dict[str, Any]) ->
f'Unknown ForwardRef["{param.annotation}"] for parameter {param.name}'
)
return inspect.Parameter.empty
if is_type_alias_type(annotation):
# Python 3.12+ supports PEP 695 TypeAliasType
annotation = cast(TypeAliasType, annotation).__value__
return annotation
+12 -18
View File
@@ -3,41 +3,35 @@
各驱动请继承以下基类。
FrontMatter:
mdx:
format: md
sidebar_position: 0
description: nonebot.drivers 模块
"""
from nonebot.internal.driver import DEFAULT_TIMEOUT as DEFAULT_TIMEOUT
from nonebot.internal.driver import URL as URL
from nonebot.internal.driver import ASGIMixin as ASGIMixin
from nonebot.internal.driver import Cookies as Cookies
from nonebot.internal.driver import Driver as Driver
from nonebot.internal.driver import ForwardDriver as ForwardDriver
from nonebot.internal.driver import ForwardMixin as ForwardMixin
from nonebot.internal.driver import HTTPClientMixin as HTTPClientMixin
from nonebot.internal.driver import HTTPClientSession as HTTPClientSession
from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup
from nonebot.internal.driver import HTTPVersion as HTTPVersion
from nonebot.internal.driver import Mixin as Mixin
from nonebot.internal.driver import Driver as Driver
from nonebot.internal.driver import Cookies as Cookies
from nonebot.internal.driver import Request as Request
from nonebot.internal.driver import Response as Response
from nonebot.internal.driver import ReverseDriver as ReverseDriver
from nonebot.internal.driver import ReverseMixin as ReverseMixin
from nonebot.internal.driver import Timeout as Timeout
from nonebot.internal.driver import ASGIMixin as ASGIMixin
from nonebot.internal.driver import WebSocket as WebSocket
from nonebot.internal.driver import HTTPVersion as HTTPVersion
from nonebot.internal.driver import ForwardMixin as ForwardMixin
from nonebot.internal.driver import ReverseMixin as ReverseMixin
from nonebot.internal.driver import ForwardDriver as ForwardDriver
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
from nonebot.internal.driver import combine_driver as combine_driver
__autodoc__ = {
"DEFAULT_TIMEOUT": True,
"URL": True,
"Cookies": True,
"Request": True,
"Response": True,
"Timeout": True,
"WebSocket": True,
"HTTPVersion": True,
"Driver": True,
+24 -197
View File
@@ -11,43 +11,29 @@ pip install nonebot2[aiohttp]
:::
FrontMatter:
mdx:
format: md
sidebar_position: 2
description: nonebot.drivers.aiohttp 模块
"""
from typing_extensions import override
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING
from typing_extensions import override
from typing import TYPE_CHECKING, Union, Optional
from multidict import CIMultiDict
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 (
URL,
HTTPVersion,
HTTPClientMixin,
HTTPClientSession,
HTTPVersion,
Request,
Response,
WebSocketClientMixin,
combine_driver,
)
from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers.none import Driver as NoneDriver
from nonebot.exception import WebSocketClosed
from nonebot.internal.driver import (
DEFAULT_TIMEOUT,
Cookies,
CookieTypes,
HeaderTypes,
QueryTypes,
Timeout,
TimeoutTypes,
)
from nonebot.log import logger
from nonebot.utils import UNSET, UnsetType, exclude_unset
try:
import aiohttp
@@ -65,11 +51,11 @@ class Session(HTTPClientSession):
params: QueryTypes = None,
headers: HeaderTypes = None,
cookies: CookieTypes = None,
version: str | HTTPVersion = HTTPVersion.H11,
timeout: TimeoutTypes | UnsetType = UNSET,
proxy: str | None = None,
version: Union[str, HTTPVersion] = HTTPVersion.H11,
timeout: Optional[float] = None,
proxy: Optional[str] = None,
):
self._client: aiohttp.ClientSession | None = None
self._client: Optional[aiohttp.ClientSession] = None
self._params = URL.build(query=params).query if params is not None else None
@@ -88,32 +74,7 @@ class Session(HTTPClientSession):
else:
raise RuntimeError(f"Unsupported HTTP version: {version}")
_timeout = None
if isinstance(timeout, Timeout):
timeout_kwargs: dict[str, float | None] = exclude_unset(
{
"total": timeout.total,
"connect": timeout.connect,
"sock_read": timeout.read,
}
)
if timeout_kwargs:
_timeout = aiohttp.ClientTimeout(**timeout_kwargs) # type: ignore
elif timeout is not UNSET:
_timeout = aiohttp.ClientTimeout(connect=timeout, sock_read=timeout)
if _timeout is None:
_timeout = aiohttp.ClientTimeout(
**exclude_unset(
{
"total": DEFAULT_TIMEOUT.total,
"connect": DEFAULT_TIMEOUT.connect,
"sock_read": DEFAULT_TIMEOUT.read,
}
)
)
self._timeout = _timeout
self._timeout = timeout
self._proxy = proxy
@property
@@ -122,29 +83,12 @@ class Session(HTTPClientSession):
raise RuntimeError("Session is not initialized")
return self._client
def _get_timeout(self, timeout: TimeoutTypes | UnsetType) -> aiohttp.ClientTimeout:
_timeout = None
if isinstance(timeout, Timeout):
timeout_kwargs: dict[str, float | None] = exclude_unset(
{
"total": timeout.total,
"connect": timeout.connect,
"sock_read": timeout.read,
}
)
if timeout_kwargs:
_timeout = aiohttp.ClientTimeout(**timeout_kwargs) # type: ignore
elif timeout is not UNSET:
_timeout = aiohttp.ClientTimeout(connect=timeout, sock_read=timeout)
if _timeout is None:
return self._timeout
return _timeout
@override
async def request(self, setup: Request) -> Response:
if self._params:
url = setup.url.with_query({**self._params, **setup.url.query})
params = self._params.copy()
params.update(setup.url.query)
url = setup.url.with_query(params)
else:
url = setup.url
@@ -160,6 +104,8 @@ class Session(HTTPClientSession):
if cookie.value is not None
)
timeout = aiohttp.ClientTimeout(setup.timeout)
async with await self.client.request(
setup.method,
url,
@@ -168,7 +114,7 @@ class Session(HTTPClientSession):
cookies=cookies,
headers=setup.headers,
proxy=setup.proxy or self._proxy,
timeout=self._get_timeout(setup.timeout),
timeout=timeout,
) as response:
return Response(
response.status,
@@ -177,64 +123,6 @@ class Session(HTTPClientSession):
request=setup,
)
@override
async def stream_request(
self,
setup: Request,
*,
chunk_size: int = 1024,
) -> AsyncGenerator[Response, None]:
if self._params:
url = setup.url.with_query({**self._params, **setup.url.query})
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
)
async with 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=self._get_timeout(setup.timeout),
) as response:
response_headers = response.headers.copy()
# aiohttp does not guarantee fixed-size chunks; re-chunk to exact size
buffer = bytearray()
async for chunk in response.content.iter_chunked(chunk_size):
if not chunk:
continue
buffer.extend(chunk)
while len(buffer) >= chunk_size:
out = bytes(buffer[:chunk_size])
del buffer[:chunk_size]
yield Response(
response.status,
headers=response_headers,
content=out,
request=setup,
)
if buffer:
yield Response(
response.status,
headers=response_headers,
content=bytes(buffer),
request=setup,
)
@override
async def setup(self) -> None:
if self._client is not None:
@@ -270,17 +158,6 @@ class Mixin(HTTPClientMixin, WebSocketClientMixin):
async with self.get_session() as session:
return await session.request(setup)
@override
async def stream_request(
self,
setup: Request,
*,
chunk_size: int = 1024,
) -> AsyncGenerator[Response, None]:
async with self.get_session() as session:
async for response in session.stream_request(setup, chunk_size=chunk_size):
yield response
@override
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
@@ -291,59 +168,13 @@ class Mixin(HTTPClientMixin, WebSocketClientMixin):
else:
raise RuntimeError(f"Unsupported HTTP version: {setup.version}")
timeout = None
if isinstance(setup.timeout, Timeout):
timeout_kwargs: dict[str, float | None] = exclude_unset(
{
"ws_receive": setup.timeout.read,
"ws_close": (
setup.timeout.total
if setup.timeout.close is UNSET
else setup.timeout.close
),
}
)
if timeout_kwargs:
timeout = aiohttp.ClientWSTimeout(**timeout_kwargs)
elif setup.timeout is not UNSET:
timeout = aiohttp.ClientWSTimeout(
ws_receive=setup.timeout, # type: ignore
ws_close=setup.timeout, # type: ignore
)
if timeout is None:
timeout = aiohttp.ClientWSTimeout(
**exclude_unset(
{
"ws_receive": DEFAULT_TIMEOUT.read,
"ws_close": (
DEFAULT_TIMEOUT.total
if DEFAULT_TIMEOUT.close is UNSET
else DEFAULT_TIMEOUT.close
),
}
)
)
heartbeat = None
if setup.ping_interval is not UNSET:
heartbeat = setup.ping_interval
if isinstance(setup.timeout, Timeout) and setup.timeout.ping is not UNSET:
logger.warning(
"aiohttp driver does not expose a separate ping timeout; "
"the configured ping timeout will be ignored."
)
async with aiohttp.ClientSession(version=version, trust_env=True) as session:
async with session.ws_connect(
setup.url,
method=setup.method,
timeout=timeout,
timeout=setup.timeout or 10,
headers=setup.headers,
proxy=setup.proxy,
autoping=heartbeat is not None,
heartbeat=heartbeat,
) as ws:
yield WebSocket(request=setup, session=session, websocket=ws)
@@ -353,9 +184,9 @@ class Mixin(HTTPClientMixin, WebSocketClientMixin):
params: QueryTypes = None,
headers: HeaderTypes = None,
cookies: CookieTypes = None,
version: str | HTTPVersion = HTTPVersion.H11,
timeout: TimeoutTypes | UnsetType = UNSET,
proxy: str | None = None,
version: Union[str, HTTPVersion] = HTTPVersion.H11,
timeout: Optional[float] = None,
proxy: Optional[str] = None,
) -> Session:
return Session(
params=params,
@@ -397,11 +228,7 @@ class WebSocket(BaseWebSocket):
async def _receive(self) -> aiohttp.WSMessage:
msg = await self.websocket.receive()
if msg.type in (
aiohttp.WSMsgType.CLOSE,
aiohttp.WSMsgType.CLOSING,
aiohttp.WSMsgType.CLOSED,
):
if msg.type in (aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSING):
raise WebSocketClosed(self.websocket.close_code or 1006)
return msg
+24 -25
View File
@@ -11,35 +11,34 @@ pip install nonebot2[fastapi]
:::
FrontMatter:
mdx:
format: md
sidebar_position: 1
description: nonebot.drivers.fastapi 模块
"""
import logging
import contextlib
from functools import wraps
import logging
from typing import Any
from typing_extensions import override
from typing import Any, Union, Optional
from pydantic import BaseModel
from nonebot.compat import model_dump, type_validate_python
from nonebot.config import Config as NoneBotConfig
from nonebot.config import Env
from nonebot.drivers import ASGIMixin, HTTPServerSetup, WebSocketServerSetup
from nonebot.drivers import Driver as BaseDriver
from nonebot.drivers import Request as BaseRequest
from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import ASGIMixin
from nonebot.exception import WebSocketClosed
from nonebot.internal.driver import FileTypes
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:
from fastapi import FastAPI, Request, UploadFile, status
from fastapi.responses import Response
from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState
import uvicorn
from fastapi.responses import Response
from fastapi import FastAPI, Request, UploadFile, status
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
except ModuleNotFoundError as e: # pragma: no cover
raise ImportError(
"Please install FastAPI first to use this driver. "
@@ -63,23 +62,23 @@ def catch_closed(func):
class Config(BaseModel):
"""FastAPI 驱动框架设置,详情参考 FastAPI 文档"""
fastapi_openapi_url: str | None = None
fastapi_openapi_url: Optional[str] = None
"""`openapi.json` 地址,默认为 `None` 即关闭"""
fastapi_docs_url: str | None = None
fastapi_docs_url: Optional[str] = None
"""`swagger` 地址,默认为 `None` 即关闭"""
fastapi_redoc_url: str | None = None
fastapi_redoc_url: Optional[str] = None
"""`redoc` 地址,默认为 `None` 即关闭"""
fastapi_include_adapter_schema: bool = True
"""是否包含适配器路由的 schema,默认为 `True`"""
fastapi_reload: bool = False
"""开启/关闭冷重载"""
fastapi_reload_dirs: list[str] | None = None
fastapi_reload_dirs: Optional[list[str]] = None
"""重载监控文件夹列表,默认为 uvicorn 默认值"""
fastapi_reload_delay: float = 0.25
"""重载延迟,默认为 uvicorn 默认值"""
fastapi_reload_includes: list[str] | None = None
fastapi_reload_includes: Optional[list[str]] = None
"""要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
fastapi_reload_excludes: list[str] | None = None
fastapi_reload_excludes: Optional[list[str]] = None
"""不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
fastapi_extra: dict[str, Any] = {}
"""传递给 `FastAPI` 的其他参数。"""
@@ -160,10 +159,10 @@ class Driver(BaseDriver, ASGIMixin):
@override
def run(
self,
host: str | None = None,
port: int | None = None,
host: Optional[str] = None,
port: Optional[int] = None,
*args,
app: str | None = None,
app: Optional[str] = None,
**kwargs,
):
"""使用 `uvicorn` 启动 FastAPI"""
@@ -206,8 +205,8 @@ class Driver(BaseDriver, ASGIMixin):
with contextlib.suppress(Exception):
json = await request.json()
data: dict | None = None
files: list[tuple[str, FileTypes]] | None = None
data: Optional[dict] = None
files: Optional[list[tuple[str, FileTypes]]] = None
with contextlib.suppress(Exception):
form = await request.form()
data = {}
@@ -280,7 +279,7 @@ class FastAPIWebSocket(BaseWebSocket):
await self.websocket.close(code, reason)
@override
async def receive(self) -> str | bytes:
async def receive(self) -> Union[str, bytes]:
# assert self.websocket.application_state == WebSocketState.CONNECTED
msg = await self.websocket.receive()
if msg["type"] == "websocket.disconnect":
+16 -120
View File
@@ -11,38 +11,26 @@ pip install nonebot2[httpx]
:::
FrontMatter:
mdx:
format: md
sidebar_position: 3
description: nonebot.drivers.httpx 模块
"""
from collections.abc import AsyncGenerator
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,
HTTPClientMixin,
HTTPClientSession,
HTTPVersion,
Request,
Response,
HTTPVersion,
HTTPClientMixin,
HTTPClientSession,
combine_driver,
)
from nonebot.drivers.none import Driver as NoneDriver
from nonebot.internal.driver import (
DEFAULT_TIMEOUT,
Cookies,
CookieTypes,
HeaderTypes,
QueryTypes,
Timeout,
TimeoutTypes,
)
from nonebot.utils import UNSET, UnsetType, exclude_unset
try:
import httpx
@@ -60,11 +48,11 @@ class Session(HTTPClientSession):
params: QueryTypes = None,
headers: HeaderTypes = None,
cookies: CookieTypes = None,
version: str | HTTPVersion = HTTPVersion.H11,
timeout: TimeoutTypes | UnsetType = UNSET,
proxy: str | None = None,
version: Union[str, HTTPVersion] = HTTPVersion.H11,
timeout: Optional[float] = None,
proxy: Optional[str] = None,
):
self._client: httpx.AsyncClient | None = None
self._client: Optional[httpx.AsyncClient] = None
self._params = (
tuple(URL.build(query=params).query.items()) if params is not None else None
@@ -74,35 +62,7 @@ class Session(HTTPClientSession):
)
self._cookies = Cookies(cookies)
self._version = HTTPVersion(version)
_timeout = None
if isinstance(timeout, Timeout):
avg_timeout = timeout.total and timeout.total / 4
timeout_kwargs: dict[str, float | None] = exclude_unset(
{
"timeout": avg_timeout,
"connect": timeout.connect,
"read": timeout.read,
}
)
if timeout_kwargs:
_timeout = httpx.Timeout(**timeout_kwargs)
elif timeout is not UNSET:
_timeout = httpx.Timeout(timeout)
if _timeout is None:
avg_timeout = DEFAULT_TIMEOUT.total and DEFAULT_TIMEOUT.total / 4
_timeout = httpx.Timeout(
**exclude_unset(
{
"timeout": avg_timeout,
"connect": DEFAULT_TIMEOUT.connect,
"read": DEFAULT_TIMEOUT.read,
}
)
)
self._timeout = _timeout
self._timeout = timeout
self._proxy = proxy
@property
@@ -111,26 +71,6 @@ class Session(HTTPClientSession):
raise RuntimeError("Session is not initialized")
return self._client
def _get_timeout(self, timeout: TimeoutTypes | UnsetType) -> httpx.Timeout:
_timeout = None
if isinstance(timeout, Timeout):
avg_timeout = timeout.total and timeout.total / 4
timeout_kwargs: dict[str, float | None] = exclude_unset(
{
"timeout": avg_timeout,
"connect": timeout.connect,
"read": timeout.read,
}
)
if timeout_kwargs:
_timeout = httpx.Timeout(**timeout_kwargs)
elif timeout is not UNSET:
_timeout = httpx.Timeout(timeout)
if _timeout is None:
return self._timeout
return _timeout
@override
async def request(self, setup: Request) -> Response:
response = await self.client.request(
@@ -140,11 +80,9 @@ class Session(HTTPClientSession):
data=setup.data,
files=setup.files,
json=setup.json,
# ensure the params priority
params=setup.url.raw_query_string,
headers=tuple(setup.headers.items()),
cookies=setup.cookies.jar,
timeout=self._get_timeout(setup.timeout),
timeout=setup.timeout,
)
return Response(
response.status_code,
@@ -153,35 +91,6 @@ class Session(HTTPClientSession):
request=setup,
)
@override
async def stream_request(
self,
setup: Request,
*,
chunk_size: int = 1024,
) -> AsyncGenerator[Response, None]:
async with self.client.stream(
setup.method,
str(setup.url),
content=setup.content,
data=setup.data,
files=setup.files,
json=setup.json,
# ensure the params priority
params=setup.url.raw_query_string,
headers=tuple(setup.headers.items()),
cookies=setup.cookies.jar,
timeout=self._get_timeout(setup.timeout),
) as response:
response_headers = response.headers.multi_items()
async for chunk in response.aiter_bytes(chunk_size=chunk_size):
yield Response(
response.status_code,
headers=response_headers,
content=chunk,
request=setup,
)
@override
async def setup(self) -> None:
if self._client is not None:
@@ -191,7 +100,7 @@ class Session(HTTPClientSession):
headers=self._headers,
cookies=self._cookies.jar,
http2=self._version == HTTPVersion.H2,
proxy=self._proxy,
proxies=self._proxy,
follow_redirects=True,
)
await self._client.__aenter__()
@@ -220,28 +129,15 @@ class Mixin(HTTPClientMixin):
) as session:
return await session.request(setup)
@override
async def stream_request(
self,
setup: Request,
*,
chunk_size: int = 1024,
) -> AsyncGenerator[Response, None]:
async with self.get_session(
version=setup.version, proxy=setup.proxy
) as session:
async for response in session.stream_request(setup, chunk_size=chunk_size):
yield response
@override
def get_session(
self,
params: QueryTypes = None,
headers: HeaderTypes = None,
cookies: CookieTypes = None,
version: str | HTTPVersion = HTTPVersion.H11,
timeout: TimeoutTypes = None,
proxy: str | None = None,
version: Union[str, HTTPVersion] = HTTPVersion.H11,
timeout: Optional[float] = None,
proxy: Optional[str] = None,
) -> Session:
return Session(
params=params,
+69 -85
View File
@@ -5,24 +5,19 @@
:::
FrontMatter:
mdx:
format: md
sidebar_position: 6
description: nonebot.drivers.none 模块
"""
import signal
import asyncio
import threading
from typing_extensions import override
import anyio
from anyio.abc import TaskGroup
from exceptiongroup import BaseExceptionGroup, catch
from nonebot.config import Config, Env
from nonebot.consts import WINDOWS
from nonebot.drivers import Driver as BaseDriver
from nonebot.log import logger
from nonebot.utils import flatten_exception_group
from nonebot.consts import WINDOWS
from nonebot.config import Env, Config
from nonebot.drivers import Driver as BaseDriver
HANDLED_SIGNALS = (
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
@@ -38,8 +33,8 @@ class Driver(BaseDriver):
def __init__(self, env: Env, config: Config):
super().__init__(env, config)
self.should_exit: anyio.Event = anyio.Event()
self.force_exit: anyio.Event = anyio.Event()
self.should_exit: asyncio.Event = asyncio.Event()
self.force_exit: bool = False
@property
@override
@@ -57,95 +52,84 @@ class Driver(BaseDriver):
def run(self, *args, **kwargs):
"""启动 none driver"""
super().run(*args, **kwargs)
anyio.run(self._serve)
loop = asyncio.get_event_loop()
loop.run_until_complete(self._serve())
async def _serve(self):
async with anyio.create_task_group() as driver_tg:
driver_tg.start_soon(self._handle_signals)
driver_tg.start_soon(self._listen_force_exit, driver_tg)
driver_tg.start_soon(self._handle_lifespan, driver_tg)
async def _handle_signals(self):
try:
with anyio.open_signal_receiver(*HANDLED_SIGNALS) as signal_receiver:
async for sig in signal_receiver:
self.exit(force=self.should_exit.is_set())
except NotImplementedError:
# Windows
for sig in HANDLED_SIGNALS:
signal.signal(sig, self._handle_legacy_signal)
# backport for Windows signal handling
def _handle_legacy_signal(self, sig, frame):
self.exit(force=self.should_exit.is_set())
async def _handle_lifespan(self, tg: TaskGroup):
try:
await self._startup()
if self.should_exit.is_set():
return
await self._listen_exit()
await self._shutdown()
finally:
tg.cancel_scope.cancel()
self._install_signal_handlers()
await self._startup()
if self.should_exit.is_set():
return
await self._main_loop()
await self._shutdown()
async def _startup(self):
def handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
self.should_exit.set()
for exc in flatten_exception_group(exc_group):
logger.opt(colors=True, exception=exc).error(
"<r><bg #f8bbd0>Error occurred while running startup hook."
"</bg #f8bbd0></r>"
)
logger.error(
"<r><bg #f8bbd0>Application startup failed. Exiting.</bg #f8bbd0></r>"
)
with catch({Exception: handle_exception}):
try:
await self._lifespan.startup()
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Application startup failed. "
"Exiting.</bg #f8bbd0></r>"
)
self.should_exit.set()
return
if not self.should_exit.is_set():
logger.info("Application startup completed.")
logger.info("Application startup completed.")
async def _listen_exit(self, tg: TaskGroup | None = None):
async def _main_loop(self):
await self.should_exit.wait()
if tg is not None:
tg.cancel_scope.cancel()
async def _shutdown(self):
logger.info("Shutting down")
logger.info("Waiting for application shutdown. (CTRL+C to force quit)")
error_occurred: bool = False
logger.info("Waiting for application shutdown.")
def handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
nonlocal error_occurred
error_occurred = True
for exc in flatten_exception_group(exc_group):
logger.opt(colors=True, exception=exc).error(
"<r><bg #f8bbd0>Error occurred while running shutdown hook."
"</bg #f8bbd0></r>"
)
logger.error(
"<r><bg #f8bbd0>Application shutdown failed. Exiting.</bg #f8bbd0></r>"
try:
await self._lifespan.shutdown()
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running shutdown function. "
"Ignored!</bg #f8bbd0></r>"
)
with catch({Exception: handle_exception}):
await self._lifespan.shutdown()
for task in asyncio.all_tasks():
if task is not asyncio.current_task() and not task.done():
task.cancel()
await asyncio.sleep(0.1)
if not error_occurred:
logger.info("Application shutdown complete.")
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
if tasks and not self.force_exit:
logger.info("Waiting for tasks to finish. (CTRL+C to force quit)")
while tasks and not self.force_exit:
await asyncio.sleep(0.1)
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
async def _listen_force_exit(self, tg: TaskGroup):
await self.force_exit.wait()
tg.cancel_scope.cancel()
for task in tasks:
task.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
logger.info("Application shutdown complete.")
loop = asyncio.get_event_loop()
loop.stop()
def _install_signal_handlers(self) -> None:
if threading.current_thread() is not threading.main_thread():
# Signals can only be listened to from the main thread.
return
loop = asyncio.get_event_loop()
try:
for sig in HANDLED_SIGNALS:
loop.add_signal_handler(sig, self._handle_exit, sig, None)
except NotImplementedError:
# Windows
for sig in HANDLED_SIGNALS:
signal.signal(sig, self._handle_exit)
def _handle_exit(self, sig, frame):
self.exit(force=self.should_exit.is_set())
def exit(self, force: bool = False):
"""退出 none driver
@@ -156,4 +140,4 @@ class Driver(BaseDriver):
if not self.should_exit.is_set():
self.should_exit.set()
if force:
self.force_exit.set()
self.force_exit = True
+19 -20
View File
@@ -11,37 +11,36 @@ pip install nonebot2[quart]
:::
FrontMatter:
mdx:
format: md
sidebar_position: 5
description: nonebot.drivers.quart 模块
"""
import asyncio
from functools import wraps
from typing import Any, cast
from typing_extensions import override
from typing import Any, Union, Optional, cast
from pydantic import BaseModel
from nonebot.compat import model_dump, type_validate_python
from nonebot.config import Config as NoneBotConfig
from nonebot.config import Env
from nonebot.drivers import ASGIMixin, HTTPServerSetup, WebSocketServerSetup
from nonebot.drivers import Driver as BaseDriver
from nonebot.drivers import Request as BaseRequest
from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import ASGIMixin
from nonebot.exception import WebSocketClosed
from nonebot.internal.driver import FileTypes
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:
from quart import Quart, Request, Response
from quart import Websocket as QuartWebSocket
import uvicorn
from quart import request as _request
from quart.ctx import WebsocketContext
from quart.datastructures import FileStorage
from quart.globals import websocket_ctx
import uvicorn
from quart import Quart, Request, Response
from quart.datastructures import FileStorage
from quart import Websocket as QuartWebSocket
except ModuleNotFoundError as e: # pragma: no cover
raise ImportError(
"Please install Quart first to use this driver. "
@@ -65,13 +64,13 @@ class Config(BaseModel):
quart_reload: bool = False
"""开启/关闭冷重载"""
quart_reload_dirs: list[str] | None = None
quart_reload_dirs: Optional[list[str]] = None
"""重载监控文件夹列表,默认为 uvicorn 默认值"""
quart_reload_delay: float = 0.25
"""重载延迟,默认为 uvicorn 默认值"""
quart_reload_includes: list[str] | None = None
quart_reload_includes: Optional[list[str]] = None
"""要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
quart_reload_excludes: list[str] | None = None
quart_reload_excludes: Optional[list[str]] = None
"""不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
quart_extra: dict[str, Any] = {}
"""传递给 `Quart` 的其他参数。"""
@@ -141,10 +140,10 @@ class Driver(BaseDriver, ASGIMixin):
@override
def run(
self,
host: str | None = None,
port: int | None = None,
host: Optional[str] = None,
port: Optional[int] = None,
*args,
app: str | None = None,
app: Optional[str] = None,
**kwargs,
):
"""使用 `uvicorn` 启动 Quart"""
@@ -257,7 +256,7 @@ class WebSocket(BaseWebSocket):
@override
@catch_closed
async def receive(self) -> str | bytes:
async def receive(self) -> Union[str, bytes]:
return await self.websocket.receive()
@override
+19 -62
View File
@@ -11,35 +11,27 @@ pip install nonebot2[websockets]
:::
FrontMatter:
mdx:
format: md
sidebar_position: 4
description: nonebot.drivers.websockets 模块
"""
from collections.abc import AsyncGenerator, Callable
from contextlib import asynccontextmanager
from functools import wraps
import logging
from types import CoroutineType
from typing import TYPE_CHECKING, Any, TypeVar
from functools import wraps
from contextlib import asynccontextmanager
from typing_extensions import ParamSpec, override
from collections.abc import Coroutine, AsyncGenerator
from typing import TYPE_CHECKING, Any, Union, TypeVar, Callable
from nonebot.drivers import (
DEFAULT_TIMEOUT,
Request,
Timeout,
WebSocketClientMixin,
combine_driver,
)
from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers.none import Driver as NoneDriver
from nonebot.exception import WebSocketClosed
from nonebot.drivers import Request
from nonebot.log import LoguruHandler
from nonebot.utils import UNSET, UnsetType, exclude_unset
from nonebot.exception import WebSocketClosed
from nonebot.drivers.none import Driver as NoneDriver
from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import WebSocketClientMixin, combine_driver
try:
from websockets import ClientConnection, ConnectionClosed, connect
from websockets.exceptions import ConnectionClosed
from websockets.legacy.client import Connect, WebSocketClientProtocol
except ModuleNotFoundError as e: # pragma: no cover
raise ImportError(
"Please install websockets first to use this driver. "
@@ -54,8 +46,8 @@ logger.addHandler(LoguruHandler())
def catch_closed(
func: Callable[P, "CoroutineType[Any, Any, T]"],
) -> Callable[P, "CoroutineType[Any, Any, T]"]:
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:
@@ -77,45 +69,10 @@ class Mixin(WebSocketClientMixin):
@override
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
timeout_kwargs: dict[str, float | None | UnsetType] = {}
if isinstance(setup.timeout, Timeout):
open_timeout = (
setup.timeout.connect or setup.timeout.read or setup.timeout.total
)
timeout_kwargs = {
"open_timeout": open_timeout,
"close_timeout": setup.timeout.close,
"ping_timeout": setup.timeout.ping,
}
elif setup.timeout is not UNSET:
timeout_kwargs = {
"open_timeout": setup.timeout,
"close_timeout": setup.timeout,
}
if not timeout_kwargs:
open_timeout = (
DEFAULT_TIMEOUT.connect or DEFAULT_TIMEOUT.read or DEFAULT_TIMEOUT.total
)
timeout_kwargs = {
"open_timeout": open_timeout,
"close_timeout": DEFAULT_TIMEOUT.close,
"ping_timeout": DEFAULT_TIMEOUT.ping,
}
kwargs = exclude_unset(
{
**timeout_kwargs,
"ping_interval": setup.ping_interval,
}
)
connection = connect(
connection = Connect(
str(setup.url),
additional_headers={**setup.headers, **setup.cookies.as_header(setup)},
proxy=setup.proxy if setup.proxy is not None else True,
**kwargs, # type: ignore
extra_headers={**setup.headers, **setup.cookies.as_header(setup)},
open_timeout=setup.timeout,
)
async with connection as ws:
yield WebSocket(request=setup, websocket=ws)
@@ -125,14 +82,14 @@ class WebSocket(BaseWebSocket):
"""Websockets WebSocket Wrapper"""
@override
def __init__(self, *, request: Request, websocket: ClientConnection):
def __init__(self, *, request: Request, websocket: WebSocketClientProtocol):
super().__init__(request=request)
self.websocket = websocket
@property
@override
def closed(self) -> bool:
return self.websocket.close_code is not None
return self.websocket.closed
@override
async def accept(self):
@@ -144,7 +101,7 @@ class WebSocket(BaseWebSocket):
@override
@catch_closed
async def receive(self) -> str | bytes:
async def receive(self) -> Union[str, bytes]:
return await self.websocket.recv()
@override
+3 -5
View File
@@ -25,13 +25,11 @@ NoneBotException
```
FrontMatter:
mdx:
format: md
sidebar_position: 10
description: nonebot.exception 模块
"""
from typing import Any
from typing import Any, Optional
from nonebot.compat import ModelField
@@ -47,7 +45,7 @@ class NoneBotException(Exception):
class ParserExit(NoneBotException):
"""{ref}`nonebot.rule.shell_command` 处理消息失败时返回的异常。"""
def __init__(self, status: int = 0, message: str | None = None) -> None:
def __init__(self, status: int = 0, message: Optional[str] = None) -> None:
self.status = status
self.message = message
@@ -232,7 +230,7 @@ class DriverException(NoneBotException):
class WebSocketClosed(DriverException):
"""WebSocket 连接已关闭。"""
def __init__(self, code: int, reason: str | None = None) -> None:
def __init__(self, code: int, reason: Optional[str] = None) -> None:
self.code = code
self.reason = reason
+1 -1
View File
@@ -1,6 +1,6 @@
from .adapter import Adapter as Adapter
from .bot import Bot as Bot
from .event import Event as Event
from .adapter import Adapter as Adapter
from .message import Message as Message
from .message import MessageSegment as MessageSegment
from .template import MessageTemplate as MessageTemplate
+5 -5
View File
@@ -1,21 +1,21 @@
import abc
from typing import Any
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from typing import Any
from nonebot.config import Config
from nonebot.internal.driver._lifespan import LIFESPAN_FUNC
from nonebot.internal.driver import (
ASGIMixin,
Driver,
HTTPClientMixin,
HTTPServerSetup,
Request,
Response,
ASGIMixin,
WebSocket,
HTTPClientMixin,
HTTPServerSetup,
WebSocketClientMixin,
WebSocketServerSetup,
)
from nonebot.internal.driver._lifespan import LIFESPAN_FUNC
from .bot import Bot
+33 -87
View File
@@ -1,19 +1,16 @@
import abc
import asyncio
from functools import partial
from typing import TYPE_CHECKING, Any, ClassVar, Protocol
import anyio
from exceptiongroup import BaseExceptionGroup, catch
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional, Protocol
from nonebot.log import logger
from nonebot.config import Config
from nonebot.exception import MockApiException
from nonebot.log import logger
from nonebot.typing import T_CalledAPIHook, T_CallingAPIHook
from nonebot.utils import flatten_exception_group
if TYPE_CHECKING:
from .adapter import Adapter
from .event import Event
from .adapter import Adapter
from .message import Message, MessageSegment
class _ApiCall(Protocol):
@@ -77,51 +74,23 @@ class Bot(abc.ABC):
result: Any = None
skip_calling_api: bool = False
exception: Exception | None = None
if self._calling_api_hook:
logger.debug("Running CallingAPI hooks...")
def _handle_mock_api_exception(
exc_group: BaseExceptionGroup[MockApiException],
) -> None:
nonlocal skip_calling_api, result
excs = [
exc
for exc in flatten_exception_group(exc_group)
if isinstance(exc, MockApiException)
]
if not excs:
return
elif len(excs) > 1:
logger.warning(
"Multiple hooks want to mock API result. Use the first one."
)
exception: Optional[Exception] = None
if coros := [hook(self, api, data) for hook in self._calling_api_hook]:
try:
logger.debug("Running CallingAPI hooks...")
await asyncio.gather(*coros)
except MockApiException as e:
skip_calling_api = True
result = excs[0].result
result = e.result
logger.debug(
f"Calling API {api} is cancelled. Return {result!r} instead."
f"Calling API {api} is cancelled. Return {result} instead."
)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running CallingAPI hook. "
"Running cancelled!</bg #f8bbd0></r>"
)
def _handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
for exc in flatten_exception_group(exc_group):
logger.opt(colors=True, exception=exc).error(
"<r><bg #f8bbd0>Error when running CallingAPI hook. "
"Running cancelled!</bg #f8bbd0></r>"
)
with catch(
{
MockApiException: _handle_mock_api_exception,
Exception: _handle_exception,
}
):
async with anyio.create_task_group() as tg:
for hook in self._calling_api_hook:
tg.start_soon(hook, self, api, data)
if not skip_calling_api:
try:
@@ -129,48 +98,25 @@ class Bot(abc.ABC):
except Exception as e:
exception = e
if self._called_api_hook:
logger.debug("Running CalledAPI hooks...")
def _handle_mock_api_exception(
exc_group: BaseExceptionGroup[MockApiException],
) -> None:
nonlocal result, exception
excs = [
exc
for exc in flatten_exception_group(exc_group)
if isinstance(exc, MockApiException)
]
if not excs:
return
elif len(excs) > 1:
logger.warning(
"Multiple hooks want to mock API result. Use the first one."
)
result = excs[0].result
if coros := [
hook(self, exception, api, data, result) for hook in self._called_api_hook
]:
try:
logger.debug("Running CalledAPI hooks...")
await asyncio.gather(*coros)
except MockApiException as e:
# mock api result
result = e.result
# ignore exception
exception = None
logger.debug(
f"Calling API {api} result is mocked. Return {result} instead."
)
def _handle_exception(exc_group: BaseExceptionGroup[Exception]) -> None:
for exc in flatten_exception_group(exc_group):
logger.opt(colors=True, exception=exc).error(
"<r><bg #f8bbd0>Error when running CalledAPI hook. "
"Running cancelled!</bg #f8bbd0></r>"
)
with catch(
{
MockApiException: _handle_mock_api_exception,
Exception: _handle_exception,
}
):
async with anyio.create_task_group() as tg:
for hook in self._called_api_hook:
tg.start_soon(hook, self, exception, api, data, result)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running CalledAPI hook. "
"Running cancelled!</bg #f8bbd0></r>"
)
if exception:
raise exception
@@ -180,7 +126,7 @@ class Bot(abc.ABC):
async def send(
self,
event: "Event",
message: "str | Message | MessageSegment",
message: Union[str, "Message", "MessageSegment"],
**kwargs: Any,
) -> Any:
"""调用机器人基础发送消息接口
+1 -1
View File
@@ -3,8 +3,8 @@ from typing import Any, TypeVar
from pydantic import BaseModel
from nonebot.compat import PYDANTIC_V2, ConfigDict
from nonebot.utils import DataclassEncoder
from nonebot.compat import PYDANTIC_V2, ConfigDict
from .message import Message
+34 -27
View File
@@ -1,16 +1,18 @@
import abc
from collections.abc import Iterable
from copy import deepcopy
from dataclasses import asdict, dataclass, field
from typing_extensions import Self
from collections.abc import Iterable
from dataclasses import field, asdict, dataclass
from typing import ( # noqa: UP035
Any,
Generic,
SupportsIndex,
Type,
Union,
Generic,
TypeVar,
Optional,
SupportsIndex,
overload,
)
from typing_extensions import Self
from nonebot.compat import custom_validation, type_validate_python
@@ -49,10 +51,10 @@ class MessageSegment(abc.ABC, Generic[TM]):
) -> bool:
return not self == other
def __add__(self, other: str | Self | Iterable[Self]) -> TM:
def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
return self.get_message_class()(self) + other
def __radd__(self, other: str | Self | Iterable[Self]) -> TM:
def __radd__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
return self.get_message_class()(other) + self
@classmethod
@@ -85,7 +87,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
def items(self):
return asdict(self).items()
def join(self, iterable: Iterable[Self | TM]) -> TM:
def join(self: TMS, iterable: Iterable[Union[TMS, TM]]) -> TM:
return self.get_message_class()(self).join(iterable)
def copy(self) -> Self:
@@ -107,7 +109,7 @@ class Message(list[TMS], abc.ABC):
def __init__(
self,
message: str | None | Iterable[TMS] | TMS = None,
message: Union[str, None, Iterable[TMS], TMS] = None,
):
super().__init__()
if message is None:
@@ -122,7 +124,7 @@ class Message(list[TMS], abc.ABC):
self.extend(self._construct(message)) # pragma: no cover
@classmethod
def template(cls, format_string: str | TM) -> MessageTemplate[Self]:
def template(cls, format_string: Union[str, TM]) -> MessageTemplate[Self]:
"""创建消息模板。
用法和 `str.format` 大致相同,支持以 `Message` 对象作为消息模板并输出消息对象。
@@ -175,17 +177,17 @@ class Message(list[TMS], abc.ABC):
raise NotImplementedError
def __add__( # pyright: ignore[reportIncompatibleMethodOverride]
self, other: str | TMS | Iterable[TMS]
self, other: Union[str, TMS, Iterable[TMS]]
) -> Self:
result = self.copy()
result += other
return result
def __radd__(self, other: str | TMS | Iterable[TMS]) -> Self:
def __radd__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
result = self.__class__(other)
return result + self
def __iadd__(self, other: str | TMS | Iterable[TMS]) -> Self:
def __iadd__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
if isinstance(other, str):
self.extend(self._construct(other))
elif isinstance(other, MessageSegment):
@@ -253,8 +255,14 @@ class Message(list[TMS], abc.ABC):
def __getitem__( # pyright: ignore[reportIncompatibleMethodOverride]
self,
args: str | tuple[str, int] | tuple[str, slice] | int | slice,
) -> TMS | Self:
args: Union[
str,
tuple[str, int],
tuple[str, slice],
int,
slice,
],
) -> Union[TMS, Self]:
arg1, arg2 = args if isinstance(args, tuple) else (args, None)
if isinstance(arg1, int) and arg2 is None:
return super().__getitem__(arg1)
@@ -270,7 +278,7 @@ class Message(list[TMS], abc.ABC):
raise ValueError("Incorrect arguments to slice") # pragma: no cover
def __contains__( # pyright: ignore[reportIncompatibleMethodOverride]
self, value: TMS | str
self, value: Union[TMS, str]
) -> bool:
"""检查消息段是否存在
@@ -283,11 +291,11 @@ class Message(list[TMS], abc.ABC):
return next((seg for seg in self if seg.type == value), None) is not None
return super().__contains__(value)
def has(self, value: TMS | str) -> bool:
def has(self, value: Union[TMS, str]) -> bool:
"""{ref}``__contains__` <nonebot.adapters.Message.__contains__>` 相同"""
return value in self
def index(self, value: TMS | str, *args: SupportsIndex) -> int:
def index(self, value: Union[TMS, str], *args: SupportsIndex) -> int:
"""索引消息段
参数:
@@ -307,7 +315,7 @@ class Message(list[TMS], abc.ABC):
return super().index(first_segment, *args)
return super().index(value, *args)
def get(self, type_: str, count: int | None = None) -> Self:
def get(self, type_: str, count: Optional[int] = None) -> Self:
"""获取指定类型的消息段
参数:
@@ -321,9 +329,8 @@ class Message(list[TMS], abc.ABC):
return self[type_]
iterator, filtered = (
(seg for seg in self if seg.type == type_),
self.__class__(),
)
seg for seg in self if seg.type == type_
), self.__class__()
for _ in range(count):
seg = next(iterator, None)
if seg is None:
@@ -331,7 +338,7 @@ class Message(list[TMS], abc.ABC):
filtered.append(seg)
return filtered
def count(self, value: TMS | str) -> int:
def count(self, value: Union[TMS, str]) -> int:
"""计算指定消息段的个数
参数:
@@ -342,7 +349,7 @@ class Message(list[TMS], abc.ABC):
"""
return len(self[value]) if isinstance(value, str) else super().count(value)
def only(self, value: TMS | str) -> bool:
def only(self, value: Union[TMS, str]) -> bool:
"""检查消息中是否仅包含指定消息段
参数:
@@ -356,7 +363,7 @@ class Message(list[TMS], abc.ABC):
return all(seg == value for seg in self)
def append( # pyright: ignore[reportIncompatibleMethodOverride]
self, obj: str | TMS
self, obj: Union[str, TMS]
) -> Self:
"""添加一个消息段到消息数组末尾。
@@ -372,7 +379,7 @@ class Message(list[TMS], abc.ABC):
return self
def extend( # pyright: ignore[reportIncompatibleMethodOverride]
self, obj: Self | Iterable[TMS]
self, obj: Union[Self, Iterable[TMS]]
) -> Self:
"""拼接一个消息数组或多个消息段到消息数组末尾。
@@ -383,7 +390,7 @@ class Message(list[TMS], abc.ABC):
self.append(segment)
return self
def join(self, iterable: Iterable[TMS | Self]) -> Self:
def join(self, iterable: Iterable[Union[TMS, Self]]) -> Self:
"""将多个消息连接并将自身作为分割
参数:
+14 -10
View File
@@ -1,17 +1,21 @@
from _string import formatter_field_name_split # type: ignore
from collections.abc import Callable, Mapping, Sequence
import functools
from string import Formatter
from typing_extensions import TypeAlias
from collections.abc import Mapping, Sequence
from typing import (
TYPE_CHECKING,
Any,
Union,
Generic,
TypeAlias,
TypeVar,
Callable,
Optional,
cast,
overload,
)
from _string import formatter_field_name_split # type: ignore
if TYPE_CHECKING:
from .message import Message, MessageSegment
@@ -47,15 +51,15 @@ class MessageTemplate(Formatter, Generic[TF]):
@overload
def __init__(
self: "MessageTemplate[TM]",
template: str | TM,
template: Union[str, TM],
factory: type[TM],
private_getattr: bool = False,
) -> None: ...
def __init__(
self,
template: str | TM,
factory: type[str] | type[TM] = str,
template: Union[str, TM],
factory: Union[type[str], type[TM]] = str,
private_getattr: bool = False,
) -> None:
self.template: TF = template # type: ignore
@@ -67,7 +71,7 @@ class MessageTemplate(Formatter, Generic[TF]):
return f"MessageTemplate({self.template!r}, factory={self.factory!r})"
def add_format_spec(
self, spec: FormatSpecFunc_T, name: str | None = None
self, spec: FormatSpecFunc_T, name: Optional[str] = None
) -> FormatSpecFunc_T:
name = name or spec.__name__
if name in self.format_specs:
@@ -123,7 +127,7 @@ class MessageTemplate(Formatter, Generic[TF]):
format_string: str,
args: Sequence[Any],
kwargs: Mapping[str, Any],
used_args: set[int | str],
used_args: set[Union[int, str]],
auto_arg_index: int = 0,
) -> tuple[TF, int]:
results: list[Any] = [self.factory()]
@@ -177,7 +181,7 @@ class MessageTemplate(Formatter, Generic[TF]):
def get_field(
self, field_name: str, args: Sequence[Any], kwargs: Mapping[str, Any]
) -> tuple[Any, int | str]:
) -> tuple[Any, Union[int, str]]:
first, rest = formatter_field_name_split(field_name)
obj = self.get_value(first, args, kwargs)
@@ -189,7 +193,7 @@ class MessageTemplate(Formatter, Generic[TF]):
return obj, first
def format_field(self, value: Any, format_spec: str) -> Any:
formatter: FormatSpecFunc | None = self.format_specs.get(format_spec)
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()
method = getattr(segment_class, format_spec, None)
+26 -29
View File
@@ -1,34 +1,31 @@
from .abstract import ASGIMixin as ASGIMixin
from .model import URL as URL
from .model import RawURL as RawURL
from .abstract import Mixin as Mixin
from .model import Cookies as Cookies
from .model import Request as Request
from .abstract import Driver as Driver
from .abstract import ForwardDriver as ForwardDriver
from .model import FileType as FileType
from .model import Response as Response
from .model import DataTypes as DataTypes
from .model import FileTypes as FileTypes
from .model import WebSocket as WebSocket
from .model import FilesTypes as FilesTypes
from .model import QueryTypes as QueryTypes
from .abstract import ASGIMixin as ASGIMixin
from .model import CookieTypes as CookieTypes
from .model import FileContent as FileContent
from .model import HTTPVersion as HTTPVersion
from .model import HeaderTypes as HeaderTypes
from .model import SimpleQuery as SimpleQuery
from .model import ContentTypes as ContentTypes
from .model import QueryVariable as QueryVariable
from .abstract import ForwardMixin as ForwardMixin
from .abstract import ReverseMixin as ReverseMixin
from .abstract import ForwardDriver as ForwardDriver
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 .abstract import Mixin as Mixin
from .abstract import ReverseDriver as ReverseDriver
from .abstract import ReverseMixin as ReverseMixin
from .abstract import WebSocketClientMixin as WebSocketClientMixin
from .combine import combine_driver as combine_driver
from .model import DEFAULT_TIMEOUT as DEFAULT_TIMEOUT
from .model import URL as URL
from .model import ContentTypes as ContentTypes
from .model import Cookies as Cookies
from .model import CookieTypes as CookieTypes
from .model import DataTypes as DataTypes
from .model import FileContent as FileContent
from .model import FilesTypes as FilesTypes
from .model import FileType as FileType
from .model import FileTypes as FileTypes
from .model import HeaderTypes as HeaderTypes
from .model import HTTPServerSetup as HTTPServerSetup
from .model import HTTPVersion as HTTPVersion
from .model import QueryTypes as QueryTypes
from .model import QueryVariable as QueryVariable
from .model import RawURL as RawURL
from .model import Request as Request
from .model import Response as Response
from .model import SimpleQuery as SimpleQuery
from .model import Timeout as Timeout
from .model import TimeoutTypes as TimeoutTypes
from .model import WebSocket as WebSocket
from .model import WebSocketServerSetup as WebSocketServerSetup
from .abstract import WebSocketClientMixin as WebSocketClientMixin
+10 -54
View File
@@ -1,38 +1,20 @@
from collections.abc import Awaitable, Callable, Iterable
from types import TracebackType
from typing import Any, TypeAlias, cast
from collections.abc import Awaitable
from typing_extensions import TypeAlias
from typing import Any, Union, Callable, cast
import anyio
from anyio.abc import TaskGroup
from exceptiongroup import suppress
from nonebot.utils import is_coroutine_callable, run_sync
from nonebot.utils import run_sync, is_coroutine_callable
SYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Any]
ASYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Awaitable[Any]]
LIFESPAN_FUNC: TypeAlias = SYNC_LIFESPAN_FUNC | ASYNC_LIFESPAN_FUNC
LIFESPAN_FUNC: TypeAlias = Union[SYNC_LIFESPAN_FUNC, ASYNC_LIFESPAN_FUNC]
class Lifespan:
def __init__(self) -> None:
self._task_group: TaskGroup | None = None
self._startup_funcs: list[LIFESPAN_FUNC] = []
self._ready_funcs: list[LIFESPAN_FUNC] = []
self._shutdown_funcs: list[LIFESPAN_FUNC] = []
@property
def task_group(self) -> TaskGroup:
if self._task_group is None:
raise RuntimeError("Lifespan not started")
return self._task_group
@task_group.setter
def task_group(self, task_group: TaskGroup) -> None:
if self._task_group is not None:
raise RuntimeError("Lifespan already started")
self._task_group = task_group
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
self._startup_funcs.append(func)
return func
@@ -47,7 +29,7 @@ class Lifespan:
@staticmethod
async def _run_lifespan_func(
funcs: Iterable[LIFESPAN_FUNC],
funcs: list[LIFESPAN_FUNC],
) -> None:
for func in funcs:
if is_coroutine_callable(func):
@@ -56,44 +38,18 @@ class Lifespan:
await run_sync(cast(SYNC_LIFESPAN_FUNC, func))()
async def startup(self) -> None:
# create background task group
self.task_group = anyio.create_task_group()
await self.task_group.__aenter__()
# run startup funcs
if self._startup_funcs:
await self._run_lifespan_func(self._startup_funcs)
# run ready funcs
if self._ready_funcs:
await self._run_lifespan_func(self._ready_funcs)
async def shutdown(
self,
*,
exc_type: type[BaseException] | None = None,
exc_val: BaseException | None = None,
exc_tb: TracebackType | None = None,
) -> None:
async def shutdown(self) -> None:
if self._shutdown_funcs:
# reverse shutdown funcs to ensure stack order
await self._run_lifespan_func(reversed(self._shutdown_funcs))
# shutdown background task group
self.task_group.cancel_scope.cancel()
with suppress(Exception):
await self.task_group.__aexit__(exc_type, exc_val, exc_tb)
self._task_group = None
await self._run_lifespan_func(self._shutdown_funcs)
async def __aenter__(self) -> None:
await self.startup()
async def __aexit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
await self.shutdown(exc_type=exc_type, exc_val=exc_val, exc_tb=exc_tb)
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
await self.shutdown()
+75 -105
View File
@@ -1,48 +1,38 @@
import abc
from collections.abc import AsyncGenerator
from contextlib import AsyncExitStack, asynccontextmanager
import asyncio
from types import TracebackType
from typing import TYPE_CHECKING, Any, ClassVar, TypeAlias
from typing_extensions import Self
from collections.abc import AsyncGenerator
from typing_extensions import Self, TypeAlias
from contextlib import AsyncExitStack, asynccontextmanager
from typing import TYPE_CHECKING, Any, Union, ClassVar, Optional
from anyio import CancelScope, create_task_group
from anyio.abc import TaskGroup
from exceptiongroup import BaseExceptionGroup, catch
from nonebot.config import Config, Env
from nonebot.log import logger
from nonebot.config import Env, Config
from nonebot.dependencies import Dependent
from nonebot.exception import SkippedException
from nonebot.internal.params import BotParam, DefaultParam, DependParam
from nonebot.log import logger
from nonebot.utils import escape_tag, run_coro_with_catch
from nonebot.internal.params import BotParam, DependParam, DefaultParam
from nonebot.typing import (
T_DependencyCache,
T_BotConnectionHook,
T_BotDisconnectionHook,
T_DependencyCache,
)
from nonebot.utils import (
UNSET,
UnsetType,
escape_tag,
flatten_exception_group,
run_coro_with_catch,
)
from ._lifespan import LIFESPAN_FUNC, Lifespan
from .model import (
CookieTypes,
HeaderTypes,
HTTPServerSetup,
HTTPVersion,
QueryTypes,
Request,
Response,
TimeoutTypes,
WebSocket,
QueryTypes,
CookieTypes,
HeaderTypes,
HTTPVersion,
HTTPServerSetup,
WebSocketServerSetup,
)
if TYPE_CHECKING:
from nonebot.internal.adapter import Adapter, Bot
from nonebot.internal.adapter import Bot, Adapter
BOT_HOOK_PARAMS = [DependParam, BotParam, DefaultParam]
@@ -71,6 +61,7 @@ class Driver(abc.ABC):
self.config: Config = config
"""全局配置对象"""
self._bots: dict[str, "Bot"] = {}
self._bot_tasks: set[asyncio.Task] = set()
self._lifespan = Lifespan()
def __repr__(self) -> str:
@@ -84,10 +75,6 @@ class Driver(abc.ABC):
"""获取当前所有已连接的 Bot"""
return self._bots
@property
def task_group(self) -> TaskGroup:
return self._lifespan.task_group
def register_adapter(self, adapter: type["Adapter"], **kwargs) -> None:
"""注册一个协议适配器
@@ -121,10 +108,12 @@ class Driver(abc.ABC):
@abc.abstractmethod
def run(self, *args, **kwargs):
"""启动驱动框架"""
logger.opt(colors=True).success(
logger.opt(colors=True).debug(
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
)
self.on_shutdown(self._cleanup)
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
"""注册一个启动时执行的函数"""
return self._lifespan.on_startup(func)
@@ -165,63 +154,66 @@ class Driver(abc.ABC):
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
self._bots[bot.self_id] = bot
if not self._bot_connection_hook:
return
def handle_exception(exc_group: BaseExceptionGroup) -> None:
for exc in flatten_exception_group(exc_group):
logger.opt(colors=True, exception=exc).error(
"<r><bg #f8bbd0>"
"Error when running WebSocketConnection hook:"
"</bg #f8bbd0></r>"
)
async def _run_hook(bot: "Bot") -> None:
dependency_cache: T_DependencyCache = {}
with CancelScope(shield=True), catch({Exception: handle_exception}):
async with AsyncExitStack() as stack, create_task_group() as tg:
for hook in self._bot_connection_hook:
tg.start_soon(
run_coro_with_catch,
hook(
bot=bot, stack=stack, dependency_cache=dependency_cache
),
(SkippedException,),
async with AsyncExitStack() as stack:
if coros := [
run_coro_with_catch(
hook(bot=bot, stack=stack, dependency_cache=dependency_cache),
(SkippedException,),
)
for hook in self._bot_connection_hook
]:
try:
await asyncio.gather(*coros)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>"
"Error when running WebSocketConnection hook. "
"Running cancelled!"
"</bg #f8bbd0></r>"
)
self.task_group.start_soon(_run_hook, bot)
task = asyncio.create_task(_run_hook(bot))
task.add_done_callback(self._bot_tasks.discard)
self._bot_tasks.add(task)
def _bot_disconnect(self, bot: "Bot") -> None:
"""在连接断开后,调用该函数来注销 bot 对象"""
if bot.self_id in self._bots:
del self._bots[bot.self_id]
if not self._bot_disconnection_hook:
return
def handle_exception(exc_group: BaseExceptionGroup) -> None:
for exc in flatten_exception_group(exc_group):
logger.opt(colors=True, exception=exc).error(
"<r><bg #f8bbd0>"
"Error when running WebSocketDisConnection hook:"
"</bg #f8bbd0></r>"
)
async def _run_hook(bot: "Bot") -> None:
dependency_cache: T_DependencyCache = {}
# shield cancellation to ensure bot disconnect hooks are always run
with CancelScope(shield=True), catch({Exception: handle_exception}):
async with create_task_group() as tg, AsyncExitStack() as stack:
for hook in self._bot_disconnection_hook:
tg.start_soon(
run_coro_with_catch,
hook(
bot=bot, stack=stack, dependency_cache=dependency_cache
),
(SkippedException,),
async with AsyncExitStack() as stack:
if coros := [
run_coro_with_catch(
hook(bot=bot, stack=stack, dependency_cache=dependency_cache),
(SkippedException,),
)
for hook in self._bot_disconnection_hook
]:
try:
await asyncio.gather(*coros)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>"
"Error when running WebSocketDisConnection hook. "
"Running cancelled!"
"</bg #f8bbd0></r>"
)
self.task_group.start_soon(_run_hook, bot)
task = asyncio.create_task(_run_hook(bot))
task.add_done_callback(self._bot_tasks.discard)
self._bot_tasks.add(task)
async def _cleanup(self) -> None:
"""清理驱动器资源"""
if self._bot_tasks:
logger.opt(colors=True).debug(
"<y>Waiting for running bot connection hooks...</y>"
)
await asyncio.gather(*self._bot_tasks, return_exceptions=True)
class Mixin(abc.ABC):
@@ -251,9 +243,9 @@ class HTTPClientSession(abc.ABC):
params: QueryTypes = None,
headers: HeaderTypes = None,
cookies: CookieTypes = None,
version: str | HTTPVersion = HTTPVersion.H11,
timeout: TimeoutTypes | UnsetType = UNSET,
proxy: str | None = None,
version: Union[str, HTTPVersion] = HTTPVersion.H11,
timeout: Optional[float] = None,
proxy: Optional[str] = None,
):
raise NotImplementedError
@@ -262,17 +254,6 @@ class HTTPClientSession(abc.ABC):
"""发送一个 HTTP 请求"""
raise NotImplementedError
@abc.abstractmethod
async def stream_request(
self,
setup: Request,
*,
chunk_size: int = 1024,
) -> AsyncGenerator[Response, None]:
"""发送一个 HTTP 流式请求"""
raise NotImplementedError
yield # used for static type checking's generator detection
@abc.abstractmethod
async def setup(self) -> None:
"""初始化会话"""
@@ -289,9 +270,9 @@ class HTTPClientSession(abc.ABC):
async def __aexit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
exc_type: Optional[type[BaseException]],
exc: Optional[BaseException],
tb: Optional[TracebackType],
) -> None:
await self.close()
@@ -304,26 +285,15 @@ class HTTPClientMixin(ForwardMixin):
"""发送一个 HTTP 请求"""
raise NotImplementedError
@abc.abstractmethod
async def stream_request(
self,
setup: Request,
*,
chunk_size: int = 1024,
) -> AsyncGenerator[Response, None]:
"""发送一个 HTTP 流式请求"""
raise NotImplementedError
yield # used for static type checking's generator detection
@abc.abstractmethod
def get_session(
self,
params: QueryTypes = None,
headers: HeaderTypes = None,
cookies: CookieTypes = None,
version: str | HTTPVersion = HTTPVersion.H11,
timeout: TimeoutTypes = None,
proxy: str | None = None,
version: Union[str, HTTPVersion] = HTTPVersion.H11,
timeout: Optional[float] = None,
proxy: Optional[str] = None,
) -> HTTPClientSession:
"""获取一个 HTTP 会话"""
raise NotImplementedError
+6 -4
View File
@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING, TypeVar, overload
from typing import TYPE_CHECKING, Union, TypeVar, overload
from .abstract import Driver, Mixin
from .abstract import Mixin, Driver
D = TypeVar("D", bound="Driver")
@@ -21,7 +21,7 @@ def combine_driver(
def combine_driver(
driver: type[D], *mixins: type[Mixin]
) -> type[D] | type["CombinedDriver"]:
) -> Union[type[D], type["CombinedDriver"]]:
"""将一个驱动器和多个混入类合并。"""
# check first
if not issubclass(driver, Driver):
@@ -39,4 +39,6 @@ def combine_driver(
+ "+".join(x.type.__get__(self) for x in mixins) # type: ignore
)
return type("CombinedDriver", (*mixins, driver), {"type": property(type_)}) # type: ignore
return type(
"CombinedDriver", (*mixins, driver), {"type": property(type_)}
) # type: ignore
+54 -69
View File
@@ -1,59 +1,47 @@
import abc
from collections.abc import Awaitable, Callable, Iterator, Mapping, MutableMapping
from dataclasses import dataclass
from enum import Enum
from http.cookiejar import Cookie, CookieJar
from typing import IO, Any, TypeAlias
import urllib.request
from enum import Enum
from dataclasses import dataclass
from typing_extensions import TypeAlias
from http.cookiejar import Cookie, CookieJar
from typing import IO, Any, Union, Callable, Optional
from collections.abc import Mapping, Iterator, Awaitable, MutableMapping
from multidict import CIMultiDict
from yarl import URL as URL
from multidict import CIMultiDict
from nonebot.utils import UNSET, UnsetType
RawURL: TypeAlias = tuple[bytes, bytes, Optional[int], bytes]
SimpleQuery: TypeAlias = Union[str, int, float]
QueryVariable: TypeAlias = Union[SimpleQuery, list[SimpleQuery]]
QueryTypes: TypeAlias = Union[
None, str, Mapping[str, QueryVariable], list[tuple[str, SimpleQuery]]
]
@dataclass
class Timeout:
"""Request 超时配置。"""
HeaderTypes: TypeAlias = Union[
None,
CIMultiDict[str],
dict[str, str],
list[tuple[str, str]],
]
total: float | None | UnsetType = UNSET
connect: float | None | UnsetType = UNSET
read: float | None | UnsetType = UNSET
close: float | None | UnsetType = UNSET
ping: float | None | UnsetType = UNSET
CookieTypes: TypeAlias = Union[
None, "Cookies", CookieJar, dict[str, str], list[tuple[str, str]]
]
DEFAULT_TIMEOUT = Timeout(total=None, connect=5.0, read=30.0, close=10.0, ping=20.0)
RawURL: TypeAlias = tuple[bytes, bytes, int | None, bytes]
SimpleQuery: TypeAlias = str | int | float
QueryVariable: TypeAlias = SimpleQuery | list[SimpleQuery]
QueryTypes: TypeAlias = (
None | str | Mapping[str, QueryVariable] | list[tuple[str, SimpleQuery]]
)
HeaderTypes: TypeAlias = (
None | CIMultiDict[str] | dict[str, str] | list[tuple[str, str]]
)
CookieTypes: TypeAlias = (
"None | Cookies | CookieJar | dict[str, str] | list[tuple[str, str]]"
)
ContentTypes: TypeAlias = str | bytes | None
DataTypes: TypeAlias = dict | None
FileContent: TypeAlias = IO[bytes] | bytes
FileType: TypeAlias = tuple[str | None, FileContent, str | None]
FileTypes: TypeAlias = (
FileContent # file (or bytes)
| tuple[str | None, FileContent] # (filename, file (or bytes))
| FileType # (filename, file (or bytes), content_type)
)
FilesTypes: TypeAlias = dict[str, FileTypes] | list[tuple[str, FileTypes]] | None
TimeoutTypes: TypeAlias = float | Timeout | None
PingIntervalTypes: TypeAlias = float | None
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]]
FileTypes: TypeAlias = Union[
# file (or bytes)
FileContent,
# (filename, file (or bytes))
tuple[Optional[str], FileContent],
# (filename, file (or bytes), content_type)
FileType,
]
FilesTypes: TypeAlias = Union[dict[str, FileTypes], list[tuple[str, FileTypes]], None]
class HTTPVersion(Enum):
@@ -65,8 +53,8 @@ class HTTPVersion(Enum):
class Request:
def __init__(
self,
method: str | bytes,
url: "URL | str | RawURL",
method: Union[str, bytes],
url: Union["URL", str, RawURL],
*,
params: QueryTypes = None,
headers: HeaderTypes = None,
@@ -75,10 +63,9 @@ class Request:
data: DataTypes = None,
json: Any = None,
files: FilesTypes = None,
version: str | HTTPVersion = HTTPVersion.H11,
timeout: TimeoutTypes | UnsetType = UNSET,
proxy: str | None = None,
ping_interval: PingIntervalTypes | UnsetType = UNSET,
version: Union[str, HTTPVersion] = HTTPVersion.H11,
timeout: Optional[float] = None,
proxy: Optional[str] = None,
):
# method
self.method: str = (
@@ -89,11 +76,9 @@ class Request:
# http version
self.version: HTTPVersion = HTTPVersion(version)
# timeout
self.timeout: TimeoutTypes | UnsetType = timeout
self.timeout: Optional[float] = timeout
# proxy
self.proxy: str | None = proxy
# ping interval
self.ping_interval: PingIntervalTypes | UnsetType = ping_interval
self.proxy: Optional[str] = proxy
# url
if isinstance(url, tuple):
@@ -122,7 +107,7 @@ class Request:
self.content: ContentTypes = content
self.data: DataTypes = data
self.json: Any = json
self.files: list[tuple[str, FileType]] | None = None
self.files: Optional[list[tuple[str, FileType]]] = None
if files:
self.files = []
files_ = files.items() if isinstance(files, dict) else files
@@ -145,7 +130,7 @@ class Response:
*,
headers: HeaderTypes = None,
content: ContentTypes = None,
request: Request | None = None,
request: Optional[Request] = None,
):
# status code
self.status_code: int = status_code
@@ -158,7 +143,7 @@ class Response:
self.content: ContentTypes = content
# request
self.request: Request | None = request
self.request: Optional[Request] = request
def __repr__(self) -> str:
return f"{self.__class__.__name__}(status_code={self.status_code!r})"
@@ -188,7 +173,7 @@ class WebSocket(abc.ABC):
raise NotImplementedError
@abc.abstractmethod
async def receive(self) -> str | bytes:
async def receive(self) -> Union[str, bytes]:
"""接收一条 WebSocket text/bytes 信息"""
raise NotImplementedError
@@ -202,7 +187,7 @@ class WebSocket(abc.ABC):
"""接收一条 WebSocket binary 信息"""
raise NotImplementedError
async def send(self, data: str | bytes) -> None:
async def send(self, data: Union[str, bytes]) -> None:
"""发送一条 WebSocket text/bytes 信息"""
if isinstance(data, str):
await self.send_text(data)
@@ -263,11 +248,11 @@ class Cookies(MutableMapping):
def get( # pyright: ignore[reportIncompatibleMethodOverride]
self,
name: str,
default: str | None = None,
domain: str | None = None,
path: str | None = None,
) -> str | None:
value: str | None = None
default: Optional[str] = None,
domain: Optional[str] = None,
path: Optional[str] = None,
) -> Optional[str]:
value: Optional[str] = None
for cookie in self.jar:
if (
cookie.name == name
@@ -282,7 +267,7 @@ class Cookies(MutableMapping):
return default if value is None else value
def delete(
self, name: str, domain: str | None = None, path: str | None = None
self, name: str, domain: Optional[str] = None, path: Optional[str] = None
) -> None:
if domain is not None and path is not None:
return self.jar.clear(domain, path, name)
@@ -298,7 +283,7 @@ class Cookies(MutableMapping):
for cookie in remove:
self.jar.clear(cookie.domain, cookie.path, cookie.name)
def clear(self, domain: str | None = None, path: str | None = None) -> None:
def clear(self, domain: Optional[str] = None, path: Optional[str] = None) -> None:
self.jar.clear(domain, path)
def update( # pyright: ignore[reportIncompatibleMethodOverride]
+2 -2
View File
@@ -1,12 +1,12 @@
from .manager import MatcherManager as MatcherManager
from .provider import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
from .provider import MatcherProvider as MatcherProvider
from .provider import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
matchers = MatcherManager()
from .matcher import Matcher as Matcher
from .matcher import MatcherSource as MatcherSource
from .matcher import current_bot as current_bot
from .matcher import MatcherSource as MatcherSource
from .matcher import current_event as current_event
from .matcher import current_handler as current_handler
from .matcher import current_matcher as current_matcher
+8 -13
View File
@@ -1,5 +1,5 @@
from collections.abc import ItemsView, Iterator, KeysView, MutableMapping, ValuesView
from typing import TYPE_CHECKING, TypeVar, 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
@@ -52,19 +52,14 @@ class MatcherManager(MutableMapping[int, list[type["Matcher"]]]):
return self.provider.items()
@overload
def get(self, key: int) -> list[type["Matcher"]] | None: ...
def get(self, key: int) -> Optional[list[type["Matcher"]]]: ...
@overload
def get(
self, key: int, default: list[type["Matcher"]]
) -> list[type["Matcher"]]: ...
@overload
def get(self, key: int, default: T) -> list[type["Matcher"]] | T: ...
def get(self, key: int, default: T) -> Union[list[type["Matcher"]], T]: ...
def get(
self, key: int, default: T | None = None
) -> list[type["Matcher"]] | T | None:
self, key: int, default: Optional[T] = None
) -> Optional[Union[list[type["Matcher"]], T]]:
return self.provider.get(key, default)
def pop( # pyright: ignore[reportIncompatibleMethodOverride]
@@ -79,9 +74,9 @@ class MatcherManager(MutableMapping[int, list[type["Matcher"]]]):
self.provider.clear()
def update( # pyright: ignore[reportIncompatibleMethodOverride]
self, m: MutableMapping[int, list[type["Matcher"]]], /
self, __m: MutableMapping[int, list[type["Matcher"]]]
) -> None:
self.provider.update(m)
self.provider.update(__m)
def setdefault(
self, key: int, default: list[type["Matcher"]]
+145 -211
View File
@@ -1,44 +1,32 @@
from collections.abc import Iterable
from contextlib import AsyncExitStack, contextmanager
from contextvars import ContextVar
from dataclasses import dataclass
from datetime import datetime, timedelta
import inspect
from pathlib import Path
import sys
import inspect
import warnings
from pathlib import Path
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 ( # noqa: UP035
TYPE_CHECKING,
Any,
Type,
Union,
TypeVar,
Callable,
ClassVar,
NoReturn,
Type,
TypeVar,
Optional,
overload,
)
from typing_extensions import Self
import warnings
from exceptiongroup import BaseExceptionGroup, catch
from nonebot.consts import (
ARG_KEY,
LAST_RECEIVE_KEY,
PAUSE_PROMPT_RESULT_KEY,
RECEIVE_KEY,
REJECT_CACHE_TARGET,
REJECT_PROMPT_RESULT_KEY,
REJECT_TARGET,
)
from nonebot.dependencies import Dependent, Param
from nonebot.exception import (
FinishedException,
PausedException,
RejectedException,
SkippedException,
StopPropagation,
)
from nonebot.log import logger
from nonebot.internal.rule import Rule
from nonebot.utils import classproperty
from nonebot.dependencies import Param, Dependent
from nonebot.internal.permission import User, Permission
from nonebot.internal.adapter import (
Bot,
Event,
@@ -46,27 +34,37 @@ from nonebot.internal.adapter import (
MessageSegment,
MessageTemplate,
)
from nonebot.typing import (
T_State,
T_Handler,
T_TypeUpdater,
T_DependencyCache,
T_PermissionUpdater,
)
from nonebot.consts import (
ARG_KEY,
RECEIVE_KEY,
REJECT_TARGET,
LAST_RECEIVE_KEY,
REJECT_CACHE_TARGET,
)
from nonebot.exception import (
PausedException,
StopPropagation,
SkippedException,
FinishedException,
RejectedException,
)
from nonebot.internal.params import (
Depends,
ArgParam,
BotParam,
DefaultParam,
DependParam,
Depends,
EventParam,
MatcherParam,
StateParam,
DependParam,
DefaultParam,
MatcherParam,
)
from nonebot.internal.permission import Permission, User
from nonebot.internal.rule import Rule
from nonebot.log import logger
from nonebot.typing import (
T_DependencyCache,
T_Handler,
T_PermissionUpdater,
T_State,
T_TypeUpdater,
)
from nonebot.utils import classproperty, flatten_exception_group
from . import matchers
@@ -85,15 +83,15 @@ current_handler: ContextVar[Dependent[Any]] = ContextVar("current_handler")
class MatcherSource:
"""Matcher 源代码上下文信息"""
plugin_id: str | None = None
plugin_id: Optional[str] = None
"""事件响应器所在插件标识符"""
module_name: str | None = None
module_name: Optional[str] = None
"""事件响应器所在插件模块的路径名"""
lineno: int | None = None
lineno: Optional[int] = None
"""事件响应器所在行号"""
@property
def plugin(self) -> "Plugin | None":
def plugin(self) -> Optional["Plugin"]:
"""事件响应器所在插件"""
from nonebot.plugin import get_plugin
@@ -101,17 +99,17 @@ class MatcherSource:
return get_plugin(self.plugin_id)
@property
def plugin_name(self) -> str | None:
def plugin_name(self) -> Optional[str]:
"""事件响应器所在插件名"""
return self.plugin and self.plugin.name
@property
def module(self) -> ModuleType | None:
def module(self) -> Optional[ModuleType]:
if self.module_name is not None:
return sys.modules.get(self.module_name)
@property
def file(self) -> Path | None:
def file(self) -> Optional[Path]:
if self.module is not None and (file := inspect.getsourcefile(self.module)):
return Path(file).absolute()
@@ -119,8 +117,8 @@ class MatcherSource:
class MatcherMeta(type):
if TYPE_CHECKING:
type: str
_source: MatcherSource | None
module_name: str | None
_source: Optional[MatcherSource]
module_name: Optional[str]
def __repr__(self) -> str:
return (
@@ -138,7 +136,7 @@ class MatcherMeta(type):
class Matcher(metaclass=MatcherMeta):
"""事件响应器类"""
_source: ClassVar[MatcherSource | None] = None
_source: ClassVar[Optional[MatcherSource]] = None
type: ClassVar[str] = ""
"""事件响应器类型"""
@@ -154,15 +152,15 @@ class Matcher(metaclass=MatcherMeta):
"""事件响应器是否阻止事件传播"""
temp: ClassVar[bool] = False
"""事件响应器是否为临时"""
expire_time: ClassVar[datetime | None] = None
expire_time: ClassVar[Optional[datetime]] = None
"""事件响应器过期时间点"""
_default_state: ClassVar[T_State] = {}
"""事件响应器默认状态"""
_default_type_updater: ClassVar[Dependent[str] | None] = None
_default_type_updater: ClassVar[Optional[Dependent[str]]] = None
"""事件响应器类型更新函数"""
_default_permission_updater: ClassVar[Dependent[Permission] | None] = None
_default_permission_updater: ClassVar[Optional[Dependent[Permission]]] = None
"""事件响应器权限更新函数"""
HANDLER_PARAM_TYPES: ClassVar[tuple[Type[Param], ...]] = ( # noqa: UP006
@@ -195,22 +193,22 @@ class Matcher(metaclass=MatcherMeta):
def new(
cls,
type_: str = "",
rule: Rule | None = None,
permission: Permission | None = None,
handlers: list[T_Handler | Dependent[Any]] | None = None,
rule: Optional[Rule] = None,
permission: Optional[Permission] = None,
handlers: Optional[list[Union[T_Handler, Dependent[Any]]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
*,
plugin: "Plugin | None" = None,
module: ModuleType | None = None,
source: MatcherSource | None = None,
expire_time: datetime | timedelta | None = None,
default_state: T_State | None = None,
default_type_updater: T_TypeUpdater | Dependent[str] | None = None,
default_permission_updater: T_PermissionUpdater
| Dependent[Permission]
| None = None,
plugin: Optional["Plugin"] = None,
module: Optional[ModuleType] = None,
source: Optional[MatcherSource] = None,
expire_time: Optional[Union[datetime, timedelta]] = None,
default_state: Optional[T_State] = None,
default_type_updater: Optional[Union[T_TypeUpdater, Dependent[str]]] = None,
default_permission_updater: Optional[
Union[T_PermissionUpdater, Dependent[Permission]]
] = None,
) -> Type[Self]: # noqa: UP006
"""
创建一个新的事件响应器,并存储至 `matchers <#matchers>`_
@@ -330,27 +328,27 @@ class Matcher(metaclass=MatcherMeta):
matchers[cls.priority].remove(cls)
@classproperty
def plugin(cls) -> "Plugin | None":
def plugin(cls) -> Optional["Plugin"]:
"""事件响应器所在插件"""
return cls._source and cls._source.plugin
@classproperty
def plugin_id(cls) -> str | None:
def plugin_id(cls) -> Optional[str]:
"""事件响应器所在插件标识符"""
return cls._source and cls._source.plugin_id
@classproperty
def plugin_name(cls) -> str | None:
def plugin_name(cls) -> Optional[str]:
"""事件响应器所在插件名"""
return cls._source and cls._source.plugin_name
@classproperty
def module(cls) -> ModuleType | None:
def module(cls) -> Optional[ModuleType]:
"""事件响应器所在插件模块"""
return cls._source and cls._source.module
@classproperty
def module_name(cls) -> str | None:
def module_name(cls) -> Optional[str]:
"""事件响应器所在插件模块路径"""
return cls._source and cls._source.module_name
@@ -359,8 +357,8 @@ class Matcher(metaclass=MatcherMeta):
cls,
bot: Bot,
event: Event,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""检查是否满足触发权限
@@ -384,8 +382,8 @@ class Matcher(metaclass=MatcherMeta):
bot: Bot,
event: Event,
state: T_State,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""检查是否满足匹配规则
@@ -430,7 +428,7 @@ class Matcher(metaclass=MatcherMeta):
@classmethod
def append_handler(
cls, handler: T_Handler, parameterless: Iterable[Any] | None = None
cls, handler: T_Handler, parameterless: Optional[Iterable[Any]] = None
) -> Dependent[Any]:
handler_ = Dependent[Any].parse(
call=handler,
@@ -442,7 +440,7 @@ class Matcher(metaclass=MatcherMeta):
@classmethod
def handle(
cls, parameterless: Iterable[Any] | None = None
cls, parameterless: Optional[Iterable[Any]] = None
) -> Callable[[T_Handler], T_Handler]:
"""装饰一个函数来向事件响应器直接添加一个处理函数
@@ -458,7 +456,7 @@ class Matcher(metaclass=MatcherMeta):
@classmethod
def receive(
cls, id: str = "", parameterless: Iterable[Any] | None = None
cls, id: str = "", parameterless: Optional[Iterable[Any]] = None
) -> Callable[[T_Handler], T_Handler]:
"""装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
@@ -501,8 +499,8 @@ class Matcher(metaclass=MatcherMeta):
def got(
cls,
key: str,
prompt: str | Message | MessageSegment | MessageTemplate | None = None,
parameterless: Iterable[Any] | None = None,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
parameterless: Optional[Iterable[Any]] = None,
) -> Callable[[T_Handler], T_Handler]:
"""装饰一个函数来指示 NoneBot 获取一个参数 `key`
@@ -548,7 +546,7 @@ class Matcher(metaclass=MatcherMeta):
@classmethod
async def send(
cls,
message: str | Message | MessageSegment | MessageTemplate,
message: Union[str, Message, MessageSegment, MessageTemplate],
**kwargs: Any,
) -> Any:
"""发送一条消息给当前交互用户
@@ -560,8 +558,8 @@ class Matcher(metaclass=MatcherMeta):
"""
bot = current_bot.get()
event = current_event.get()
state = current_matcher.get().state
if isinstance(message, MessageTemplate):
state = current_matcher.get().state
_message = message.format(**state)
else:
_message = message
@@ -570,7 +568,7 @@ class Matcher(metaclass=MatcherMeta):
@classmethod
async def finish(
cls,
message: str | Message | MessageSegment | MessageTemplate | None = None,
message: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""发送一条消息给当前交互用户并结束当前事件响应器
@@ -587,7 +585,7 @@ class Matcher(metaclass=MatcherMeta):
@classmethod
async def pause(
cls,
prompt: str | Message | MessageSegment | MessageTemplate | None = None,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
@@ -597,21 +595,14 @@ class Matcher(metaclass=MatcherMeta):
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
try:
matcher = current_matcher.get()
except Exception:
matcher = None
if prompt is not None:
result = await cls.send(prompt, **kwargs)
if matcher is not None:
matcher.state[PAUSE_PROMPT_RESULT_KEY] = result
await cls.send(prompt, **kwargs)
raise PausedException
@classmethod
async def reject(
cls,
prompt: str | Message | MessageSegment | MessageTemplate | None = None,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""最近使用 `got` / `receive` 接收的消息不符合预期,
@@ -622,26 +613,15 @@ class Matcher(metaclass=MatcherMeta):
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
try:
matcher = current_matcher.get()
key = matcher.get_target()
except Exception:
matcher = None
key = None
key = REJECT_PROMPT_RESULT_KEY.format(key=key) if key is not None else None
if prompt is not None:
result = await cls.send(prompt, **kwargs)
if key is not None and matcher:
matcher.state[key] = result
await cls.send(prompt, **kwargs)
raise RejectedException
@classmethod
async def reject_arg(
cls,
key: str,
prompt: str | Message | MessageSegment | MessageTemplate | None = None,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""最近使用 `got` 接收的消息不符合预期,
@@ -654,19 +634,16 @@ class Matcher(metaclass=MatcherMeta):
请参考对应 adapter 的 bot 对象 api
"""
matcher = current_matcher.get()
arg_key = ARG_KEY.format(key=key)
matcher.set_target(arg_key)
matcher.set_target(ARG_KEY.format(key=key))
if prompt is not None:
result = await cls.send(prompt, **kwargs)
matcher.state[REJECT_PROMPT_RESULT_KEY.format(key=arg_key)] = result
await cls.send(prompt, **kwargs)
raise RejectedException
@classmethod
async def reject_receive(
cls,
id: str = "",
prompt: str | Message | MessageSegment | MessageTemplate | None = None,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""最近使用 `receive` 接收的消息不符合预期,
@@ -679,12 +656,9 @@ class Matcher(metaclass=MatcherMeta):
请参考对应 adapter 的 bot 对象 api
"""
matcher = current_matcher.get()
receive_key = RECEIVE_KEY.format(id=id)
matcher.set_target(receive_key)
matcher.set_target(RECEIVE_KEY.format(id=id))
if prompt is not None:
result = await cls.send(prompt, **kwargs)
matcher.state[REJECT_PROMPT_RESULT_KEY.format(key=receive_key)] = result
await cls.send(prompt, **kwargs)
raise RejectedException
@classmethod
@@ -696,12 +670,14 @@ class Matcher(metaclass=MatcherMeta):
raise SkippedException
@overload
def get_receive(self, id: str) -> Event | None: ...
def get_receive(self, id: str) -> Union[Event, None]: ...
@overload
def get_receive(self, id: str, default: T) -> Event | T: ...
def get_receive(self, id: str, default: T) -> Union[Event, T]: ...
def get_receive(self, id: str, default: T | None = None) -> Event | T | None:
def get_receive(
self, id: str, default: Optional[T] = None
) -> Optional[Union[Event, T]]:
"""获取一个 `receive` 事件
如果没有找到对应的事件,返回 `default` 值
@@ -714,12 +690,14 @@ class Matcher(metaclass=MatcherMeta):
self.state[LAST_RECEIVE_KEY] = event
@overload
def get_last_receive(self) -> Event | None: ...
def get_last_receive(self) -> Union[Event, None]: ...
@overload
def get_last_receive(self, default: T) -> Event | T: ...
def get_last_receive(self, default: T) -> Union[Event, T]: ...
def get_last_receive(self, default: T | None = None) -> Event | T | None:
def get_last_receive(
self, default: Optional[T] = None
) -> Optional[Union[Event, T]]:
"""获取最近一次 `receive` 事件
如果没有事件,返回 `default` 值
@@ -727,12 +705,14 @@ class Matcher(metaclass=MatcherMeta):
return self.state.get(LAST_RECEIVE_KEY, default)
@overload
def get_arg(self, key: str) -> Message | None: ...
def get_arg(self, key: str) -> Union[Message, None]: ...
@overload
def get_arg(self, key: str, default: T) -> Message | T: ...
def get_arg(self, key: str, default: T) -> Union[Message, T]: ...
def get_arg(self, key: str, default: T | None = None) -> Message | T | None:
def get_arg(
self, key: str, default: Optional[T] = None
) -> Optional[Union[Message, T]]:
"""获取一个 `got` 消息
如果没有找到对应的消息,返回 `default` 值
@@ -750,12 +730,12 @@ class Matcher(metaclass=MatcherMeta):
self.state[REJECT_TARGET] = target
@overload
def get_target(self) -> str | None: ...
def get_target(self) -> Union[str, None]: ...
@overload
def get_target(self, default: T) -> str | T: ...
def get_target(self, default: T) -> Union[str, T]: ...
def get_target(self, default: T | None = None) -> str | T | None:
def get_target(self, default: Optional[T] = None) -> Optional[Union[str, T]]:
return self.state.get(REJECT_TARGET, default)
def stop_propagation(self):
@@ -766,8 +746,8 @@ class Matcher(metaclass=MatcherMeta):
self,
bot: Bot,
event: Event,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> str:
updater = self.__class__._default_type_updater
return (
@@ -787,8 +767,8 @@ class Matcher(metaclass=MatcherMeta):
self,
bot: Bot,
event: Event,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> Permission:
if updater := self.__class__._default_permission_updater:
return await updater(
@@ -824,42 +804,36 @@ class Matcher(metaclass=MatcherMeta):
bot: Bot,
event: Event,
state: T_State,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
):
logger.trace(
f"{self} run with incoming args: "
f"bot={bot}, event={event!r}, state={state!r}"
)
def _handle_stop_propagation(exc_group: BaseExceptionGroup[StopPropagation]):
self.block = True
with self.ensure_context(bot, event):
try:
with catch({StopPropagation: _handle_stop_propagation}):
# Refresh preprocess state
self.state.update(state)
# Refresh preprocess state
self.state.update(state)
while self.remain_handlers:
handler = self.remain_handlers.pop(0)
current_handler.set(handler)
logger.debug(f"Running handler {handler}")
def _handle_skipped(
exc_group: BaseExceptionGroup[SkippedException],
):
logger.debug(f"Handler {handler} skipped")
with catch({SkippedException: _handle_skipped}):
await handler(
matcher=self,
bot=bot,
event=event,
state=self.state,
stack=stack,
dependency_cache=dependency_cache,
)
while self.remain_handlers:
handler = self.remain_handlers.pop(0)
current_handler.set(handler)
logger.debug(f"Running handler {handler}")
try:
await handler(
matcher=self,
bot=bot,
event=event,
state=self.state,
stack=stack,
dependency_cache=dependency_cache,
)
except SkippedException:
logger.debug(f"Handler {handler} skipped")
except StopPropagation:
self.block = True
finally:
logger.info(f"{self} running complete")
@@ -869,55 +843,13 @@ class Matcher(metaclass=MatcherMeta):
bot: Bot,
event: Event,
state: T_State,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
):
exc: FinishedException | RejectedException | PausedException | None = None
def _handle_special_exception(
exc_group: BaseExceptionGroup[
FinishedException | RejectedException | PausedException
],
):
nonlocal exc
excs = list(flatten_exception_group(exc_group))
if len(excs) > 1:
logger.warning(
"Multiple session control exceptions occurred. "
"NoneBot will choose the proper one."
)
finished_exc = next(
(e for e in excs if isinstance(e, FinishedException)),
None,
)
rejected_exc = next(
(e for e in excs if isinstance(e, RejectedException)),
None,
)
paused_exc = next(
(e for e in excs if isinstance(e, PausedException)),
None,
)
exc = finished_exc or rejected_exc or paused_exc
elif isinstance(
excs[0], (FinishedException, RejectedException, PausedException)
):
exc = excs[0]
with catch(
{
(
FinishedException,
RejectedException,
PausedException,
): _handle_special_exception
}
):
try:
await self.simple_run(bot, event, state, stack, dependency_cache)
if isinstance(exc, FinishedException):
pass
elif isinstance(exc, RejectedException):
except RejectedException:
await self.resolve_reject()
type_ = await self.update_type(bot, event, stack, dependency_cache)
permission = await self.update_permission(
@@ -938,7 +870,7 @@ class Matcher(metaclass=MatcherMeta):
default_type_updater=self.__class__._default_type_updater,
default_permission_updater=self.__class__._default_permission_updater,
)
elif isinstance(exc, PausedException):
except PausedException:
type_ = await self.update_type(bot, event, stack, dependency_cache)
permission = await self.update_permission(
bot, event, stack, dependency_cache
@@ -958,3 +890,5 @@ class Matcher(metaclass=MatcherMeta):
default_type_updater=self.__class__._default_type_updater,
default_permission_updater=self.__class__._default_permission_updater,
)
except FinishedException:
pass
+2 -2
View File
@@ -1,7 +1,7 @@
import abc
from typing import TYPE_CHECKING
from collections import defaultdict
from collections.abc import Mapping, MutableMapping
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .matcher import Matcher
@@ -19,7 +19,7 @@ class MatcherProvider(abc.ABC, MutableMapping[int, list[type["Matcher"]]]):
raise NotImplementedError
class _DictProvider(defaultdict[int, list[type["Matcher"]]], MatcherProvider): # type: ignore
class _DictProvider(defaultdict, MatcherProvider):
def __init__(self, matchers: Mapping[int, list[type["Matcher"]]]):
super().__init__(list, matchers)
+90 -200
View File
@@ -1,56 +1,52 @@
from collections.abc import Callable
from contextlib import AsyncExitStack, asynccontextmanager, contextmanager
from enum import Enum
import asyncio
import inspect
from typing_extensions import Self, get_args, override, get_origin
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
from typing import (
TYPE_CHECKING,
Annotated,
Any,
Union,
Literal,
Callable,
Optional,
Annotated,
cast,
get_args,
get_origin,
)
from typing_extensions import Self, override
import anyio
from exceptiongroup import BaseExceptionGroup, catch
from pydantic.fields import FieldInfo as PydanticFieldInfo
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
from nonebot.consts import ARG_KEY, REJECT_PROMPT_RESULT_KEY
from nonebot.dependencies import Dependent, Param
from nonebot.dependencies import Param, Dependent
from nonebot.dependencies.utils import check_field_type
from nonebot.exception import SkippedException
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined, extract_field_info
from nonebot.typing import (
_STATE_FLAG,
T_DependencyCache,
T_Handler,
T_State,
T_Handler,
T_DependencyCache,
origin_is_annotated,
)
from nonebot.utils import (
generic_check_issubclass,
get_name,
run_sync,
is_gen_callable,
run_sync_ctx_manager,
is_async_gen_callable,
is_coroutine_callable,
is_gen_callable,
run_sync,
run_sync_ctx_manager,
generic_check_issubclass,
)
if TYPE_CHECKING:
from nonebot.adapters import Bot, Event, Message
from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Event
class DependsInner:
def __init__(
self,
dependency: T_Handler | None = None,
dependency: Optional[T_Handler] = None,
*,
use_cache: bool = True,
validate: bool | PydanticFieldInfo = False,
validate: Union[bool, PydanticFieldInfo] = False,
) -> None:
self.dependency = dependency
self.use_cache = use_cache
@@ -64,10 +60,10 @@ class DependsInner:
def Depends(
dependency: T_Handler | None = None,
dependency: Optional[T_Handler] = None,
*,
use_cache: bool = True,
validate: bool | PydanticFieldInfo = False,
validate: Union[bool, PydanticFieldInfo] = False,
) -> Any:
"""子依赖装饰器
@@ -97,78 +93,6 @@ def Depends(
return DependsInner(dependency, use_cache=use_cache, validate=validate)
class CacheState(str, Enum):
"""子依赖缓存状态"""
PENDING = "PENDING"
FINISHED = "FINISHED"
class DependencyCache:
"""子依赖结果缓存。
用于缓存子依赖的结果,以避免重复计算。
"""
def __init__(self):
self._state = CacheState.PENDING
self._result: Any = None
self._exception: BaseException | None = None
self._waiter = anyio.Event()
def done(self) -> bool:
return self._state == CacheState.FINISHED
def result(self) -> Any:
"""获取子依赖结果"""
if self._state != CacheState.FINISHED:
raise RuntimeError("Result is not ready")
if self._exception is not None:
raise self._exception
return self._result
def exception(self) -> BaseException | None:
"""获取子依赖异常"""
if self._state != CacheState.FINISHED:
raise RuntimeError("Result is not ready")
return self._exception
def set_result(self, result: Any) -> None:
"""设置子依赖结果"""
if self._state != CacheState.PENDING:
raise RuntimeError(f"Cache state invalid: {self._state}")
self._result = result
self._state = CacheState.FINISHED
self._waiter.set()
def set_exception(self, exception: BaseException) -> None:
"""设置子依赖异常"""
if self._state != CacheState.PENDING:
raise RuntimeError(f"Cache state invalid: {self._state}")
self._exception = exception
self._state = CacheState.FINISHED
self._waiter.set()
async def wait(self):
"""等待子依赖结果"""
await self._waiter.wait()
if self._state != CacheState.FINISHED:
raise RuntimeError("Invalid cache state")
if self._exception is not None:
raise self._exception
return self._result
class DependParam(Param):
"""子依赖注入参数。
@@ -192,20 +116,23 @@ class DependParam(Param):
cls,
sub_dependent: Dependent[Any],
use_cache: bool,
validate: bool | PydanticFieldInfo,
validate: Union[bool, PydanticFieldInfo],
) -> Self:
return cls._inherit_construct(
validate if isinstance(validate, PydanticFieldInfo) else None,
dependent=sub_dependent,
use_cache=use_cache,
validate=bool(validate),
)
kwargs = {}
if isinstance(validate, PydanticFieldInfo):
kwargs.update(extract_field_info(validate))
kwargs["validate"] = bool(validate)
kwargs["dependent"] = sub_dependent
kwargs["use_cache"] = use_cache
return cls(**kwargs)
@classmethod
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
) -> Self | None:
) -> Optional[Self]:
type_annotation, depends_inner = param.annotation, None
# extract type annotation and dependency from Annotated
if get_origin(param.annotation) is Annotated:
@@ -225,9 +152,9 @@ class DependParam(Param):
dependency: T_Handler
# sub dependency is not specified, use type annotation
if depends_inner.dependency is None:
assert type_annotation is not inspect.Signature.empty, (
"Dependency cannot be empty"
)
assert (
type_annotation is not inspect.Signature.empty
), "Dependency cannot be empty"
dependency = type_annotation
else:
dependency = depends_inner.dependency
@@ -245,7 +172,7 @@ class DependParam(Param):
@override
def _check_parameterless(
cls, value: Any, allow_types: tuple[type[Param], ...]
) -> "Param | None":
) -> Optional["Param"]:
if isinstance(value, DependsInner):
assert value.dependency, "Dependency cannot be empty"
dependent = Dependent[Any].parse(
@@ -256,8 +183,8 @@ class DependParam(Param):
@override
async def _solve(
self,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
**kwargs: Any,
) -> Any:
use_cache: bool = self.use_cache
@@ -267,56 +194,35 @@ class DependParam(Param):
call = cast(Callable[..., Any], sub_dependent.call)
# solve sub dependency with current cache
exc: BaseExceptionGroup[SkippedException] | None = None
def _handle_skipped(exc_group: BaseExceptionGroup[SkippedException]):
nonlocal exc
exc = exc_group
with catch({SkippedException: _handle_skipped}):
sub_values = await sub_dependent.solve(
stack=stack,
dependency_cache=dependency_cache,
**kwargs,
)
if exc is not None:
raise exc
sub_values = await sub_dependent.solve(
stack=stack,
dependency_cache=dependency_cache,
**kwargs,
)
# run dependency function
task: asyncio.Task[Any]
if use_cache and call in dependency_cache:
return await dependency_cache[call].wait()
if is_gen_callable(call) or is_async_gen_callable(call):
assert isinstance(stack, AsyncExitStack), (
"Generator dependency should be called in context"
)
return await dependency_cache[call]
elif is_gen_callable(call) or is_async_gen_callable(call):
assert isinstance(
stack, AsyncExitStack
), "Generator dependency should be called in context"
if is_gen_callable(call):
cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
else:
cm = asynccontextmanager(call)(**sub_values)
target = stack.enter_async_context(cm)
task = asyncio.create_task(stack.enter_async_context(cm))
dependency_cache[call] = task
return await task
elif is_coroutine_callable(call):
target = call(**sub_values)
task = asyncio.create_task(call(**sub_values))
dependency_cache[call] = task
return await task
else:
target = run_sync(call)(**sub_values)
dependency_cache[call] = cache = DependencyCache()
try:
result = await target
except Exception as e:
cache.set_exception(e)
raise
except BaseException as e:
cache.set_exception(e)
# remove cache when base exception occurs
# e.g. CancelledError
dependency_cache.pop(call, None)
raise
else:
cache.set_result(result)
return result
task = asyncio.create_task(run_sync(call)(**sub_values))
dependency_cache[call] = task
return await task
@override
async def _check(self, **kwargs: Any) -> None:
@@ -332,7 +238,9 @@ class BotParam(Param):
为保证兼容性,本注入还会解析名为 `bot` 且没有类型注解的参数。
"""
def __init__(self, *args, checker: ModelField | None = None, **kwargs: Any) -> None:
def __init__(
self, *args, checker: Optional[ModelField] = None, **kwargs: Any
) -> None:
super().__init__(*args, **kwargs)
self.checker = checker
@@ -347,12 +255,12 @@ class BotParam(Param):
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
) -> Self | None:
) -> Optional[Self]:
from nonebot.adapters import Bot
# param type is Bot(s) or subclass(es) of Bot or None
if generic_check_issubclass(param.annotation, Bot):
checker: ModelField | None = None
checker: Optional[ModelField] = None
if param.annotation is not Bot:
checker = ModelField.construct(
name=param.name, annotation=param.annotation, field_info=FieldInfo()
@@ -384,7 +292,9 @@ class EventParam(Param):
为保证兼容性,本注入还会解析名为 `event` 且没有类型注解的参数。
"""
def __init__(self, *args, checker: ModelField | None = None, **kwargs: Any) -> None:
def __init__(
self, *args, checker: Optional[ModelField] = None, **kwargs: Any
) -> None:
super().__init__(*args, **kwargs)
self.checker = checker
@@ -399,12 +309,12 @@ class EventParam(Param):
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
) -> Self | None:
) -> Optional[Self]:
from nonebot.adapters import Event
# param type is Event(s) or subclass(es) of Event or None
if generic_check_issubclass(param.annotation, Event):
checker: ModelField | None = None
checker: Optional[ModelField] = None
if param.annotation is not Event:
checker = ModelField.construct(
name=param.name, annotation=param.annotation, field_info=FieldInfo()
@@ -443,7 +353,7 @@ class StateParam(Param):
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
) -> Self | None:
) -> Optional[Self]:
# param type is T_State
if origin_is_annotated(
get_origin(param.annotation)
@@ -468,7 +378,9 @@ class MatcherParam(Param):
为保证兼容性,本注入还会解析名为 `matcher` 且没有类型注解的参数。
"""
def __init__(self, *args, checker: ModelField | None = None, **kwargs: Any) -> None:
def __init__(
self, *args, checker: Optional[ModelField] = None, **kwargs: Any
) -> None:
super().__init__(*args, **kwargs)
self.checker = checker
@@ -483,12 +395,12 @@ class MatcherParam(Param):
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
) -> Self | None:
) -> Optional[Self]:
from nonebot.matcher import Matcher
# param type is Matcher(s) or subclass(es) of Matcher or None
if generic_check_issubclass(param.annotation, Matcher):
checker: ModelField | None = None
checker: Optional[ModelField] = None
if param.annotation is not Matcher:
checker = ModelField.construct(
name=param.name, annotation=param.annotation, field_info=FieldInfo()
@@ -514,35 +426,30 @@ class MatcherParam(Param):
class ArgInner:
def __init__(
self, key: str | None, type: Literal["message", "str", "plaintext", "prompt"]
self, key: Optional[str], type: Literal["message", "str", "plaintext"]
) -> None:
self.key: str | None = key
self.type: Literal["message", "str", "plaintext", "prompt"] = type
self.key: Optional[str] = key
self.type: Literal["message", "str", "plaintext"] = type
def __repr__(self) -> str:
return f"ArgInner(key={self.key!r}, type={self.type!r})"
def Arg(key: str | None = None) -> Any:
def Arg(key: Optional[str] = None) -> Any:
"""Arg 参数消息"""
return ArgInner(key, "message")
def ArgStr(key: str | None = None) -> str:
def ArgStr(key: Optional[str] = None) -> str:
"""Arg 参数消息文本"""
return ArgInner(key, "str") # type: ignore
def ArgPlainText(key: str | None = None) -> str:
def ArgPlainText(key: Optional[str] = None) -> str:
"""Arg 参数消息纯文本"""
return ArgInner(key, "plaintext") # type: ignore
def ArgPromptResult(key: str | None = None) -> Any:
"""`arg` prompt 发送结果"""
return ArgInner(key, "prompt")
class ArgParam(Param):
"""Arg 注入参数
@@ -556,7 +463,7 @@ class ArgParam(Param):
self,
*args,
key: str,
type: Literal["message", "str", "plaintext", "prompt"],
type: Literal["message", "str", "plaintext"],
**kwargs: Any,
) -> None:
super().__init__(*args, **kwargs)
@@ -570,7 +477,7 @@ class ArgParam(Param):
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
) -> Self | None:
) -> Optional[Self]:
if isinstance(param.default, ArgInner):
return cls(key=param.default.key or param.name, type=param.default.type)
elif get_origin(param.annotation) is Annotated:
@@ -581,32 +488,15 @@ class ArgParam(Param):
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
self, matcher: "Matcher", **kwargs: Any
) -> Any:
message = matcher.get_arg(self.key)
if message is None:
return message
if self.type == "message":
return self._solve_message(matcher)
return message
elif self.type == "str":
return self._solve_str(matcher)
elif self.type == "plaintext":
return self._solve_plaintext(matcher)
elif self.type == "prompt":
return self._solve_prompt(matcher)
return str(message)
else:
raise ValueError(f"Unknown Arg type: {self.type}")
def _solve_message(self, matcher: "Matcher") -> "Message | None":
return matcher.get_arg(self.key)
def _solve_str(self, matcher: "Matcher") -> str | None:
message = matcher.get_arg(self.key)
return str(message) if message is not None else None
def _solve_plaintext(self, matcher: "Matcher") -> str | None:
message = matcher.get_arg(self.key)
return message.extract_plain_text() if message is not None else None
def _solve_prompt(self, matcher: "Matcher") -> Any | None:
return matcher.state.get(
REJECT_PROMPT_RESULT_KEY.format(key=ARG_KEY.format(key=self.key))
)
return message.extract_plain_text()
class ExceptionParam(Param):
@@ -624,7 +514,7 @@ class ExceptionParam(Param):
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
) -> Self | None:
) -> Optional[Self]:
# param type is Exception(s) or subclass(es) of Exception or None
if generic_check_issubclass(param.annotation, Exception):
return cls()
@@ -633,7 +523,7 @@ class ExceptionParam(Param):
return cls()
@override
async def _solve(self, exception: Exception | None = None, **kwargs: Any) -> Any:
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
return exception
@@ -652,7 +542,7 @@ class DefaultParam(Param):
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
) -> Self | None:
) -> Optional[Self]:
if param.default != param.empty:
return cls(default=param.default)
+38 -37
View File
@@ -1,16 +1,15 @@
from contextlib import AsyncExitStack
from typing import ClassVar, NoReturn
import asyncio
from typing_extensions import Self
import anyio
from contextlib import AsyncExitStack
from typing import Union, ClassVar, NoReturn, Optional
from nonebot.dependencies import Dependent
from nonebot.utils import run_coro_with_catch
from nonebot.exception import SkippedException
from nonebot.typing import T_DependencyCache, T_PermissionChecker
from nonebot.utils import run_coro_with_catch
from .adapter import Bot, Event
from .params import BotParam, DefaultParam, DependParam, EventParam, Param
from .params import Param, BotParam, EventParam, DependParam, DefaultParam
class Permission:
@@ -38,7 +37,7 @@ class Permission:
DefaultParam,
]
def __init__(self, *checkers: T_PermissionChecker | Dependent[bool]) -> None:
def __init__(self, *checkers: Union[T_PermissionChecker, Dependent[bool]]) -> None:
self.checkers: set[Dependent[bool]] = {
(
checker
@@ -58,8 +57,8 @@ class Permission:
self,
bot: Bot,
event: Event,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""检查是否满足某个权限。
@@ -71,31 +70,29 @@ class Permission:
"""
if not self.checkers:
return True
result = False
async def _run_checker(checker: Dependent[bool]) -> None:
nonlocal result
# calculate the result first to avoid data racing
is_passed = await run_coro_with_catch(
checker(
bot=bot, event=event, stack=stack, dependency_cache=dependency_cache
),
(SkippedException,),
False,
)
result |= is_passed
async with anyio.create_task_group() as tg:
for checker in self.checkers:
tg.start_soon(_run_checker, checker)
return result
results = await asyncio.gather(
*(
run_coro_with_catch(
checker(
bot=bot,
event=event,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
False,
)
for checker in self.checkers
),
)
return any(results)
def __and__(self, other: object) -> NoReturn:
raise RuntimeError("And operation between Permissions is not allowed.")
def __or__(self, other: "Permission | T_PermissionChecker | None") -> "Permission":
def __or__(
self, other: Optional[Union["Permission", T_PermissionChecker]]
) -> "Permission":
if other is None:
return self
elif isinstance(other, Permission):
@@ -103,7 +100,9 @@ class Permission:
else:
return Permission(*self.checkers, other)
def __ror__(self, other: "Permission | T_PermissionChecker | None") -> "Permission":
def __ror__(
self, other: Optional[Union["Permission", T_PermissionChecker]]
) -> "Permission":
if other is None:
return self
elif isinstance(other, Permission):
@@ -120,9 +119,11 @@ class User:
perm: 需同时满足的权限
"""
__slots__ = ("perm", "users")
__slots__ = ("users", "perm")
def __init__(self, users: tuple[str, ...], perm: Permission | None = None) -> None:
def __init__(
self, users: tuple[str, ...], perm: Optional[Permission] = None
) -> None:
self.users = users
self.perm = perm
@@ -143,7 +144,7 @@ class User:
)
@classmethod
def _clean_permission(cls, perm: Permission) -> Permission | None:
def _clean_permission(cls, perm: Permission) -> Optional[Permission]:
if len(perm.checkers) == 1 and isinstance(
user_perm := next(iter(perm.checkers)).call, cls
):
@@ -151,7 +152,7 @@ class User:
return perm
@classmethod
def from_event(cls, event: Event, perm: Permission | None = None) -> Self:
def from_event(cls, event: Event, perm: Optional[Permission] = None) -> Self:
"""从事件中获取会话 ID。
如果 `perm` 中仅有 `User` 类型的权限检查函数,则会去除原有的会话 ID 限制。
@@ -163,7 +164,7 @@ class User:
return cls((event.get_session_id(),), perm=perm and cls._clean_permission(perm))
@classmethod
def from_permission(cls, *users: str, perm: Permission | None = None) -> Self:
def from_permission(cls, *users: str, perm: Optional[Permission] = None) -> Self:
"""指定会话与权限。
如果 `perm` 中仅有 `User` 类型的权限检查函数,则会去除原有的会话 ID 限制。
@@ -175,7 +176,7 @@ class User:
return cls(users, perm=perm and cls._clean_permission(perm))
def USER(*users: str, perm: Permission | None = None):
def USER(*users: str, perm: Optional[Permission] = None):
"""匹配当前事件属于指定会话。
如果 `perm` 中仅有 `User` 类型的权限检查函数,则会去除原有检查函数的会话 ID 限制。
+24 -37
View File
@@ -1,15 +1,13 @@
import asyncio
from contextlib import AsyncExitStack
from typing import ClassVar, NoReturn
import anyio
from exceptiongroup import BaseExceptionGroup, catch
from typing import Union, ClassVar, NoReturn, Optional
from nonebot.dependencies import Dependent
from nonebot.exception import SkippedException
from nonebot.typing import T_DependencyCache, T_RuleChecker, T_State
from nonebot.typing import T_State, T_RuleChecker, T_DependencyCache
from .adapter import Bot, Event
from .params import BotParam, DefaultParam, DependParam, EventParam, Param, StateParam
from .params import Param, BotParam, EventParam, StateParam, DependParam, DefaultParam
class Rule:
@@ -38,7 +36,7 @@ class Rule:
DefaultParam,
]
def __init__(self, *checkers: T_RuleChecker | Dependent[bool]) -> None:
def __init__(self, *checkers: Union[T_RuleChecker, Dependent[bool]]) -> None:
self.checkers: set[Dependent[bool]] = {
(
checker
@@ -59,8 +57,8 @@ class Rule:
bot: Bot,
event: Event,
state: T_State,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""检查是否符合所有规则
@@ -73,35 +71,24 @@ class Rule:
"""
if not self.checkers:
return True
result = True
def _handle_skipped_exception(
exc_group: BaseExceptionGroup[SkippedException],
) -> None:
nonlocal result
result = False
async def _run_checker(checker: Dependent[bool]) -> None:
nonlocal result
# calculate the result first to avoid data racing
is_passed = await checker(
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
try:
results = await asyncio.gather(
*(
checker(
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
)
for checker in self.checkers
)
)
result &= is_passed
except SkippedException:
return False
return all(results)
with catch({SkippedException: _handle_skipped_exception}):
async with anyio.create_task_group() as tg:
for checker in self.checkers:
tg.start_soon(_run_checker, checker)
return result
def __and__(self, other: "Rule | T_RuleChecker | None") -> "Rule":
def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
if other is None:
return self
elif isinstance(other, Rule):
@@ -109,7 +96,7 @@ class Rule:
else:
return Rule(*self.checkers, other)
def __rand__(self, other: "Rule | T_RuleChecker | None") -> "Rule":
def __rand__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
if other is None:
return self
elif isinstance(other, Rule):
+1 -3
View File
@@ -8,15 +8,13 @@ NoneBot 使用 [`loguru`][loguru] 来记录日志信息。
[loguru]: https://github.com/Delgan/loguru
FrontMatter:
mdx:
format: md
sidebar_position: 7
description: nonebot.log 模块
"""
import sys
import inspect
import logging
import sys
from typing import TYPE_CHECKING
import loguru
+5 -7
View File
@@ -1,22 +1,20 @@
"""本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。
FrontMatter:
mdx:
format: md
sidebar_position: 3
description: nonebot.matcher 模块
"""
from nonebot.internal.matcher import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
from nonebot.internal.matcher import Matcher as Matcher
from nonebot.internal.matcher import matchers as matchers
from nonebot.internal.matcher import current_bot as current_bot
from nonebot.internal.matcher import MatcherSource as MatcherSource
from nonebot.internal.matcher import current_event as current_event
from nonebot.internal.matcher import MatcherManager as MatcherManager
from nonebot.internal.matcher import MatcherProvider as MatcherProvider
from nonebot.internal.matcher import MatcherSource as MatcherSource
from nonebot.internal.matcher import current_bot as current_bot
from nonebot.internal.matcher import current_event as current_event
from nonebot.internal.matcher import current_handler as current_handler
from nonebot.internal.matcher import current_matcher as current_matcher
from nonebot.internal.matcher import matchers as matchers
from nonebot.internal.matcher import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
__autodoc__ = {
"Matcher": True,
+134 -180
View File
@@ -3,54 +3,44 @@
NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供了多个插槽以进行事件的预处理等。
FrontMatter:
mdx:
format: md
sidebar_position: 2
description: nonebot.message 模块
"""
from collections.abc import Callable
import asyncio
import contextlib
from contextlib import AsyncExitStack
from datetime import datetime
from typing import TYPE_CHECKING, Any
import anyio
from exceptiongroup import BaseExceptionGroup, catch
from contextlib import AsyncExitStack
from typing import TYPE_CHECKING, Any, Optional
from nonebot.log import logger
from nonebot.rule import TrieRule
from nonebot.dependencies import Dependent
from nonebot.matcher import Matcher, matchers
from nonebot.utils import escape_tag, run_coro_with_catch
from nonebot.exception import (
IgnoredException,
NoLogException,
SkippedException,
StopPropagation,
IgnoredException,
SkippedException,
)
from nonebot.typing import (
T_State,
T_DependencyCache,
T_RunPreProcessor,
T_RunPostProcessor,
T_EventPreProcessor,
T_EventPostProcessor,
)
from nonebot.internal.params import (
ArgParam,
BotParam,
DefaultParam,
DependParam,
EventParam,
ExceptionParam,
MatcherParam,
StateParam,
)
from nonebot.log import logger
from nonebot.matcher import Matcher, matchers
from nonebot.rule import TrieRule
from nonebot.typing import (
T_DependencyCache,
T_EventPostProcessor,
T_EventPreProcessor,
T_RunPostProcessor,
T_RunPreProcessor,
T_State,
)
from nonebot.utils import (
escape_tag,
flatten_exception_group,
run_coro_with_catch,
run_coro_with_shield,
DependParam,
DefaultParam,
MatcherParam,
ExceptionParam,
)
if TYPE_CHECKING:
@@ -133,29 +123,12 @@ def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
return func
def _handle_ignored_exception(
msg: str,
) -> Callable[[BaseExceptionGroup[IgnoredException]], None]:
def _handle(exc_group: BaseExceptionGroup[IgnoredException]) -> None:
logger.opt(colors=True).info(msg)
return _handle
def _handle_exception(msg: str) -> Callable[[BaseExceptionGroup[Exception]], None]:
def _handle(exc_group: BaseExceptionGroup[Exception]) -> None:
for exc in flatten_exception_group(exc_group):
logger.opt(colors=True, exception=exc).error(msg)
return _handle
async def _apply_event_preprocessors(
bot: "Bot",
event: "Event",
state: T_State,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
show_log: bool = True,
) -> bool:
"""运行事件预处理。
@@ -177,21 +150,10 @@ async def _apply_event_preprocessors(
if show_log:
logger.debug("Running PreProcessors...")
with catch(
{
IgnoredException: _handle_ignored_exception(
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
),
Exception: _handle_exception(
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
"Event ignored!</bg #f8bbd0></r>"
),
}
):
async with anyio.create_task_group() as tg:
for proc in _event_preprocessors:
tg.start_soon(
run_coro_with_catch,
try:
await asyncio.gather(
*(
run_coro_with_catch(
proc(
bot=bot,
event=event,
@@ -201,18 +163,30 @@ async def _apply_event_preprocessors(
),
(SkippedException,),
)
for proc in _event_preprocessors
)
)
except IgnoredException:
logger.opt(colors=True).info(
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
)
return False
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
"Event ignored!</bg #f8bbd0></r>"
)
return False
return True
return False
return True
async def _apply_event_postprocessors(
bot: "Bot",
event: "Event",
state: T_State,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
show_log: bool = True,
) -> None:
"""运行事件后处理。
@@ -231,17 +205,10 @@ async def _apply_event_postprocessors(
if show_log:
logger.debug("Running PostProcessors...")
with catch(
{
Exception: _handle_exception(
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
)
}
):
async with anyio.create_task_group() as tg:
for proc in _event_postprocessors:
tg.start_soon(
run_coro_with_catch,
try:
await asyncio.gather(
*(
run_coro_with_catch(
proc(
bot=bot,
event=event,
@@ -251,6 +218,13 @@ async def _apply_event_postprocessors(
),
(SkippedException,),
)
for proc in _event_postprocessors
)
)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
)
async def _apply_run_preprocessors(
@@ -258,8 +232,8 @@ async def _apply_run_preprocessors(
event: "Event",
state: T_State,
matcher: Matcher,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""运行事件响应器运行前处理。
@@ -278,47 +252,44 @@ async def _apply_run_preprocessors(
return True
# ensure matcher function can be correctly called
with (
matcher.ensure_context(bot, event),
catch(
{
IgnoredException: _handle_ignored_exception(
f"{matcher} running is <b>cancelled</b>"
),
Exception: _handle_exception(
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
"Running cancelled!</bg #f8bbd0></r>"
),
}
),
):
async with anyio.create_task_group() as tg:
for proc in _run_preprocessors:
tg.start_soon(
run_coro_with_catch,
proc(
matcher=matcher,
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
with matcher.ensure_context(bot, event):
try:
await asyncio.gather(
*(
run_coro_with_catch(
proc(
matcher=matcher,
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
)
for proc in _run_preprocessors
)
)
except IgnoredException:
logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
return False
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
"Running cancelled!</bg #f8bbd0></r>"
)
return False
return True
return False
return True
async def _apply_run_postprocessors(
bot: "Bot",
event: "Event",
matcher: Matcher,
exception: Exception | None = None,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
exception: Optional[Exception] = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> None:
"""运行事件响应器运行后处理。
@@ -333,32 +304,29 @@ async def _apply_run_postprocessors(
if not _run_postprocessors:
return
with (
matcher.ensure_context(bot, event),
catch(
{
Exception: _handle_exception(
"<r><bg #f8bbd0>Error when running RunPostProcessors"
"</bg #f8bbd0></r>"
)
}
),
):
async with anyio.create_task_group() as tg:
for proc in _run_postprocessors:
tg.start_soon(
run_coro_with_catch,
proc(
matcher=matcher,
exception=exception,
bot=bot,
event=event,
state=matcher.state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
with matcher.ensure_context(bot, event):
try:
await asyncio.gather(
*(
run_coro_with_catch(
proc(
matcher=matcher,
exception=exception,
bot=bot,
event=event,
state=matcher.state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
)
for proc in _run_postprocessors
)
)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>"
)
async def _check_matcher(
@@ -366,8 +334,8 @@ async def _check_matcher(
bot: "Bot",
event: "Event",
state: T_State,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""检查事件响应器是否符合运行条件。
@@ -417,8 +385,8 @@ async def _run_matcher(
bot: "Bot",
event: "Event",
state: T_State,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> None:
"""运行事件响应器。
@@ -455,9 +423,8 @@ async def _run_matcher(
exception = None
logger.debug(f"Running {matcher}")
try:
logger.debug(f"Running {matcher}")
await matcher.run(bot, event, state, stack, dependency_cache)
except Exception as e:
logger.opt(colors=True, exception=e).error(
@@ -483,8 +450,8 @@ async def check_and_run_matcher(
bot: "Bot",
event: "Event",
state: T_State,
stack: AsyncExitStack | None = None,
dependency_cache: T_DependencyCache | None = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> None:
"""检查并运行事件响应器。
@@ -525,7 +492,8 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
用法:
```python
driver.task_group.start_soon(handle_event, bot, event)
import asyncio
asyncio.create_task(handle_event(bot, event))
```
"""
show_log = True
@@ -560,13 +528,6 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
)
break_flag = False
def _handle_stop_propagation(exc_group: BaseExceptionGroup) -> None:
nonlocal break_flag
break_flag = True
logger.debug("Stop event propagation")
# iterate through all priority until stop propagation
for priority in sorted(matchers.keys()):
if break_flag:
@@ -575,30 +536,23 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
if show_log:
logger.debug(f"Checking for matchers in priority {priority}...")
if not (priority_matchers := matchers[priority]):
continue
with catch(
{
StopPropagation: _handle_stop_propagation,
Exception: _handle_exception(
pending_tasks = [
check_and_run_matcher(
matcher, bot, event, state.copy(), stack, dependency_cache
)
for matcher in matchers[priority]
]
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
for result in results:
if not isinstance(result, Exception):
continue
if isinstance(result, StopPropagation):
break_flag = True
logger.debug("Stop event propagation")
else:
logger.opt(colors=True, exception=result).error(
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
),
}
):
async with anyio.create_task_group() as tg:
for matcher in priority_matchers:
tg.start_soon(
run_coro_with_shield,
check_and_run_matcher(
matcher,
bot,
event,
state.copy(),
stack,
dependency_cache,
),
)
)
if show_log:
logger.debug("Checking for matchers completed")
+33 -61
View File
@@ -1,50 +1,43 @@
"""本模块定义了依赖注入的各类参数。
FrontMatter:
mdx:
format: md
sidebar_position: 4
description: nonebot.params 模块
"""
from collections.abc import Callable
from re import Match
from typing import Any, Literal, overload
from typing import Any, Union, Literal, Callable, Optional, overload
from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.internal.params import Arg as Arg
from nonebot.internal.params import ArgStr as ArgStr
from nonebot.internal.params import Depends as Depends
from nonebot.internal.params import ArgParam as ArgParam
from nonebot.internal.params import BotParam as BotParam
from nonebot.adapters import Event, Message, MessageSegment
from nonebot.internal.params import EventParam as EventParam
from nonebot.internal.params import StateParam as StateParam
from nonebot.internal.params import DependParam as DependParam
from nonebot.internal.params import ArgPlainText as ArgPlainText
from nonebot.internal.params import DefaultParam as DefaultParam
from nonebot.internal.params import MatcherParam as MatcherParam
from nonebot.internal.params import ExceptionParam as ExceptionParam
from nonebot.consts import (
CMD_ARG_KEY,
CMD_KEY,
CMD_START_KEY,
CMD_WHITESPACE_KEY,
ENDSWITH_KEY,
FULLMATCH_KEY,
KEYWORD_KEY,
PAUSE_PROMPT_RESULT_KEY,
PREFIX_KEY,
RAW_CMD_KEY,
RECEIVE_KEY,
REGEX_MATCHED,
REJECT_PROMPT_RESULT_KEY,
SHELL_ARGS,
SHELL_ARGV,
CMD_ARG_KEY,
KEYWORD_KEY,
RAW_CMD_KEY,
ENDSWITH_KEY,
CMD_START_KEY,
FULLMATCH_KEY,
REGEX_MATCHED,
STARTSWITH_KEY,
CMD_WHITESPACE_KEY,
)
from nonebot.internal.params import Arg as Arg
from nonebot.internal.params import ArgParam as ArgParam
from nonebot.internal.params import ArgPlainText as ArgPlainText
from nonebot.internal.params import ArgPromptResult as ArgPromptResult
from nonebot.internal.params import ArgStr as ArgStr
from nonebot.internal.params import BotParam as BotParam
from nonebot.internal.params import DefaultParam as DefaultParam
from nonebot.internal.params import DependParam as DependParam
from nonebot.internal.params import Depends as Depends
from nonebot.internal.params import EventParam as EventParam
from nonebot.internal.params import ExceptionParam as ExceptionParam
from nonebot.internal.params import MatcherParam as MatcherParam
from nonebot.internal.params import StateParam as StateParam
from nonebot.matcher import Matcher
from nonebot.typing import T_State
async def _event_type(event: Event) -> str:
@@ -137,7 +130,7 @@ def ShellCommandArgs() -> Any:
return Depends(_shell_command_args, use_cache=False)
def _shell_command_argv(state: T_State) -> list[str | MessageSegment]:
def _shell_command_argv(state: T_State) -> list[Union[str, MessageSegment]]:
return state[SHELL_ARGV]
@@ -156,31 +149,31 @@ def RegexMatched() -> Match[str]:
def _regex_str(
groups: tuple[str | int, ...],
) -> Callable[[T_State], str | tuple[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,
) -> str | tuple[str | Any, ...] | Any:
) -> Union[str, tuple[Union[str, Any], ...], Any]:
return _regex_matched(state).group(*groups)
return _regex_str_dependency
@overload
def RegexStr(group: Literal[0] = 0, /) -> str: ...
def RegexStr(__group: Literal[0] = 0) -> str: ...
@overload
def RegexStr(group: str | int, /) -> str | Any: ...
def RegexStr(__group: Union[str, int]) -> Union[str, Any]: ...
@overload
def RegexStr(
group1: str | int, group2: str | int, /, *groups: str | int
) -> tuple[str | Any, ...]: ...
__group1: Union[str, int], __group2: Union[str, int], *groups: Union[str, int]
) -> tuple[Union[str, Any], ...]: ...
def RegexStr(*groups: str | int) -> str | tuple[str | Any, ...] | Any:
def RegexStr(*groups: Union[str, int]) -> Union[str, tuple[Union[str, Any], ...], Any]:
"""正则匹配结果文本"""
return Depends(_regex_str(groups), use_cache=False)
@@ -239,7 +232,7 @@ def Keyword() -> str:
return Depends(_keyword, use_cache=False)
def Received(id: str | None = None, default: Any = None) -> Any:
def Received(id: Optional[str] = None, default: Any = None) -> Any:
"""`receive` 事件参数"""
def _received(matcher: "Matcher") -> Any:
@@ -257,26 +250,6 @@ def LastReceived(default: Any = None) -> Any:
return Depends(_last_received, use_cache=False)
def ReceivePromptResult(id: str | None = None) -> Any:
"""`receive` prompt 发送结果"""
def _receive_prompt_result(matcher: "Matcher") -> Any:
return matcher.state.get(
REJECT_PROMPT_RESULT_KEY.format(key=RECEIVE_KEY.format(id=id))
)
return Depends(_receive_prompt_result, use_cache=False)
def PausePromptResult() -> Any:
"""`pause` prompt 发送结果"""
def _pause_prompt_result(matcher: "Matcher") -> Any:
return matcher.state.get(PAUSE_PROMPT_RESULT_KEY)
return Depends(_pause_prompt_result, use_cache=False)
__autodoc__ = {
"Arg": True,
"ArgStr": True,
@@ -290,5 +263,4 @@ __autodoc__ = {
"DefaultParam": True,
"MatcherParam": True,
"ExceptionParam": True,
"ArgPromptResult": True,
}
+2 -4
View File
@@ -5,17 +5,15 @@
只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
FrontMatter:
mdx:
format: md
sidebar_position: 6
description: nonebot.permission 模块
"""
from nonebot.params import EventType
from nonebot.adapters import Bot, Event
from nonebot.internal.permission import USER as USER
from nonebot.internal.permission import Permission as Permission
from nonebot.internal.permission import User as User
from nonebot.params import EventType
from nonebot.internal.permission import Permission as Permission
class Message:
+33 -46
View File
@@ -32,28 +32,25 @@
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.model.PluginMetadata>`
FrontMatter:
mdx:
format: md
sidebar_position: 0
description: nonebot.plugin 模块
"""
from contextvars import ContextVar
from itertools import chain
from types import ModuleType
from typing import TypeVar
from contextvars import ContextVar
from typing import TypeVar, Optional
from pydantic import BaseModel
from nonebot import get_driver
from nonebot.compat import model_dump, type_validate_python
from nonebot.config import BaseSettings
C = TypeVar("C", bound=BaseModel)
_plugins: dict[str, "Plugin"] = {}
_managers: list["PluginManager"] = []
_current_plugin: ContextVar["Plugin | None"] = ContextVar(
_current_plugin: ContextVar[Optional["Plugin"]] = ContextVar(
"_current_plugin", default=None
)
@@ -71,8 +68,8 @@ def _controlled_modules() -> dict[str, str]:
def _find_parent_plugin_id(
module_name: str, controlled_modules: dict[str, str] | None = None
) -> str | None:
module_name: str, controlled_modules: Optional[dict[str, str]] = None
) -> Optional[str]:
if controlled_modules is None:
controlled_modules = _controlled_modules()
available = {
@@ -85,7 +82,7 @@ def _find_parent_plugin_id(
def _module_name_to_plugin_id(
module_name: str, controlled_modules: dict[str, str] | None = None
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):
@@ -132,7 +129,7 @@ def _revert_plugin(plugin: "Plugin") -> None:
parent_plugin.sub_plugins.discard(plugin)
def get_plugin(plugin_id: str) -> "Plugin | None":
def get_plugin(plugin_id: str) -> Optional["Plugin"]:
"""获取已经导入的某个插件。
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
@@ -145,7 +142,7 @@ def get_plugin(plugin_id: str) -> "Plugin | None":
return _plugins.get(plugin_id)
def get_plugin_by_module_name(module_name: str) -> "Plugin | None":
def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]:
"""通过模块名获取已经导入的某个插件。
如果提供的模块名为某个插件的子模块,同样会返回该插件。
@@ -173,43 +170,33 @@ def get_available_plugin_names() -> set[str]:
def get_plugin_config(config: type[C]) -> C:
"""从全局配置获取当前插件需要的配置项。"""
global_config = get_driver().config
return type_validate_python(
config,
BaseSettings._settings_build_values(
config,
model_dump(global_config),
env_file=global_config._env_file,
env_file_encoding=global_config._env_file_encoding,
env_nested_delimiter=global_config._env_nested_delimiter,
),
)
return type_validate_python(config, model_dump(get_driver().config))
from .load import inherit_supported_adapters as inherit_supported_adapters
from .on import on as on
from .manager import PluginManager
from .on import on_type as on_type
from .model import Plugin as Plugin
from .load import require as require
from .on import on_regex as on_regex
from .on import on_notice as on_notice
from .on import on_command as on_command
from .on import on_keyword as on_keyword
from .on import on_message as on_message
from .on import on_request as on_request
from .on import on_endswith as on_endswith
from .load import load_plugin as load_plugin
from .on import CommandGroup as CommandGroup
from .on import MatcherGroup as MatcherGroup
from .on import on_fullmatch as on_fullmatch
from .on import on_metaevent as on_metaevent
from .load import load_plugins as load_plugins
from .on import on_startswith as on_startswith
from .load import load_from_json as load_from_json
from .load import load_from_toml as load_from_toml
from .model import PluginMetadata as PluginMetadata
from .on import on_shell_command as on_shell_command
from .load import load_all_plugins as load_all_plugins
from .load import load_builtin_plugin as load_builtin_plugin
from .load import load_builtin_plugins as load_builtin_plugins
from .load import load_from_json as load_from_json
from .load import load_from_toml as load_from_toml
from .load import load_plugin as load_plugin
from .load import load_plugins as load_plugins
from .load import require as require
from .manager import PluginManager
from .model import Plugin as Plugin
from .model import PluginMetadata as PluginMetadata
from .on import CommandGroup as CommandGroup
from .on import MatcherGroup as MatcherGroup
from .on import on as on
from .on import on_command as on_command
from .on import on_endswith as on_endswith
from .on import on_fullmatch as on_fullmatch
from .on import on_keyword as on_keyword
from .on import on_message as on_message
from .on import on_metaevent as on_metaevent
from .on import on_notice as on_notice
from .on import on_regex as on_regex
from .on import on_request as on_request
from .on import on_shell_command as on_shell_command
from .on import on_startswith as on_startswith
from .on import on_type as on_type
from .load import inherit_supported_adapters as inherit_supported_adapters
+13 -40
View File
@@ -1,32 +1,29 @@
"""本模块定义插件加载接口。
FrontMatter:
mdx:
format: md
sidebar_position: 1
description: nonebot.plugin.load 模块
"""
from collections.abc import Iterable
from itertools import chain
import json
from pathlib import Path
from types import ModuleType
from typing import Union, Optional
from collections.abc import Iterable
from nonebot.log import logger
from nonebot.utils import path_to_module_name
from . import _managers, _module_name_to_plugin_id, get_plugin
from .manager import PluginManager
from .model import Plugin
from .manager import PluginManager
from . import _managers, get_plugin, _module_name_to_plugin_id
try: # pragma: py-gte-311
import tomllib # pyright: ignore[reportMissingImports]
except ModuleNotFoundError: # pragma: py-lt-311
import tomli as tomllib # pyright: ignore[reportMissingImports]
import tomli as tomllib
def load_plugin(module_path: str | Path) -> Plugin | None:
def load_plugin(module_path: Union[str, Path]) -> Optional[Plugin]:
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
参数:
@@ -109,19 +106,6 @@ def load_from_toml(file_path: str, encoding: str = "utf-8") -> set[Plugin]:
encoding: 指定 toml 文件编码
用法:
新格式:
```toml title=pyproject.toml
[tool.nonebot]
plugin_dirs = ["some_dir"]
[tool.nonebot.plugins]
some-store-plugin = ["some_store_plugin"]
"@local" = ["some_local_plugin"]
```
旧格式:
```toml title=pyproject.toml
[tool.nonebot]
plugins = ["some_plugin"]
@@ -140,25 +124,14 @@ def load_from_toml(file_path: str, encoding: str = "utf-8") -> set[Plugin]:
raise ValueError("Cannot find '[tool.nonebot]' in given toml file!")
if not isinstance(nonebot_data, dict):
raise TypeError("'[tool.nonebot]' must be a Table!")
plugins = nonebot_data.get("plugins", {})
plugins = nonebot_data.get("plugins", [])
plugin_dirs = nonebot_data.get("plugin_dirs", [])
assert isinstance(plugins, (list, dict)), (
"plugins must be a list or a dict of plugin name"
)
assert isinstance(plugins, list), "plugins must be a list of plugin name"
assert isinstance(plugin_dirs, list), "plugin_dirs must be a list of directories"
if isinstance(plugins, list):
logger.warning("Legacy project format found! Upgrade with `nb upgrade-format`.")
return load_all_plugins(
set(
chain.from_iterable(plugins.values())
if isinstance(plugins, dict)
else plugins
),
plugin_dirs,
)
return load_all_plugins(plugins, plugin_dirs)
def load_builtin_plugin(name: str) -> Plugin | None:
def load_builtin_plugin(name: str) -> Optional[Plugin]:
"""导入 NoneBot 内置插件。
参数:
@@ -176,7 +149,7 @@ def load_builtin_plugins(*plugins: str) -> set[Plugin]:
return load_all_plugins([f"nonebot.plugins.{p}" for p in plugins], [])
def _find_manager_by_name(name: str) -> PluginManager | None:
def _find_manager_by_name(name: str) -> Optional[PluginManager]:
for manager in reversed(_managers):
if (
name in manager.controlled_modules
@@ -216,7 +189,7 @@ def require(name: str) -> ModuleType:
return plugin.module
def inherit_supported_adapters(*names: str) -> set[str] | None:
def inherit_supported_adapters(*names: str) -> Optional[set[str]]:
"""获取已加载插件的适配器支持状态集合。
如果传入了多个插件名称,返回值会自动取交集。
@@ -228,7 +201,7 @@ def inherit_supported_adapters(*names: str) -> set[str] | None:
RuntimeError: 插件未加载
ValueError: 插件缺少元数据
"""
final_supported: set[str] | None = None
final_supported: Optional[set[str]] = None
for name in names:
plugin = get_plugin(_module_name_to_plugin_id(name))
+18 -19
View File
@@ -3,33 +3,32 @@
参考: [import hooks](https://docs.python.org/3/reference/import.html#import-hooks), [PEP302](https://www.python.org/dev/peps/pep-0302/)
FrontMatter:
mdx:
format: md
sidebar_position: 5
description: nonebot.plugin.manager 模块
"""
from collections.abc import Iterable, Sequence
import importlib
from importlib.abc import MetaPathFinder
from importlib.machinery import PathFinder, SourceFileLoader
from itertools import chain
from pathlib import Path
import pkgutil
import sys
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 nonebot.log import logger
from nonebot.utils import escape_tag, path_to_module_name
from .model import Plugin, PluginMetadata
from . import (
_current_plugin,
_managers,
_module_name_to_plugin_id,
_new_plugin,
_revert_plugin,
_current_plugin,
_module_name_to_plugin_id,
)
from .model import Plugin, PluginMetadata
class PluginManager:
@@ -42,8 +41,8 @@ class PluginManager:
def __init__(
self,
plugins: Iterable[str] | None = None,
search_path: Iterable[str] | None = None,
plugins: Optional[Iterable[str]] = None,
search_path: Optional[Iterable[str]] = None,
):
# simple plugin not in search path
self.plugins: set[str] = set(plugins or [])
@@ -153,7 +152,7 @@ class PluginManager:
return self.available_plugins
def load_plugin(self, name: str) -> Plugin | None:
def load_plugin(self, name: str) -> Optional[Plugin]:
"""加载指定插件。
可以使用完整插件模块名或者插件标识符加载。
@@ -210,8 +209,8 @@ class PluginFinder(MetaPathFinder):
def find_spec(
self,
fullname: str,
path: Sequence[str] | None,
target: ModuleType | None = None,
path: Optional[Sequence[str]],
target: Optional[ModuleType] = None,
):
if _managers:
module_spec = PathFinder.find_spec(fullname, path, target)
@@ -234,7 +233,7 @@ class PluginLoader(SourceFileLoader):
self.loaded = False
super().__init__(fullname, path)
def create_module(self, spec) -> ModuleType | None:
def create_module(self, spec) -> Optional[ModuleType]:
if self.name in sys.modules:
self.loaded = True
return sys.modules[self.name]
@@ -262,7 +261,7 @@ class PluginLoader(SourceFileLoader):
_current_plugin.reset(_plugin_token)
# get plugin metadata
metadata: PluginMetadata | None = getattr(module, "__plugin_meta__", None)
metadata: Optional[PluginMetadata] = getattr(module, "__plugin_meta__", None)
plugin.metadata = metadata
return
+9 -12
View File
@@ -1,16 +1,14 @@
"""本模块定义插件相关信息。
FrontMatter:
mdx:
format: md
sidebar_position: 3
description: nonebot.plugin.model 模块
"""
import contextlib
from dataclasses import dataclass, field
from types import ModuleType
from typing import TYPE_CHECKING, Any, Type # noqa: UP035
from dataclasses import field, dataclass
from typing import TYPE_CHECKING, Any, Type, Optional # noqa: UP035
from pydantic import BaseModel
@@ -33,13 +31,13 @@ class PluginMetadata:
"""插件功能介绍"""
usage: str
"""插件使用方法"""
type: str | None = None
type: Optional[str] = None
"""插件类型,用于商店分类"""
homepage: str | None = None
homepage: Optional[str] = None
"""插件主页"""
config: Type[BaseModel] | None = None # noqa: UP006
config: Optional[Type[BaseModel]] = None # noqa: UP006
"""插件配置项"""
supported_adapters: set[str] | None = None
supported_adapters: Optional[set[str]] = None
"""插件支持的适配器模块路径
格式为 `<module>[:<Adapter>]``~` 为 `nonebot.adapters.` 的缩写。
@@ -49,7 +47,7 @@ class PluginMetadata:
extra: dict[Any, Any] = field(default_factory=dict)
"""插件额外信息,可由插件编写者自由扩展定义"""
def get_supported_adapters(self) -> set[Type["Adapter"]] | None: # noqa: UP006
def get_supported_adapters(self) -> Optional[set[Type["Adapter"]]]: # noqa: UP006
"""获取当前已安装的插件支持适配器类列表"""
if self.supported_adapters is None:
return None
@@ -77,12 +75,11 @@ class Plugin:
"""导入该插件的插件管理器"""
matcher: set[type[Matcher]] = field(default_factory=set)
"""插件加载时定义的 `Matcher`"""
parent_plugin: "Plugin | None" = None
parent_plugin: Optional["Plugin"] = None
"""父插件"""
sub_plugins: set["Plugin"] = field(default_factory=set)
"""子插件集合"""
metadata: PluginMetadata | None = None
"""插件元信息"""
metadata: Optional[PluginMetadata] = None
@property
def id_(self) -> str:
+60 -58
View File
@@ -1,40 +1,38 @@
"""本模块定义事件响应器便携定义函数。
FrontMatter:
mdx:
format: md
sidebar_position: 2
description: nonebot.plugin.on 模块
"""
from datetime import datetime, timedelta
import inspect
import re
from types import ModuleType
from typing import Any
import inspect
import warnings
from types import ModuleType
from typing import Any, Union, Optional
from datetime import datetime, timedelta
from nonebot.adapters import Event
from nonebot.permission import Permission
from nonebot.dependencies import Dependent
from nonebot.matcher import Matcher, MatcherSource
from nonebot.permission import Permission
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
from nonebot.rule import (
ArgumentParser,
Rule,
ArgumentParser,
regex,
command,
endswith,
fullmatch,
is_type,
keyword,
regex,
shell_command,
endswith,
fullmatch,
startswith,
shell_command,
)
from nonebot.typing import T_Handler, T_PermissionChecker, T_RuleChecker, T_State
from . import get_plugin_by_module_name
from .manager import _current_plugin
from .model import Plugin
from .manager import _current_plugin
from . import get_plugin_by_module_name
def store_matcher(matcher: type[Matcher]) -> None:
@@ -48,7 +46,7 @@ def store_matcher(matcher: type[Matcher]) -> None:
plugin.matcher.add(matcher)
def get_matcher_plugin(depth: int = 1) -> Plugin | None: # pragma: no cover
def get_matcher_plugin(depth: int = 1) -> Optional[Plugin]: # pragma: no cover
"""获取事件响应器定义所在插件。
**Deprecated**, 请使用 {ref}`nonebot.plugin.on.get_matcher_source` 获取信息。
@@ -63,7 +61,7 @@ def get_matcher_plugin(depth: int = 1) -> Plugin | None: # pragma: no cover
return (source := get_matcher_source(depth + 1)) and source.plugin
def get_matcher_module(depth: int = 1) -> ModuleType | None: # pragma: no cover
def get_matcher_module(depth: int = 1) -> Optional[ModuleType]: # pragma: no cover
"""获取事件响应器定义所在模块。
**Deprecated**, 请使用 {ref}`nonebot.plugin.on.get_matcher_source` 获取信息。
@@ -78,7 +76,7 @@ def get_matcher_module(depth: int = 1) -> ModuleType | None: # pragma: no cover
return (source := get_matcher_source(depth + 1)) and source.module
def get_matcher_source(depth: int = 0) -> MatcherSource | None:
def get_matcher_source(depth: int = 0) -> Optional[MatcherSource]:
"""获取事件响应器定义所在源码信息。
参数:
@@ -99,7 +97,7 @@ def get_matcher_source(depth: int = 0) -> MatcherSource | None:
module_name = (module := inspect.getmodule(frame)) and module.__name__
# matcher defined when plugin loading
plugin: Plugin | None = _current_plugin.get()
plugin: Optional["Plugin"] = _current_plugin.get()
# matcher defined when plugin running
if plugin is None and module_name:
plugin = get_plugin_by_module_name(module_name)
@@ -113,15 +111,15 @@ def get_matcher_source(depth: int = 0) -> MatcherSource | None:
def on(
type: str = "",
rule: Rule | T_RuleChecker | None = None,
permission: Permission | T_PermissionChecker | None = None,
rule: Optional[Union[Rule, T_RuleChecker]] = None,
permission: Optional[Union[Permission, T_PermissionChecker]] = None,
*,
handlers: list[T_Handler | Dependent[Any]] | None = None,
handlers: Optional[list[Union[T_Handler, Dependent[Any]]]] = None,
temp: bool = False,
expire_time: datetime | timedelta | None = None,
expire_time: Optional[Union[datetime, timedelta]] = None,
priority: int = 1,
block: bool = False,
state: T_State | None = None,
state: Optional[T_State] = None,
_depth: int = 0,
) -> type[Matcher]:
"""注册一个基础事件响应器,可自定义类型。
@@ -219,8 +217,8 @@ def on_request(*args, _depth: int = 0, **kwargs) -> type[Matcher]:
def on_startswith(
msg: str | tuple[str, ...],
rule: Rule | T_RuleChecker | None = None,
msg: Union[str, tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = None,
ignorecase: bool = False,
_depth: int = 0,
**kwargs,
@@ -243,8 +241,8 @@ def on_startswith(
def on_endswith(
msg: str | tuple[str, ...],
rule: Rule | T_RuleChecker | None = None,
msg: Union[str, tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = None,
ignorecase: bool = False,
_depth: int = 0,
**kwargs,
@@ -267,8 +265,8 @@ def on_endswith(
def on_fullmatch(
msg: str | tuple[str, ...],
rule: Rule | T_RuleChecker | None = None,
msg: Union[str, tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = None,
ignorecase: bool = False,
_depth: int = 0,
**kwargs,
@@ -292,7 +290,7 @@ def on_fullmatch(
def on_keyword(
keywords: set[str],
rule: Rule | T_RuleChecker | None = None,
rule: Optional[Union[Rule, T_RuleChecker]] = None,
_depth: int = 0,
**kwargs,
) -> type[Matcher]:
@@ -313,10 +311,10 @@ def on_keyword(
def on_command(
cmd: str | tuple[str, ...],
rule: Rule | T_RuleChecker | None = None,
aliases: set[str | tuple[str, ...]] | None = None,
force_whitespace: str | bool | None = None,
cmd: Union[str, tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = None,
aliases: Optional[set[Union[str, tuple[str, ...]]]] = None,
force_whitespace: Optional[Union[str, bool]] = None,
_depth: int = 0,
**kwargs,
) -> type[Matcher]:
@@ -348,10 +346,10 @@ def on_command(
def on_shell_command(
cmd: str | tuple[str, ...],
rule: Rule | T_RuleChecker | None = None,
aliases: set[str | tuple[str, ...]] | None = None,
parser: ArgumentParser | None = None,
cmd: Union[str, tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = None,
aliases: Optional[set[Union[str, tuple[str, ...]]]] = None,
parser: Optional[ArgumentParser] = None,
_depth: int = 0,
**kwargs,
) -> type[Matcher]:
@@ -386,8 +384,8 @@ def on_shell_command(
def on_regex(
pattern: str,
flags: int | re.RegexFlag = 0,
rule: Rule | T_RuleChecker | None = None,
flags: Union[int, re.RegexFlag] = 0,
rule: Optional[Union[Rule, T_RuleChecker]] = None,
_depth: int = 0,
**kwargs,
) -> type[Matcher]:
@@ -411,8 +409,8 @@ def on_regex(
def on_type(
types: type[Event] | tuple[type[Event], ...],
rule: Rule | T_RuleChecker | None = None,
types: Union[type[Event], tuple[type[Event], ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = None,
*,
_depth: int = 0,
**kwargs,
@@ -443,7 +441,7 @@ class _Group:
"""其他传递给 `on` 的参数默认值"""
def _get_final_kwargs(
self, update: dict[str, Any], *, exclude: set[str] | None = None
self, update: dict[str, Any], *, exclude: Optional[set[str]] = None
) -> dict[str, Any]:
"""获取最终传递给 `on` 的参数
@@ -477,7 +475,7 @@ class CommandGroup(_Group):
"""
def __init__(
self, cmd: str | tuple[str, ...], prefix_aliases: bool = False, **kwargs
self, cmd: Union[str, tuple[str, ...]], prefix_aliases: bool = False, **kwargs
):
"""命令前缀"""
super().__init__(**kwargs)
@@ -488,7 +486,7 @@ class CommandGroup(_Group):
def __repr__(self) -> str:
return f"CommandGroup(cmd={self.basecmd}, matchers={len(self.matchers)})"
def command(self, cmd: str | tuple[str, ...], **kwargs) -> type[Matcher]:
def command(self, cmd: Union[str, tuple[str, ...]], **kwargs) -> type[Matcher]:
"""注册一个新的命令。新参数将会覆盖命令组默认值
参数:
@@ -515,7 +513,9 @@ class CommandGroup(_Group):
self.matchers.append(matcher)
return matcher
def shell_command(self, cmd: str | tuple[str, ...], **kwargs) -> type[Matcher]:
def shell_command(
self, cmd: Union[str, tuple[str, ...]], **kwargs
) -> type[Matcher]:
"""注册一个新的 `shell_like` 命令。新参数将会覆盖命令组默认值
参数:
@@ -639,7 +639,9 @@ class MatcherGroup(_Group):
self.matchers.append(matcher)
return matcher
def on_startswith(self, msg: str | tuple[str, ...], **kwargs) -> type[Matcher]:
def on_startswith(
self, msg: Union[str, tuple[str, ...]], **kwargs
) -> type[Matcher]:
"""注册一个消息事件响应器,并且当消息的**文本部分**以指定内容开头时响应。
参数:
@@ -659,7 +661,7 @@ class MatcherGroup(_Group):
self.matchers.append(matcher)
return matcher
def on_endswith(self, msg: str | tuple[str, ...], **kwargs) -> type[Matcher]:
def on_endswith(self, msg: Union[str, tuple[str, ...]], **kwargs) -> type[Matcher]:
"""注册一个消息事件响应器,并且当消息的**文本部分**以指定内容结尾时响应。
参数:
@@ -679,7 +681,7 @@ class MatcherGroup(_Group):
self.matchers.append(matcher)
return matcher
def on_fullmatch(self, msg: str | tuple[str, ...], **kwargs) -> type[Matcher]:
def on_fullmatch(self, msg: Union[str, tuple[str, ...]], **kwargs) -> type[Matcher]:
"""注册一个消息事件响应器,并且当消息的**文本部分**与指定内容完全一致时响应。
参数:
@@ -720,9 +722,9 @@ class MatcherGroup(_Group):
def on_command(
self,
cmd: str | tuple[str, ...],
aliases: set[str | tuple[str, ...]] | None = None,
force_whitespace: str | bool | None = None,
cmd: Union[str, tuple[str, ...]],
aliases: Optional[set[Union[str, tuple[str, ...]]]] = None,
force_whitespace: Optional[Union[str, bool]] = None,
**kwargs,
) -> type[Matcher]:
"""注册一个消息事件响应器,并且当消息以指定命令开头时响应。
@@ -751,9 +753,9 @@ class MatcherGroup(_Group):
def on_shell_command(
self,
cmd: str | tuple[str, ...],
aliases: set[str | tuple[str, ...]] | None = None,
parser: ArgumentParser | None = None,
cmd: Union[str, tuple[str, ...]],
aliases: Optional[set[Union[str, tuple[str, ...]]]] = None,
parser: Optional[ArgumentParser] = None,
**kwargs,
) -> type[Matcher]:
"""注册一个支持 `shell_like` 解析参数的命令消息事件响应器。
@@ -782,7 +784,7 @@ class MatcherGroup(_Group):
return matcher
def on_regex(
self, pattern: str, flags: int | re.RegexFlag = 0, **kwargs
self, pattern: str, flags: Union[int, re.RegexFlag] = 0, **kwargs
) -> type[Matcher]:
"""注册一个消息事件响应器,并且当消息匹配正则表达式时响应。
@@ -806,7 +808,7 @@ class MatcherGroup(_Group):
return matcher
def on_type(
self, types: type[Event] | tuple[type[Event]], **kwargs
self, types: Union[type[Event], tuple[type[Event]]], **kwargs
) -> type[Matcher]:
"""注册一个事件响应器,并且当事件为指定类型时响应。
+6 -6
View File
@@ -1,14 +1,14 @@
from datetime import datetime, timedelta
import re
from types import ModuleType
from typing import Any
from types import ModuleType
from datetime import datetime, timedelta
from nonebot.adapters import Event
from nonebot.dependencies import Dependent
from nonebot.matcher import Matcher, MatcherSource
from nonebot.permission import Permission
from nonebot.rule import ArgumentParser, Rule
from nonebot.typing import T_Handler, T_PermissionChecker, T_RuleChecker, T_State
from nonebot.dependencies import Dependent
from nonebot.rule import Rule, ArgumentParser
from nonebot.matcher import Matcher, MatcherSource
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
from .model import Plugin
+1 -1
View File
@@ -1,8 +1,8 @@
from nonebot import on_command
from nonebot.rule import to_me
from nonebot.adapters import Message
from nonebot.params import CommandArg
from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
__plugin_meta__ = PluginMetadata(
name="echo",
+1 -1
View File
@@ -1,9 +1,9 @@
from collections.abc import AsyncGenerator
from nonebot.adapters import Event
from nonebot.message import IgnoredException, event_preprocessor
from nonebot.params import Depends
from nonebot.plugin import PluginMetadata
from nonebot.message import IgnoredException, event_preprocessor
__plugin_meta__ = PluginMetadata(
name="唯一会话",
+77 -86
View File
@@ -5,27 +5,28 @@
只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
FrontMatter:
mdx:
format: md
sidebar_position: 5
description: nonebot.rule 模块
"""
from argparse import Action, ArgumentError
from argparse import ArgumentParser as ArgParser
from argparse import Namespace as Namespace
from collections.abc import Sequence
from contextvars import ContextVar
from gettext import gettext
from itertools import chain, product
import re
import shlex
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,
NamedTuple,
TypedDict,
Union,
TypeVar,
Optional,
TypedDict,
NamedTuple,
cast,
overload,
)
@@ -33,37 +34,37 @@ from typing import (
from pygtrie import CharTrie
from nonebot import get_driver
from nonebot.adapters import Bot, Event, Message, MessageSegment
from nonebot.consts import (
CMD_ARG_KEY,
CMD_KEY,
CMD_START_KEY,
CMD_WHITESPACE_KEY,
ENDSWITH_KEY,
FULLMATCH_KEY,
KEYWORD_KEY,
PREFIX_KEY,
RAW_CMD_KEY,
REGEX_MATCHED,
SHELL_ARGS,
SHELL_ARGV,
STARTSWITH_KEY,
)
from nonebot.log import logger
from nonebot.typing import T_State
from nonebot.exception import ParserExit
from nonebot.internal.rule import Rule as Rule
from nonebot.log import logger
from nonebot.params import Command, CommandArg, CommandWhitespace, EventToMe
from nonebot.typing import T_State
from nonebot.adapters import Bot, Event, Message, MessageSegment
from nonebot.params import Command, EventToMe, CommandArg, CommandWhitespace
from nonebot.consts import (
CMD_KEY,
PREFIX_KEY,
SHELL_ARGS,
SHELL_ARGV,
CMD_ARG_KEY,
KEYWORD_KEY,
RAW_CMD_KEY,
ENDSWITH_KEY,
CMD_START_KEY,
FULLMATCH_KEY,
REGEX_MATCHED,
STARTSWITH_KEY,
CMD_WHITESPACE_KEY,
)
T = TypeVar("T")
class CMD_RESULT(TypedDict):
command: tuple[str, ...] | None
raw_command: str | None
command_arg: Message | None
command_start: str | None
command_whitespace: str | None
command: Optional[tuple[str, ...]]
raw_command: Optional[str]
command_arg: Optional[Message]
command_start: Optional[str]
command_whitespace: Optional[str]
class TRIE_VALUE(NamedTuple):
@@ -143,7 +144,7 @@ class StartswithRule:
ignorecase: 是否忽略大小写
"""
__slots__ = ("ignorecase", "msg")
__slots__ = ("msg", "ignorecase")
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
self.msg = msg
@@ -177,7 +178,7 @@ class StartswithRule:
return False
def startswith(msg: str | tuple[str, ...], ignorecase: bool = False) -> Rule:
def startswith(msg: Union[str, tuple[str, ...]], ignorecase: bool = False) -> Rule:
"""匹配消息纯文本开头。
参数:
@@ -198,7 +199,7 @@ class EndswithRule:
ignorecase: 是否忽略大小写
"""
__slots__ = ("ignorecase", "msg")
__slots__ = ("msg", "ignorecase")
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
self.msg = msg
@@ -232,7 +233,7 @@ class EndswithRule:
return False
def endswith(msg: str | tuple[str, ...], ignorecase: bool = False) -> Rule:
def endswith(msg: Union[str, tuple[str, ...]], ignorecase: bool = False) -> Rule:
"""匹配消息纯文本结尾。
参数:
@@ -253,7 +254,7 @@ class FullmatchRule:
ignorecase: 是否忽略大小写
"""
__slots__ = ("ignorecase", "msg")
__slots__ = ("msg", "ignorecase")
def __init__(self, msg: tuple[str, ...], ignorecase: bool = False):
self.msg = tuple(map(str.casefold, msg) if ignorecase else msg)
@@ -286,7 +287,7 @@ class FullmatchRule:
return False
def fullmatch(msg: str | tuple[str, ...], ignorecase: bool = False) -> Rule:
def fullmatch(msg: Union[str, tuple[str, ...]], ignorecase: bool = False) -> Rule:
"""完全匹配消息。
参数:
@@ -358,7 +359,7 @@ class CommandRule:
def __init__(
self,
cmds: list[tuple[str, ...]],
force_whitespace: str | bool | None = None,
force_whitespace: Optional[Union[str, bool]] = None,
):
self.cmds = tuple(cmds)
self.force_whitespace = force_whitespace
@@ -376,9 +377,9 @@ class CommandRule:
async def __call__(
self,
cmd: tuple[str, ...] | None = Command(),
cmd_arg: Message | None = CommandArg(),
cmd_whitespace: str | None = CommandWhitespace(),
cmd: Optional[tuple[str, ...]] = Command(),
cmd_arg: Optional[Message] = CommandArg(),
cmd_whitespace: Optional[str] = CommandWhitespace(),
) -> bool:
if cmd not in self.cmds:
return False
@@ -390,8 +391,8 @@ class CommandRule:
def command(
*cmds: str | tuple[str, ...],
force_whitespace: str | bool | None = None,
*cmds: Union[str, tuple[str, ...]],
force_whitespace: Optional[Union[str, bool]] = None,
) -> Rule:
"""匹配消息命令。
@@ -454,36 +455,36 @@ class ArgumentParser(ArgParser):
@overload
def parse_known_args(
self,
args: Sequence[str | MessageSegment] | None = None,
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: None = None,
) -> tuple[Namespace, list[str | MessageSegment]]: ...
) -> tuple[Namespace, list[Union[str, MessageSegment]]]: ...
@overload
def parse_known_args(
self, args: Sequence[str | MessageSegment] | None, namespace: T
) -> tuple[T, list[str | MessageSegment]]: ...
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
) -> tuple[T, list[Union[str, MessageSegment]]]: ...
@overload
def parse_known_args(
self, *, namespace: T
) -> tuple[T, list[str | MessageSegment]]: ...
) -> tuple[T, list[Union[str, MessageSegment]]]: ...
def parse_known_args( # pyright: ignore[reportIncompatibleMethodOverride]
self,
args: Sequence[str | MessageSegment] | None = None,
namespace: T | None = None,
) -> tuple[Namespace | T, list[str | MessageSegment]]: ...
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: Optional[T] = None,
) -> tuple[Union[Namespace, T], list[Union[str, MessageSegment]]]: ...
@overload
def parse_args(
self,
args: Sequence[str | MessageSegment] | None = None,
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: None = None,
) -> Namespace: ...
@overload
def parse_args(
self, args: Sequence[str | MessageSegment] | None, namespace: T
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
) -> T: ...
@overload
@@ -491,29 +492,29 @@ class ArgumentParser(ArgParser):
def parse_args(
self,
args: Sequence[str | MessageSegment] | None = None,
namespace: T | None = None,
) -> Namespace | T:
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: Optional[T] = None,
) -> Union[Namespace, T]:
result, argv = self.parse_known_args(args, namespace)
if argv:
msg = gettext("unrecognized arguments: %s")
self.error(msg % " ".join(map(str, argv)))
return cast(Namespace | T, result)
return cast(Union[Namespace, T], result)
def _parse_optional(
self, arg_string: str | MessageSegment
) -> tuple[Action | None, str, str | None] | None:
self, arg_string: Union[str, MessageSegment]
) -> Optional[tuple[Optional[Action], str, Optional[str]]]:
return (
super()._parse_optional(arg_string) if isinstance(arg_string, str) else None
)
def _print_message(self, message: str, file: IO[str] | None = None): # type: ignore
def _print_message(self, message: str, file: Optional[IO[str]] = None):
if (msg := parser_message.get(None)) is not None:
parser_message.set(msg + message)
else:
super()._print_message(message, file)
def exit(self, status: int = 0, message: str | None = None):
def exit(self, status: int = 0, message: Optional[str] = None):
if message:
self._print_message(message)
raise ParserExit(status=status, message=parser_message.get(None))
@@ -529,7 +530,7 @@ class ShellCommandRule:
__slots__ = ("cmds", "parser")
def __init__(self, cmds: list[tuple[str, ...]], parser: ArgumentParser | None):
def __init__(self, cmds: list[tuple[str, ...]], parser: Optional[ArgumentParser]):
self.cmds = tuple(cmds)
self.parser = parser
@@ -549,28 +550,18 @@ class ShellCommandRule:
async def __call__(
self,
state: T_State,
cmd: tuple[str, ...] | None = Command(),
msg: Message | None = CommandArg(),
cmd: Optional[tuple[str, ...]] = Command(),
msg: Optional[Message] = CommandArg(),
) -> bool:
if cmd not in self.cmds or msg is None:
return False
try:
state[SHELL_ARGV] = list(
chain.from_iterable(
shlex.split(str(seg))
if cast(MessageSegment, seg).is_text()
else (seg,)
for seg in msg
)
state[SHELL_ARGV] = list(
chain.from_iterable(
shlex.split(str(seg)) if cast(MessageSegment, seg).is_text() else (seg,)
for seg in msg
)
except Exception as e:
# set SHELL_ARGV to none indicating shlex error
state[SHELL_ARGV] = None
# ensure SHELL_ARGS is set to ParserExit if parser is provided
if self.parser:
state[SHELL_ARGS] = ParserExit(status=2, message=str(e))
return True
)
if self.parser:
t = parser_message.set("")
@@ -587,7 +578,7 @@ class ShellCommandRule:
def shell_command(
*cmds: str | tuple[str, ...], parser: ArgumentParser | None = None
*cmds: Union[str, tuple[str, ...]], parser: Optional[ArgumentParser] = None
) -> Rule:
"""匹配 `shell_like` 形式的消息命令。
@@ -662,7 +653,7 @@ class RegexRule:
flags: 正则表达式标记
"""
__slots__ = ("flags", "regex")
__slots__ = ("regex", "flags")
def __init__(self, regex: str, flags: int = 0):
self.regex = regex
@@ -693,7 +684,7 @@ class RegexRule:
return False
def regex(regex: str, flags: int | re.RegexFlag = 0) -> Rule:
def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
"""匹配符合正则表达式的消息字符串。
可以通过 {ref}`nonebot.params.RegexStr` 获取匹配成功的字符串,
+36 -29
View File
@@ -6,23 +6,22 @@
[`typing`](https://docs.python.org/3/library/typing.html)。
FrontMatter:
mdx:
format: md
sidebar_position: 11
description: nonebot.typing 模块
"""
import sys
import types
import typing as t
from typing import TYPE_CHECKING, TypeAlias, TypeVar, get_args, get_origin
import typing_extensions as t_ext
from typing_extensions import ParamSpec, override
import warnings
import typing as t
import typing_extensions as t_ext
from typing import TYPE_CHECKING, TypeVar
from typing_extensions import ParamSpec, TypeAlias, get_args, override, get_origin
if TYPE_CHECKING:
from asyncio import Task
from nonebot.adapters import Bot
from nonebot.internal.params import DependencyCache
from nonebot.permission import Permission
T = TypeVar("T")
@@ -43,15 +42,31 @@ def overrides(InterfaceClass: object):
return override
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 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
def origin_is_union(origin: type[t.Any] | None) -> bool:
return origin is t.Union or origin is types.UnionType
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[type[t.Any]]) -> bool:
return origin is t.Union or origin is types.UnionType
def origin_is_literal(origin: type[t.Any] | None) -> bool:
def origin_is_literal(origin: t.Optional[type[t.Any]]) -> bool:
"""判断是否是 Literal 类型"""
return origin is t.Literal or origin is t_ext.Literal
@@ -68,12 +83,14 @@ def all_literal_values(type_: type[t.Any]) -> list[t.Any]:
return [x for value in _literal_values(type_) for x in all_literal_values(value)]
def origin_is_annotated(origin: type[t.Any] | None) -> bool:
def origin_is_annotated(origin: t.Optional[type[t.Any]]) -> bool:
"""判断是否是 Annotated 类型"""
return origin is t_ext.Annotated
NONE_TYPES = {None, type(None), t.Literal[None], t_ext.Literal[None], types.NoneType} # noqa: PYI061
NONE_TYPES = {None, type(None), t.Literal[None], t_ext.Literal[None]}
if sys.version_info >= (3, 10):
NONE_TYPES.add(types.NoneType)
def is_none_type(type_: type[t.Any]) -> bool:
@@ -81,18 +98,6 @@ def is_none_type(type_: type[t.Any]) -> bool:
return type_ in NONE_TYPES
if sys.version_info < (3, 12):
def is_type_alias_type(type_: type[t.Any]) -> bool:
"""判断是否是 TypeAliasType 类型"""
return isinstance(type_, t_ext.TypeAliasType)
else:
def is_type_alias_type(type_: type[t.Any]) -> bool:
return isinstance(type_, (t.TypeAliasType, t_ext.TypeAliasType))
def evaluate_forwardref(
ref: t.ForwardRef, globalns: dict[str, t.Any], localns: dict[str, t.Any]
) -> t.Any:
@@ -115,7 +120,9 @@ _STATE_FLAG = StateFlag()
T_State: TypeAlias = t.Annotated[dict[t.Any, t.Any], _STATE_FLAG]
"""事件处理状态 State 类型"""
_DependentCallable: TypeAlias = t.Callable[..., T] | t.Callable[..., t.Awaitable[T]]
_DependentCallable: TypeAlias = t.Union[
t.Callable[..., T], t.Callable[..., t.Awaitable[T]]
]
# driver hooks
T_BotConnectionHook: TypeAlias = _DependentCallable[t.Any]
@@ -143,7 +150,7 @@ T_CallingAPIHook: TypeAlias = t.Callable[
]
"""`bot.call_api` 钩子函数"""
T_CalledAPIHook: TypeAlias = t.Callable[
["Bot", Exception | None, str, 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"""
@@ -249,5 +256,5 @@ T_PermissionUpdater: TypeAlias = _DependentCallable["Permission"]
- MatcherParam: Matcher 对象
- DefaultParam: 带有默认值的参数
"""
T_DependencyCache: TypeAlias = dict[_DependentCallable[t.Any], "DependencyCache"]
T_DependencyCache: TypeAlias = dict[_DependentCallable[t.Any], "Task[t.Any]"]
"""依赖缓存, 用于存储依赖函数的返回值"""
+48 -150
View File
@@ -1,115 +1,42 @@
"""本模块包含了 NoneBot 的一些工具函数
FrontMatter:
mdx:
format: md
sidebar_position: 8
description: nonebot.utils 模块
"""
from collections import deque
from collections.abc import (
AsyncGenerator,
Callable,
Coroutine,
Generator,
Mapping,
Sequence,
)
import contextlib
from contextlib import AbstractContextManager, asynccontextmanager
import dataclasses
from enum import Enum
from functools import partial, wraps
import importlib
import inspect
import json
from pathlib import Path
import re
from typing import (
Any,
Final,
Generic,
Literal,
TypeAlias,
TypeVar,
final,
get_args,
get_origin,
overload,
)
from typing_extensions import ParamSpec, override
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 AbstractContextManager, asynccontextmanager
from typing_extensions import ParamSpec, get_args, override, get_origin
from collections.abc import Mapping, Sequence, Coroutine, AsyncGenerator
from typing import Any, Union, Generic, TypeVar, Callable, Optional, overload
import anyio
import anyio.to_thread
from exceptiongroup import BaseExceptionGroup, catch
from pydantic import BaseModel
from nonebot.log import logger
from nonebot.typing import (
all_literal_values,
is_none_type,
origin_is_literal,
origin_is_union,
type_has_args,
origin_is_union,
origin_is_literal,
all_literal_values,
)
from .compat import custom_validation
P = ParamSpec("P")
R = TypeVar("R")
T = TypeVar("T")
K = TypeVar("K")
V = TypeVar("V")
E = TypeVar("E", bound=BaseException)
@final
@custom_validation
class Unset(Enum):
_UNSET = "<UNSET>"
def __repr__(self) -> str:
return "<UNSET>"
def __str__(self) -> str:
return self.__repr__()
def __bool__(self) -> Literal[False]:
return False
def __copy__(self):
return self._UNSET
def __deepcopy__(self, memo: dict[int, Any]):
return self._UNSET
@classmethod
def __get_validators__(cls):
yield cls._validate
@classmethod
def _validate(cls, value: Any):
if value is not cls._UNSET:
raise ValueError(f"{value!r} is not UNSET")
return value
UnsetType: TypeAlias = Literal[Unset._UNSET]
UNSET: Final[UnsetType] = Unset._UNSET
def exclude_unset(data: Any) -> Any:
if isinstance(data, dict):
return data.__class__(
(k, exclude_unset(v)) for k, v in data.items() if v is not UNSET
)
elif isinstance(data, list):
return data.__class__(exclude_unset(i) for i in data if i is not UNSET)
elif data is UNSET:
return None
return data
def escape_tag(s: str) -> str:
@@ -142,7 +69,7 @@ def deep_update(
def lenient_issubclass(
cls: Any, class_or_tuple: type[Any] | tuple[type[Any], ...]
cls: Any, class_or_tuple: Union[type[Any], tuple[type[Any], ...]]
) -> bool:
"""检查 cls 是否是 class_or_tuple 中的一个类型子类并忽略类型错误。"""
try:
@@ -152,34 +79,21 @@ def lenient_issubclass(
def generic_check_issubclass(
cls: Any, class_or_tuple: type[Any] | tuple[type[Any], ...]
cls: Any, class_or_tuple: Union[type[Any], tuple[type[Any], ...]]
) -> bool:
"""检查 cls 是否是 class_or_tuple 中的一个类型子类。
特别的:
- 如果 cls 是 `typing.TypeVar` 类型,
则会检查其 `__bound__` 或 `__constraints__`
是否是 class_or_tuple 中一个类型的子类或 None。
- 如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
则会检查其中的所有类型是否是 class_or_tuple 中一个类型的子类或 None。
- 如果 cls 是 `typing.Literal` 类型,
则会检查其中的所有值是否是 class_or_tuple 中一个类型的实例。
- 如果 cls 是 `typing.List`、`typing.Dict` 等泛型类型,
则会检查其原始类型是否是 class_or_tuple 中一个类型的子类。
- 如果 cls 是 `typing.TypeVar` 类型,
则会检查其 `__bound__` 或 `__constraints__`
是否是 class_or_tuple 中一个类型的子类或 None。
"""
# if the target is a TypeVar, we check it first
if 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 the target is not a generic type, we check it directly
elif not type_has_args(cls):
if not type_has_args(cls):
with contextlib.suppress(TypeError):
return issubclass(cls, class_or_tuple)
@@ -201,6 +115,14 @@ def generic_check_issubclass(
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
@@ -210,7 +132,7 @@ def type_is_complex(type_: type[Any]) -> bool:
return _type_is_complex_inner(type_) or _type_is_complex_inner(origin)
def _type_is_complex_inner(type_: type[Any] | None) -> bool:
def _type_is_complex_inner(type_: Optional[type[Any]]) -> bool:
if lenient_issubclass(type_, (str, bytes)):
return False
@@ -254,9 +176,11 @@ def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]:
@wraps(call)
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
return await anyio.to_thread.run_sync(
partial(call, *args, **kwargs), abandon_on_cancel=True
)
loop = asyncio.get_running_loop()
pfunc = partial(call, *args, **kwargs)
context = copy_context()
result = await loop.run_in_executor(None, partial(context.run, pfunc))
return result
return _wrapper
@@ -281,7 +205,7 @@ async def run_coro_with_catch(
coro: Coroutine[Any, Any, T],
exc: tuple[type[Exception], ...],
return_on_err: None = None,
) -> T | None: ...
) -> Union[T, None]: ...
@overload
@@ -289,14 +213,14 @@ async def run_coro_with_catch(
coro: Coroutine[Any, Any, T],
exc: tuple[type[Exception], ...],
return_on_err: R,
) -> T | R: ...
) -> Union[T, R]: ...
async def run_coro_with_catch(
coro: Coroutine[Any, Any, T],
exc: tuple[type[Exception], ...],
return_on_err: R | None = None,
) -> T | R | None:
return_on_err: Optional[R] = None,
) -> Optional[Union[T, R]]:
"""运行协程并当遇到指定异常时返回指定值。
参数:
@@ -308,36 +232,10 @@ async def run_coro_with_catch(
协程的返回值或发生异常时的指定值
"""
with catch({exc: lambda exc_group: None}):
try:
return await coro
return return_on_err
async def run_coro_with_shield(coro: Coroutine[Any, Any, T]) -> T:
"""运行协程并在取消时屏蔽取消异常。
参数:
coro: 要运行的协程
返回:
协程的返回值
"""
with anyio.CancelScope(shield=True):
return await coro
raise RuntimeError("This should not happen")
def flatten_exception_group(
exc_group: BaseExceptionGroup[E],
) -> Generator[E, None, None]:
for exc in exc_group.exceptions:
if isinstance(exc, BaseExceptionGroup):
yield from flatten_exception_group(exc)
else:
yield exc
except exc:
return return_on_err
def get_name(obj: Any) -> str:
@@ -353,11 +251,11 @@ def path_to_module_name(path: Path) -> str:
if rel_path.stem == "__init__":
return ".".join(rel_path.parts[:-1])
else:
return ".".join((*rel_path.parts[:-1], rel_path.stem))
return ".".join(rel_path.parts[:-1] + (rel_path.stem,))
def resolve_dot_notation(
obj_str: str, default_attr: str, default_prefix: str | None = None
obj_str: str, default_attr: str, default_prefix: Optional[str] = None
) -> Any:
"""解析并导入点分表示法的对象"""
modulename, _, cls = obj_str.partition(":")
@@ -378,7 +276,7 @@ class classproperty(Generic[T]):
def __init__(self, func: Callable[[Any], T]) -> None:
self.func = func
def __get__(self, instance: Any, owner: type[Any] | None = None) -> T:
def __get__(self, instance: Any, owner: Optional[type[Any]] = None) -> T:
return self.func(type(instance) if owner is None else owner)
@@ -408,7 +306,7 @@ def logger_wrapper(logger_name: str):
- exception: 异常信息
"""
def log(level: str, message: str, exception: Exception | None = None):
def log(level: str, message: str, exception: Optional[Exception] = None):
logger.opt(colors=True, exception=exception).log(
level, f"<m>{escape_tag(logger_name)}</m> | {message}"
)
+8 -8
View File
@@ -1,7 +1,6 @@
{
"name": "root",
"private": true,
"packageManager": "yarn@1.22.22",
"workspaces": [
"website"
],
@@ -21,19 +20,20 @@
"pyright": "pyright"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@typescript-eslint/eslint-plugin": "^6.6.0",
"@typescript-eslint/parser": "^6.6.0",
"cross-env": "^7.0.3",
"eslint": "^8.48.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-regexp": "^1.15.0",
"prettier": "^3.0.3",
"pyright": "1.1.393",
"pyright": "^1.1.317",
"stylelint": "^15.10.3",
"stylelint-config-standard": "^34.0.0",
"stylelint-prettier": "^4.0.2"
@@ -31,7 +31,8 @@ def init():
if host in {"0.0.0.0", "127.0.0.1"}:
host = "localhost"
logger.opt(colors=True).info(
f"Nonebot docs will be running at: <b><u>http://{host}:{port}/website/</u></b>"
f"Nonebot docs will be running at: "
f"<b><u>http://{host}:{port}/website/</u></b>"
)
+12 -14
View File
@@ -1,24 +1,22 @@
[project]
[tool.poetry]
name = "nonebot-plugin-docs"
version = "2.0.0"
description = "View NoneBot2 Docs Locally"
authors = [{ name = "yanyongyu", email = "yyy@nonebot.dev" }]
readme = "README.md"
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
readme = "README.md"
homepage = "https://github.com/nonebot/nonebot2/blob/master/packages/nonebot-plugin-docs"
repository = "https://github.com/nonebot/nonebot2"
keywords = ["nonebot", "nonebot2", "docs"]
requires-python = ">=3.9, <4.0"
dependencies = ["nonebot2 >=2.0.0, <3.0.0"]
include = ["nonebot_plugin_docs/dist/**/*"]
[project.urls]
Homepage = "https://github.com/nonebot/nonebot2/blob/master/packages/nonebot-plugin-docs"
Repository = "https://github.com/nonebot/nonebot2"
[dependency-groups]
dev = []
[tool.poetry.dependencies]
python = "^3.9"
nonebot2 = "^2.0.0"
[tool.uv.build-backend]
module-root = ""
[tool.poetry.dev-dependencies]
[build-system]
requires = ["uv_build >=0.8.3, <0.9.0"]
build-backend = "uv_build"
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
-665
View File
@@ -1,665 +0,0 @@
version = 1
revision = 2
requires-python = ">=3.9, <4"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "anyio"
version = "4.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "idna" },
{ name = "sniffio" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "exceptiongroup"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
]
[[package]]
name = "loguru"
version = "0.7.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "win32-setctime", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
]
[[package]]
name = "multidict"
version = "6.6.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006, upload-time = "2025-06-30T15:53:46.929Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/67/414933982bce2efce7cbcb3169eaaf901e0f25baec69432b4874dfb1f297/multidict-6.6.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a2be5b7b35271f7fff1397204ba6708365e3d773579fe2a30625e16c4b4ce817", size = 77017, upload-time = "2025-06-30T15:50:58.931Z" },
{ url = "https://files.pythonhosted.org/packages/8a/fe/d8a3ee1fad37dc2ef4f75488b0d9d4f25bf204aad8306cbab63d97bff64a/multidict-6.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12f4581d2930840295c461764b9a65732ec01250b46c6b2c510d7ee68872b140", size = 44897, upload-time = "2025-06-30T15:51:00.999Z" },
{ url = "https://files.pythonhosted.org/packages/1f/e0/265d89af8c98240265d82b8cbcf35897f83b76cd59ee3ab3879050fd8c45/multidict-6.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd7793bab517e706c9ed9d7310b06c8672fd0aeee5781bfad612f56b8e0f7d14", size = 44574, upload-time = "2025-06-30T15:51:02.449Z" },
{ url = "https://files.pythonhosted.org/packages/e6/05/6b759379f7e8e04ccc97cfb2a5dcc5cdbd44a97f072b2272dc51281e6a40/multidict-6.6.3-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:72d8815f2cd3cf3df0f83cac3f3ef801d908b2d90409ae28102e0553af85545a", size = 225729, upload-time = "2025-06-30T15:51:03.794Z" },
{ url = "https://files.pythonhosted.org/packages/4e/f5/8d5a15488edd9a91fa4aad97228d785df208ed6298580883aa3d9def1959/multidict-6.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:531e331a2ee53543ab32b16334e2deb26f4e6b9b28e41f8e0c87e99a6c8e2d69", size = 242515, upload-time = "2025-06-30T15:51:05.002Z" },
{ url = "https://files.pythonhosted.org/packages/6e/b5/a8f317d47d0ac5bb746d6d8325885c8967c2a8ce0bb57be5399e3642cccb/multidict-6.6.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:42ca5aa9329a63be8dc49040f63817d1ac980e02eeddba763a9ae5b4027b9c9c", size = 222224, upload-time = "2025-06-30T15:51:06.148Z" },
{ url = "https://files.pythonhosted.org/packages/76/88/18b2a0d5e80515fa22716556061189c2853ecf2aa2133081ebbe85ebea38/multidict-6.6.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:208b9b9757060b9faa6f11ab4bc52846e4f3c2fb8b14d5680c8aac80af3dc751", size = 253124, upload-time = "2025-06-30T15:51:07.375Z" },
{ url = "https://files.pythonhosted.org/packages/62/bf/ebfcfd6b55a1b05ef16d0775ae34c0fe15e8dab570d69ca9941073b969e7/multidict-6.6.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:acf6b97bd0884891af6a8b43d0f586ab2fcf8e717cbd47ab4bdddc09e20652d8", size = 251529, upload-time = "2025-06-30T15:51:08.691Z" },
{ url = "https://files.pythonhosted.org/packages/44/11/780615a98fd3775fc309d0234d563941af69ade2df0bb82c91dda6ddaea1/multidict-6.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68e9e12ed00e2089725669bdc88602b0b6f8d23c0c95e52b95f0bc69f7fe9b55", size = 241627, upload-time = "2025-06-30T15:51:10.605Z" },
{ url = "https://files.pythonhosted.org/packages/28/3d/35f33045e21034b388686213752cabc3a1b9d03e20969e6fa8f1b1d82db1/multidict-6.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05db2f66c9addb10cfa226e1acb363450fab2ff8a6df73c622fefe2f5af6d4e7", size = 239351, upload-time = "2025-06-30T15:51:12.18Z" },
{ url = "https://files.pythonhosted.org/packages/6e/cc/ff84c03b95b430015d2166d9aae775a3985d757b94f6635010d0038d9241/multidict-6.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0db58da8eafb514db832a1b44f8fa7906fdd102f7d982025f816a93ba45e3dcb", size = 233429, upload-time = "2025-06-30T15:51:13.533Z" },
{ url = "https://files.pythonhosted.org/packages/2e/f0/8cd49a0b37bdea673a4b793c2093f2f4ba8e7c9d6d7c9bd672fd6d38cd11/multidict-6.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:14117a41c8fdb3ee19c743b1c027da0736fdb79584d61a766da53d399b71176c", size = 243094, upload-time = "2025-06-30T15:51:14.815Z" },
{ url = "https://files.pythonhosted.org/packages/96/19/5d9a0cfdafe65d82b616a45ae950975820289069f885328e8185e64283c2/multidict-6.6.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:877443eaaabcd0b74ff32ebeed6f6176c71850feb7d6a1d2db65945256ea535c", size = 248957, upload-time = "2025-06-30T15:51:16.076Z" },
{ url = "https://files.pythonhosted.org/packages/e6/dc/c90066151da87d1e489f147b9b4327927241e65f1876702fafec6729c014/multidict-6.6.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:70b72e749a4f6e7ed8fb334fa8d8496384840319512746a5f42fa0aec79f4d61", size = 243590, upload-time = "2025-06-30T15:51:17.413Z" },
{ url = "https://files.pythonhosted.org/packages/ec/39/458afb0cccbb0ee9164365273be3e039efddcfcb94ef35924b7dbdb05db0/multidict-6.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43571f785b86afd02b3855c5ac8e86ec921b760298d6f82ff2a61daf5a35330b", size = 237487, upload-time = "2025-06-30T15:51:19.039Z" },
{ url = "https://files.pythonhosted.org/packages/35/38/0016adac3990426610a081787011177e661875546b434f50a26319dc8372/multidict-6.6.3-cp310-cp310-win32.whl", hash = "sha256:20c5a0c3c13a15fd5ea86c42311859f970070e4e24de5a550e99d7c271d76318", size = 41390, upload-time = "2025-06-30T15:51:20.362Z" },
{ url = "https://files.pythonhosted.org/packages/f3/d2/17897a8f3f2c5363d969b4c635aa40375fe1f09168dc09a7826780bfb2a4/multidict-6.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab0a34a007704c625e25a9116c6770b4d3617a071c8a7c30cd338dfbadfe6485", size = 45954, upload-time = "2025-06-30T15:51:21.383Z" },
{ url = "https://files.pythonhosted.org/packages/2d/5f/d4a717c1e457fe44072e33fa400d2b93eb0f2819c4d669381f925b7cba1f/multidict-6.6.3-cp310-cp310-win_arm64.whl", hash = "sha256:769841d70ca8bdd140a715746199fc6473414bd02efd678d75681d2d6a8986c5", size = 42981, upload-time = "2025-06-30T15:51:22.809Z" },
{ url = "https://files.pythonhosted.org/packages/08/f0/1a39863ced51f639c81a5463fbfa9eb4df59c20d1a8769ab9ef4ca57ae04/multidict-6.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18f4eba0cbac3546b8ae31e0bbc55b02c801ae3cbaf80c247fcdd89b456ff58c", size = 76445, upload-time = "2025-06-30T15:51:24.01Z" },
{ url = "https://files.pythonhosted.org/packages/c9/0e/a7cfa451c7b0365cd844e90b41e21fab32edaa1e42fc0c9f68461ce44ed7/multidict-6.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef43b5dd842382329e4797c46f10748d8c2b6e0614f46b4afe4aee9ac33159df", size = 44610, upload-time = "2025-06-30T15:51:25.158Z" },
{ url = "https://files.pythonhosted.org/packages/c6/bb/a14a4efc5ee748cc1904b0748be278c31b9295ce5f4d2ef66526f410b94d/multidict-6.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bd1fd5eec01494e0f2e8e446a74a85d5e49afb63d75a9934e4a5423dba21d", size = 44267, upload-time = "2025-06-30T15:51:26.326Z" },
{ url = "https://files.pythonhosted.org/packages/c2/f8/410677d563c2d55e063ef74fe578f9d53fe6b0a51649597a5861f83ffa15/multidict-6.6.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5bd8d6f793a787153956cd35e24f60485bf0651c238e207b9a54f7458b16d539", size = 230004, upload-time = "2025-06-30T15:51:27.491Z" },
{ url = "https://files.pythonhosted.org/packages/fd/df/2b787f80059314a98e1ec6a4cc7576244986df3e56b3c755e6fc7c99e038/multidict-6.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf99b4daf908c73856bd87ee0a2499c3c9a3d19bb04b9c6025e66af3fd07462", size = 247196, upload-time = "2025-06-30T15:51:28.762Z" },
{ url = "https://files.pythonhosted.org/packages/05/f2/f9117089151b9a8ab39f9019620d10d9718eec2ac89e7ca9d30f3ec78e96/multidict-6.6.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b9e59946b49dafaf990fd9c17ceafa62976e8471a14952163d10a7a630413a9", size = 225337, upload-time = "2025-06-30T15:51:30.025Z" },
{ url = "https://files.pythonhosted.org/packages/93/2d/7115300ec5b699faa152c56799b089a53ed69e399c3c2d528251f0aeda1a/multidict-6.6.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e2db616467070d0533832d204c54eea6836a5e628f2cb1e6dfd8cd6ba7277cb7", size = 257079, upload-time = "2025-06-30T15:51:31.716Z" },
{ url = "https://files.pythonhosted.org/packages/15/ea/ff4bab367623e39c20d3b07637225c7688d79e4f3cc1f3b9f89867677f9a/multidict-6.6.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7394888236621f61dcdd25189b2768ae5cc280f041029a5bcf1122ac63df79f9", size = 255461, upload-time = "2025-06-30T15:51:33.029Z" },
{ url = "https://files.pythonhosted.org/packages/74/07/2c9246cda322dfe08be85f1b8739646f2c4c5113a1422d7a407763422ec4/multidict-6.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f114d8478733ca7388e7c7e0ab34b72547476b97009d643644ac33d4d3fe1821", size = 246611, upload-time = "2025-06-30T15:51:34.47Z" },
{ url = "https://files.pythonhosted.org/packages/a8/62/279c13d584207d5697a752a66ffc9bb19355a95f7659140cb1b3cf82180e/multidict-6.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdf22e4db76d323bcdc733514bf732e9fb349707c98d341d40ebcc6e9318ef3d", size = 243102, upload-time = "2025-06-30T15:51:36.525Z" },
{ url = "https://files.pythonhosted.org/packages/69/cc/e06636f48c6d51e724a8bc8d9e1db5f136fe1df066d7cafe37ef4000f86a/multidict-6.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e995a34c3d44ab511bfc11aa26869b9d66c2d8c799fa0e74b28a473a692532d6", size = 238693, upload-time = "2025-06-30T15:51:38.278Z" },
{ url = "https://files.pythonhosted.org/packages/89/a4/66c9d8fb9acf3b226cdd468ed009537ac65b520aebdc1703dd6908b19d33/multidict-6.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:766a4a5996f54361d8d5a9050140aa5362fe48ce51c755a50c0bc3706460c430", size = 246582, upload-time = "2025-06-30T15:51:39.709Z" },
{ url = "https://files.pythonhosted.org/packages/cf/01/c69e0317be556e46257826d5449feb4e6aa0d18573e567a48a2c14156f1f/multidict-6.6.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3893a0d7d28a7fe6ca7a1f760593bc13038d1d35daf52199d431b61d2660602b", size = 253355, upload-time = "2025-06-30T15:51:41.013Z" },
{ url = "https://files.pythonhosted.org/packages/c0/da/9cc1da0299762d20e626fe0042e71b5694f9f72d7d3f9678397cbaa71b2b/multidict-6.6.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:934796c81ea996e61914ba58064920d6cad5d99140ac3167901eb932150e2e56", size = 247774, upload-time = "2025-06-30T15:51:42.291Z" },
{ url = "https://files.pythonhosted.org/packages/e6/91/b22756afec99cc31105ddd4a52f95ab32b1a4a58f4d417979c570c4a922e/multidict-6.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ed948328aec2072bc00f05d961ceadfd3e9bfc2966c1319aeaf7b7c21219183", size = 242275, upload-time = "2025-06-30T15:51:43.642Z" },
{ url = "https://files.pythonhosted.org/packages/be/f1/adcc185b878036a20399d5be5228f3cbe7f823d78985d101d425af35c800/multidict-6.6.3-cp311-cp311-win32.whl", hash = "sha256:9f5b28c074c76afc3e4c610c488e3493976fe0e596dd3db6c8ddfbb0134dcac5", size = 41290, upload-time = "2025-06-30T15:51:45.264Z" },
{ url = "https://files.pythonhosted.org/packages/e0/d4/27652c1c6526ea6b4f5ddd397e93f4232ff5de42bea71d339bc6a6cc497f/multidict-6.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc7f6fbc61b1c16050a389c630da0b32fc6d4a3d191394ab78972bf5edc568c2", size = 45942, upload-time = "2025-06-30T15:51:46.377Z" },
{ url = "https://files.pythonhosted.org/packages/16/18/23f4932019804e56d3c2413e237f866444b774b0263bcb81df2fdecaf593/multidict-6.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:d4e47d8faffaae822fb5cba20937c048d4f734f43572e7079298a6c39fb172cb", size = 42880, upload-time = "2025-06-30T15:51:47.561Z" },
{ url = "https://files.pythonhosted.org/packages/0e/a0/6b57988ea102da0623ea814160ed78d45a2645e4bbb499c2896d12833a70/multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6", size = 76514, upload-time = "2025-06-30T15:51:48.728Z" },
{ url = "https://files.pythonhosted.org/packages/07/7a/d1e92665b0850c6c0508f101f9cf0410c1afa24973e1115fe9c6a185ebf7/multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f", size = 45394, upload-time = "2025-06-30T15:51:49.986Z" },
{ url = "https://files.pythonhosted.org/packages/52/6f/dd104490e01be6ef8bf9573705d8572f8c2d2c561f06e3826b081d9e6591/multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55", size = 43590, upload-time = "2025-06-30T15:51:51.331Z" },
{ url = "https://files.pythonhosted.org/packages/44/fe/06e0e01b1b0611e6581b7fd5a85b43dacc08b6cea3034f902f383b0873e5/multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b", size = 237292, upload-time = "2025-06-30T15:51:52.584Z" },
{ url = "https://files.pythonhosted.org/packages/ce/71/4f0e558fb77696b89c233c1ee2d92f3e1d5459070a0e89153c9e9e804186/multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888", size = 258385, upload-time = "2025-06-30T15:51:53.913Z" },
{ url = "https://files.pythonhosted.org/packages/e3/25/cca0e68228addad24903801ed1ab42e21307a1b4b6dd2cf63da5d3ae082a/multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d", size = 242328, upload-time = "2025-06-30T15:51:55.672Z" },
{ url = "https://files.pythonhosted.org/packages/6e/a3/46f2d420d86bbcb8fe660b26a10a219871a0fbf4d43cb846a4031533f3e0/multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680", size = 268057, upload-time = "2025-06-30T15:51:57.037Z" },
{ url = "https://files.pythonhosted.org/packages/9e/73/1c743542fe00794a2ec7466abd3f312ccb8fad8dff9f36d42e18fb1ec33e/multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a", size = 269341, upload-time = "2025-06-30T15:51:59.111Z" },
{ url = "https://files.pythonhosted.org/packages/a4/11/6ec9dcbe2264b92778eeb85407d1df18812248bf3506a5a1754bc035db0c/multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961", size = 256081, upload-time = "2025-06-30T15:52:00.533Z" },
{ url = "https://files.pythonhosted.org/packages/9b/2b/631b1e2afeb5f1696846d747d36cda075bfdc0bc7245d6ba5c319278d6c4/multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65", size = 253581, upload-time = "2025-06-30T15:52:02.43Z" },
{ url = "https://files.pythonhosted.org/packages/bf/0e/7e3b93f79efeb6111d3bf9a1a69e555ba1d07ad1c11bceb56b7310d0d7ee/multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643", size = 250750, upload-time = "2025-06-30T15:52:04.26Z" },
{ url = "https://files.pythonhosted.org/packages/ad/9e/086846c1d6601948e7de556ee464a2d4c85e33883e749f46b9547d7b0704/multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063", size = 251548, upload-time = "2025-06-30T15:52:06.002Z" },
{ url = "https://files.pythonhosted.org/packages/8c/7b/86ec260118e522f1a31550e87b23542294880c97cfbf6fb18cc67b044c66/multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3", size = 262718, upload-time = "2025-06-30T15:52:07.707Z" },
{ url = "https://files.pythonhosted.org/packages/8c/bd/22ce8f47abb0be04692c9fc4638508b8340987b18691aa7775d927b73f72/multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75", size = 259603, upload-time = "2025-06-30T15:52:09.58Z" },
{ url = "https://files.pythonhosted.org/packages/07/9c/91b7ac1691be95cd1f4a26e36a74b97cda6aa9820632d31aab4410f46ebd/multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10", size = 251351, upload-time = "2025-06-30T15:52:10.947Z" },
{ url = "https://files.pythonhosted.org/packages/6f/5c/4d7adc739884f7a9fbe00d1eac8c034023ef8bad71f2ebe12823ca2e3649/multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5", size = 41860, upload-time = "2025-06-30T15:52:12.334Z" },
{ url = "https://files.pythonhosted.org/packages/6a/a3/0fbc7afdf7cb1aa12a086b02959307848eb6bcc8f66fcb66c0cb57e2a2c1/multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17", size = 45982, upload-time = "2025-06-30T15:52:13.6Z" },
{ url = "https://files.pythonhosted.org/packages/b8/95/8c825bd70ff9b02462dc18d1295dd08d3e9e4eb66856d292ffa62cfe1920/multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b", size = 43210, upload-time = "2025-06-30T15:52:14.893Z" },
{ url = "https://files.pythonhosted.org/packages/52/1d/0bebcbbb4f000751fbd09957257903d6e002943fc668d841a4cf2fb7f872/multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55", size = 75843, upload-time = "2025-06-30T15:52:16.155Z" },
{ url = "https://files.pythonhosted.org/packages/07/8f/cbe241b0434cfe257f65c2b1bcf9e8d5fb52bc708c5061fb29b0fed22bdf/multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b", size = 45053, upload-time = "2025-06-30T15:52:17.429Z" },
{ url = "https://files.pythonhosted.org/packages/32/d2/0b3b23f9dbad5b270b22a3ac3ea73ed0a50ef2d9a390447061178ed6bdb8/multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65", size = 43273, upload-time = "2025-06-30T15:52:19.346Z" },
{ url = "https://files.pythonhosted.org/packages/fd/fe/6eb68927e823999e3683bc49678eb20374ba9615097d085298fd5b386564/multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3", size = 237124, upload-time = "2025-06-30T15:52:20.773Z" },
{ url = "https://files.pythonhosted.org/packages/e7/ab/320d8507e7726c460cb77117848b3834ea0d59e769f36fdae495f7669929/multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c", size = 256892, upload-time = "2025-06-30T15:52:22.242Z" },
{ url = "https://files.pythonhosted.org/packages/76/60/38ee422db515ac69834e60142a1a69111ac96026e76e8e9aa347fd2e4591/multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6", size = 240547, upload-time = "2025-06-30T15:52:23.736Z" },
{ url = "https://files.pythonhosted.org/packages/27/fb/905224fde2dff042b030c27ad95a7ae744325cf54b890b443d30a789b80e/multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8", size = 266223, upload-time = "2025-06-30T15:52:25.185Z" },
{ url = "https://files.pythonhosted.org/packages/76/35/dc38ab361051beae08d1a53965e3e1a418752fc5be4d3fb983c5582d8784/multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca", size = 267262, upload-time = "2025-06-30T15:52:26.969Z" },
{ url = "https://files.pythonhosted.org/packages/1f/a3/0a485b7f36e422421b17e2bbb5a81c1af10eac1d4476f2ff92927c730479/multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884", size = 254345, upload-time = "2025-06-30T15:52:28.467Z" },
{ url = "https://files.pythonhosted.org/packages/b4/59/bcdd52c1dab7c0e0d75ff19cac751fbd5f850d1fc39172ce809a74aa9ea4/multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7", size = 252248, upload-time = "2025-06-30T15:52:29.938Z" },
{ url = "https://files.pythonhosted.org/packages/bb/a4/2d96aaa6eae8067ce108d4acee6f45ced5728beda55c0f02ae1072c730d1/multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b", size = 250115, upload-time = "2025-06-30T15:52:31.416Z" },
{ url = "https://files.pythonhosted.org/packages/25/d2/ed9f847fa5c7d0677d4f02ea2c163d5e48573de3f57bacf5670e43a5ffaa/multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c", size = 249649, upload-time = "2025-06-30T15:52:32.996Z" },
{ url = "https://files.pythonhosted.org/packages/1f/af/9155850372563fc550803d3f25373308aa70f59b52cff25854086ecb4a79/multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b", size = 261203, upload-time = "2025-06-30T15:52:34.521Z" },
{ url = "https://files.pythonhosted.org/packages/36/2f/c6a728f699896252cf309769089568a33c6439626648843f78743660709d/multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1", size = 258051, upload-time = "2025-06-30T15:52:35.999Z" },
{ url = "https://files.pythonhosted.org/packages/d0/60/689880776d6b18fa2b70f6cc74ff87dd6c6b9b47bd9cf74c16fecfaa6ad9/multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6", size = 249601, upload-time = "2025-06-30T15:52:37.473Z" },
{ url = "https://files.pythonhosted.org/packages/75/5e/325b11f2222a549019cf2ef879c1f81f94a0d40ace3ef55cf529915ba6cc/multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e", size = 41683, upload-time = "2025-06-30T15:52:38.927Z" },
{ url = "https://files.pythonhosted.org/packages/b1/ad/cf46e73f5d6e3c775cabd2a05976547f3f18b39bee06260369a42501f053/multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9", size = 45811, upload-time = "2025-06-30T15:52:40.207Z" },
{ url = "https://files.pythonhosted.org/packages/c5/c9/2e3fe950db28fb7c62e1a5f46e1e38759b072e2089209bc033c2798bb5ec/multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600", size = 43056, upload-time = "2025-06-30T15:52:41.575Z" },
{ url = "https://files.pythonhosted.org/packages/3a/58/aaf8114cf34966e084a8cc9517771288adb53465188843d5a19862cb6dc3/multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134", size = 82811, upload-time = "2025-06-30T15:52:43.281Z" },
{ url = "https://files.pythonhosted.org/packages/71/af/5402e7b58a1f5b987a07ad98f2501fdba2a4f4b4c30cf114e3ce8db64c87/multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37", size = 48304, upload-time = "2025-06-30T15:52:45.026Z" },
{ url = "https://files.pythonhosted.org/packages/39/65/ab3c8cafe21adb45b24a50266fd747147dec7847425bc2a0f6934b3ae9ce/multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8", size = 46775, upload-time = "2025-06-30T15:52:46.459Z" },
{ url = "https://files.pythonhosted.org/packages/49/ba/9fcc1b332f67cc0c0c8079e263bfab6660f87fe4e28a35921771ff3eea0d/multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1", size = 229773, upload-time = "2025-06-30T15:52:47.88Z" },
{ url = "https://files.pythonhosted.org/packages/a4/14/0145a251f555f7c754ce2dcbcd012939bbd1f34f066fa5d28a50e722a054/multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373", size = 250083, upload-time = "2025-06-30T15:52:49.366Z" },
{ url = "https://files.pythonhosted.org/packages/9e/d4/d5c0bd2bbb173b586c249a151a26d2fb3ec7d53c96e42091c9fef4e1f10c/multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e", size = 228980, upload-time = "2025-06-30T15:52:50.903Z" },
{ url = "https://files.pythonhosted.org/packages/21/32/c9a2d8444a50ec48c4733ccc67254100c10e1c8ae8e40c7a2d2183b59b97/multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f", size = 257776, upload-time = "2025-06-30T15:52:52.764Z" },
{ url = "https://files.pythonhosted.org/packages/68/d0/14fa1699f4ef629eae08ad6201c6b476098f5efb051b296f4c26be7a9fdf/multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0", size = 256882, upload-time = "2025-06-30T15:52:54.596Z" },
{ url = "https://files.pythonhosted.org/packages/da/88/84a27570fbe303c65607d517a5f147cd2fc046c2d1da02b84b17b9bdc2aa/multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc", size = 247816, upload-time = "2025-06-30T15:52:56.175Z" },
{ url = "https://files.pythonhosted.org/packages/1c/60/dca352a0c999ce96a5d8b8ee0b2b9f729dcad2e0b0c195f8286269a2074c/multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f", size = 245341, upload-time = "2025-06-30T15:52:57.752Z" },
{ url = "https://files.pythonhosted.org/packages/50/ef/433fa3ed06028f03946f3993223dada70fb700f763f70c00079533c34578/multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471", size = 235854, upload-time = "2025-06-30T15:52:59.74Z" },
{ url = "https://files.pythonhosted.org/packages/1b/1f/487612ab56fbe35715320905215a57fede20de7db40a261759690dc80471/multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2", size = 243432, upload-time = "2025-06-30T15:53:01.602Z" },
{ url = "https://files.pythonhosted.org/packages/da/6f/ce8b79de16cd885c6f9052c96a3671373d00c59b3ee635ea93e6e81b8ccf/multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648", size = 252731, upload-time = "2025-06-30T15:53:03.517Z" },
{ url = "https://files.pythonhosted.org/packages/bb/fe/a2514a6aba78e5abefa1624ca85ae18f542d95ac5cde2e3815a9fbf369aa/multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d", size = 247086, upload-time = "2025-06-30T15:53:05.48Z" },
{ url = "https://files.pythonhosted.org/packages/8c/22/b788718d63bb3cce752d107a57c85fcd1a212c6c778628567c9713f9345a/multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c", size = 243338, upload-time = "2025-06-30T15:53:07.522Z" },
{ url = "https://files.pythonhosted.org/packages/22/d6/fdb3d0670819f2228f3f7d9af613d5e652c15d170c83e5f1c94fbc55a25b/multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e", size = 47812, upload-time = "2025-06-30T15:53:09.263Z" },
{ url = "https://files.pythonhosted.org/packages/b6/d6/a9d2c808f2c489ad199723197419207ecbfbc1776f6e155e1ecea9c883aa/multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d", size = 53011, upload-time = "2025-06-30T15:53:11.038Z" },
{ url = "https://files.pythonhosted.org/packages/f2/40/b68001cba8188dd267590a111f9661b6256debc327137667e832bf5d66e8/multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb", size = 45254, upload-time = "2025-06-30T15:53:12.421Z" },
{ url = "https://files.pythonhosted.org/packages/d2/64/ba29bd6dfc895e592b2f20f92378e692ac306cf25dd0be2f8e0a0f898edb/multidict-6.6.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c8161b5a7778d3137ea2ee7ae8a08cce0010de3b00ac671c5ebddeaa17cefd22", size = 76959, upload-time = "2025-06-30T15:53:13.827Z" },
{ url = "https://files.pythonhosted.org/packages/ca/cd/872ae4c134257dacebff59834983c1615d6ec863b6e3d360f3203aad8400/multidict-6.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1328201ee930f069961ae707d59c6627ac92e351ed5b92397cf534d1336ce557", size = 44864, upload-time = "2025-06-30T15:53:15.658Z" },
{ url = "https://files.pythonhosted.org/packages/15/35/d417d8f62f2886784b76df60522d608aba39dfc83dd53b230ca71f2d4c53/multidict-6.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b1db4d2093d6b235de76932febf9d50766cf49a5692277b2c28a501c9637f616", size = 44540, upload-time = "2025-06-30T15:53:17.208Z" },
{ url = "https://files.pythonhosted.org/packages/85/59/25cddf781f12cddb2386baa29744a3fdd160eb705539b48065f0cffd86d5/multidict-6.6.3-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53becb01dd8ebd19d1724bebe369cfa87e4e7f29abbbe5c14c98ce4c383e16cd", size = 224075, upload-time = "2025-06-30T15:53:18.705Z" },
{ url = "https://files.pythonhosted.org/packages/c4/21/4055b6a527954c572498a8068c26bd3b75f2b959080e17e12104b592273c/multidict-6.6.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41bb9d1d4c303886e2d85bade86e59885112a7f4277af5ad47ab919a2251f306", size = 240535, upload-time = "2025-06-30T15:53:20.359Z" },
{ url = "https://files.pythonhosted.org/packages/58/98/17f1f80bdba0b2fef49cf4ba59cebf8a81797f745f547abb5c9a4039df62/multidict-6.6.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:775b464d31dac90f23192af9c291dc9f423101857e33e9ebf0020a10bfcf4144", size = 219361, upload-time = "2025-06-30T15:53:22.371Z" },
{ url = "https://files.pythonhosted.org/packages/f8/0e/a5e595fdd0820069f0c29911d5dc9dc3a75ec755ae733ce59a4e6962ae42/multidict-6.6.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d04d01f0a913202205a598246cf77826fe3baa5a63e9f6ccf1ab0601cf56eca0", size = 251207, upload-time = "2025-06-30T15:53:24.307Z" },
{ url = "https://files.pythonhosted.org/packages/66/9e/0f51e4cffea2daf24c137feabc9ec848ce50f8379c9badcbac00b41ab55e/multidict-6.6.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d25594d3b38a2e6cabfdcafef339f754ca6e81fbbdb6650ad773ea9775af35ab", size = 249749, upload-time = "2025-06-30T15:53:26.056Z" },
{ url = "https://files.pythonhosted.org/packages/49/a0/a7cfc13c9a71ceb8c1c55457820733af9ce01e121139271f7b13e30c29d2/multidict-6.6.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:35712f1748d409e0707b165bf49f9f17f9e28ae85470c41615778f8d4f7d9609", size = 239202, upload-time = "2025-06-30T15:53:28.096Z" },
{ url = "https://files.pythonhosted.org/packages/c7/50/7ae0d1149ac71cab6e20bb7faf2a1868435974994595dadfdb7377f7140f/multidict-6.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1c8082e5814b662de8589d6a06c17e77940d5539080cbab9fe6794b5241b76d9", size = 237269, upload-time = "2025-06-30T15:53:30.124Z" },
{ url = "https://files.pythonhosted.org/packages/b4/ac/2d0bf836c9c63a57360d57b773359043b371115e1c78ff648993bf19abd0/multidict-6.6.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:61af8a4b771f1d4d000b3168c12c3120ccf7284502a94aa58c68a81f5afac090", size = 232961, upload-time = "2025-06-30T15:53:31.766Z" },
{ url = "https://files.pythonhosted.org/packages/85/e1/68a65f069df298615591e70e48bfd379c27d4ecb252117c18bf52eebc237/multidict-6.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:448e4a9afccbf297577f2eaa586f07067441e7b63c8362a3540ba5a38dc0f14a", size = 240863, upload-time = "2025-06-30T15:53:33.488Z" },
{ url = "https://files.pythonhosted.org/packages/ae/ab/702f1baca649f88ea1dc6259fc2aa4509f4ad160ba48c8e61fbdb4a5a365/multidict-6.6.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:233ad16999afc2bbd3e534ad8dbe685ef8ee49a37dbc2cdc9514e57b6d589ced", size = 246800, upload-time = "2025-06-30T15:53:35.21Z" },
{ url = "https://files.pythonhosted.org/packages/5e/0b/726e690bfbf887985a8710ef2f25f1d6dd184a35bd3b36429814f810a2fc/multidict-6.6.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:bb933c891cd4da6bdcc9733d048e994e22e1883287ff7540c2a0f3b117605092", size = 242034, upload-time = "2025-06-30T15:53:36.913Z" },
{ url = "https://files.pythonhosted.org/packages/73/bb/839486b27bcbcc2e0d875fb9d4012b4b6aa99639137343106aa7210e047a/multidict-6.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:37b09ca60998e87734699e88c2363abfd457ed18cfbf88e4009a4e83788e63ed", size = 235377, upload-time = "2025-06-30T15:53:38.618Z" },
{ url = "https://files.pythonhosted.org/packages/e3/46/574d75ab7b9ae8690fe27e89f5fcd0121633112b438edfb9ed2be8be096b/multidict-6.6.3-cp39-cp39-win32.whl", hash = "sha256:f54cb79d26d0cd420637d184af38f0668558f3c4bbe22ab7ad830e67249f2e0b", size = 41420, upload-time = "2025-06-30T15:53:40.309Z" },
{ url = "https://files.pythonhosted.org/packages/78/c3/8b3bc755508b777868349f4bfa844d3d31832f075ee800a3d6f1807338c5/multidict-6.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:295adc9c0551e5d5214b45cf29ca23dbc28c2d197a9c30d51aed9e037cb7c578", size = 46124, upload-time = "2025-06-30T15:53:41.984Z" },
{ url = "https://files.pythonhosted.org/packages/b2/30/5a66e7e4550e80975faee5b5dd9e9bd09194d2fd8f62363119b9e46e204b/multidict-6.6.3-cp39-cp39-win_arm64.whl", hash = "sha256:15332783596f227db50fb261c2c251a58ac3873c457f3a550a95d5c0aa3c770d", size = 42973, upload-time = "2025-06-30T15:53:43.505Z" },
{ url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313, upload-time = "2025-06-30T15:53:45.437Z" },
]
[[package]]
name = "nonebot-plugin-docs"
version = "2.0.0"
source = { editable = "." }
dependencies = [
{ name = "nonebot2" },
]
[package.metadata]
requires-dist = [{ name = "nonebot2", specifier = ">=2.0.0,<3" }]
[package.metadata.requires-dev]
dev = []
[[package]]
name = "nonebot2"
version = "2.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "exceptiongroup" },
{ name = "loguru" },
{ name = "pydantic" },
{ name = "pygtrie" },
{ name = "python-dotenv" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
{ name = "yarl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1c/06/754d4aea53d1a6622956c9d9a261760b15df03f95d7540666b85ba48d653/nonebot2-2.4.2.tar.gz", hash = "sha256:cf72d5920503ff373ba1d7963f3ddf573db913eb504e3b68ee347efb937db27d", size = 94868, upload-time = "2025-03-12T03:57:45.705Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/26/a8/18b79572cb6178a18bc2da76a041fc6b9f6308c5d376f5c7cbd84f4acf2d/nonebot2-2.4.2-py3-none-any.whl", hash = "sha256:ed3e970cdb6c885fb23349b65a045c08cf3ac7f43e28564ae0c72d3671ecda74", size = 115183, upload-time = "2025-03-12T03:57:43.988Z" },
]
[[package]]
name = "propcache"
version = "0.3.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/14/510deed325e262afeb8b360043c5d7c960da7d3ecd6d6f9496c9c56dc7f4/propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770", size = 73178, upload-time = "2025-06-09T22:53:40.126Z" },
{ url = "https://files.pythonhosted.org/packages/cd/4e/ad52a7925ff01c1325653a730c7ec3175a23f948f08626a534133427dcff/propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3", size = 43133, upload-time = "2025-06-09T22:53:41.965Z" },
{ url = "https://files.pythonhosted.org/packages/63/7c/e9399ba5da7780871db4eac178e9c2e204c23dd3e7d32df202092a1ed400/propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3", size = 43039, upload-time = "2025-06-09T22:53:43.268Z" },
{ url = "https://files.pythonhosted.org/packages/22/e1/58da211eb8fdc6fc854002387d38f415a6ca5f5c67c1315b204a5d3e9d7a/propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e", size = 201903, upload-time = "2025-06-09T22:53:44.872Z" },
{ url = "https://files.pythonhosted.org/packages/c4/0a/550ea0f52aac455cb90111c8bab995208443e46d925e51e2f6ebdf869525/propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220", size = 213362, upload-time = "2025-06-09T22:53:46.707Z" },
{ url = "https://files.pythonhosted.org/packages/5a/af/9893b7d878deda9bb69fcf54600b247fba7317761b7db11fede6e0f28bd0/propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb", size = 210525, upload-time = "2025-06-09T22:53:48.547Z" },
{ url = "https://files.pythonhosted.org/packages/7c/bb/38fd08b278ca85cde36d848091ad2b45954bc5f15cce494bb300b9285831/propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614", size = 198283, upload-time = "2025-06-09T22:53:50.067Z" },
{ url = "https://files.pythonhosted.org/packages/78/8c/9fe55bd01d362bafb413dfe508c48753111a1e269737fa143ba85693592c/propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50", size = 191872, upload-time = "2025-06-09T22:53:51.438Z" },
{ url = "https://files.pythonhosted.org/packages/54/14/4701c33852937a22584e08abb531d654c8bcf7948a8f87ad0a4822394147/propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339", size = 199452, upload-time = "2025-06-09T22:53:53.229Z" },
{ url = "https://files.pythonhosted.org/packages/16/44/447f2253d859602095356007657ee535e0093215ea0b3d1d6a41d16e5201/propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0", size = 191567, upload-time = "2025-06-09T22:53:54.541Z" },
{ url = "https://files.pythonhosted.org/packages/f2/b3/e4756258749bb2d3b46defcff606a2f47410bab82be5824a67e84015b267/propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2", size = 193015, upload-time = "2025-06-09T22:53:56.44Z" },
{ url = "https://files.pythonhosted.org/packages/1e/df/e6d3c7574233164b6330b9fd697beeac402afd367280e6dc377bb99b43d9/propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7", size = 204660, upload-time = "2025-06-09T22:53:57.839Z" },
{ url = "https://files.pythonhosted.org/packages/b2/53/e4d31dd5170b4a0e2e6b730f2385a96410633b4833dc25fe5dffd1f73294/propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b", size = 206105, upload-time = "2025-06-09T22:53:59.638Z" },
{ url = "https://files.pythonhosted.org/packages/7f/fe/74d54cf9fbe2a20ff786e5f7afcfde446588f0cf15fb2daacfbc267b866c/propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c", size = 196980, upload-time = "2025-06-09T22:54:01.071Z" },
{ url = "https://files.pythonhosted.org/packages/22/ec/c469c9d59dada8a7679625e0440b544fe72e99311a4679c279562051f6fc/propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70", size = 37679, upload-time = "2025-06-09T22:54:03.003Z" },
{ url = "https://files.pythonhosted.org/packages/38/35/07a471371ac89d418f8d0b699c75ea6dca2041fbda360823de21f6a9ce0a/propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9", size = 41459, upload-time = "2025-06-09T22:54:04.134Z" },
{ url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" },
{ url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" },
{ url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" },
{ url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" },
{ url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" },
{ url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" },
{ url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" },
{ url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" },
{ url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" },
{ url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" },
{ url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" },
{ url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" },
{ url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" },
{ url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" },
{ url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" },
{ url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" },
{ url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" },
{ url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" },
{ url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" },
{ url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" },
{ url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" },
{ url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" },
{ url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" },
{ url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" },
{ url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" },
{ url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" },
{ url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" },
{ url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" },
{ url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" },
{ url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" },
{ url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" },
{ url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" },
{ url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" },
{ url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" },
{ url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" },
{ url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" },
{ url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" },
{ url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" },
{ url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" },
{ url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" },
{ url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" },
{ url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" },
{ url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" },
{ url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" },
{ url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" },
{ url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" },
{ url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" },
{ url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" },
{ url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" },
{ url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" },
{ url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" },
{ url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" },
{ url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" },
{ url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" },
{ url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" },
{ url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" },
{ url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" },
{ url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" },
{ url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" },
{ url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" },
{ url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" },
{ url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" },
{ url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" },
{ url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" },
{ url = "https://files.pythonhosted.org/packages/6c/39/8ea9bcfaaff16fd0b0fc901ee522e24c9ec44b4ca0229cfffb8066a06959/propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5", size = 74678, upload-time = "2025-06-09T22:55:41.227Z" },
{ url = "https://files.pythonhosted.org/packages/d3/85/cab84c86966e1d354cf90cdc4ba52f32f99a5bca92a1529d666d957d7686/propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4", size = 43829, upload-time = "2025-06-09T22:55:42.417Z" },
{ url = "https://files.pythonhosted.org/packages/23/f7/9cb719749152d8b26d63801b3220ce2d3931312b2744d2b3a088b0ee9947/propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2", size = 43729, upload-time = "2025-06-09T22:55:43.651Z" },
{ url = "https://files.pythonhosted.org/packages/a2/a2/0b2b5a210ff311260002a315f6f9531b65a36064dfb804655432b2f7d3e3/propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d", size = 204483, upload-time = "2025-06-09T22:55:45.327Z" },
{ url = "https://files.pythonhosted.org/packages/3f/e0/7aff5de0c535f783b0c8be5bdb750c305c1961d69fbb136939926e155d98/propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec", size = 217425, upload-time = "2025-06-09T22:55:46.729Z" },
{ url = "https://files.pythonhosted.org/packages/92/1d/65fa889eb3b2a7d6e4ed3c2b568a9cb8817547a1450b572de7bf24872800/propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701", size = 214723, upload-time = "2025-06-09T22:55:48.342Z" },
{ url = "https://files.pythonhosted.org/packages/9a/e2/eecf6989870988dfd731de408a6fa366e853d361a06c2133b5878ce821ad/propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef", size = 200166, upload-time = "2025-06-09T22:55:49.775Z" },
{ url = "https://files.pythonhosted.org/packages/12/06/c32be4950967f18f77489268488c7cdc78cbfc65a8ba8101b15e526b83dc/propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1", size = 194004, upload-time = "2025-06-09T22:55:51.335Z" },
{ url = "https://files.pythonhosted.org/packages/46/6c/17b521a6b3b7cbe277a4064ff0aa9129dd8c89f425a5a9b6b4dd51cc3ff4/propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886", size = 203075, upload-time = "2025-06-09T22:55:52.681Z" },
{ url = "https://files.pythonhosted.org/packages/62/cb/3bdba2b736b3e45bc0e40f4370f745b3e711d439ffbffe3ae416393eece9/propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b", size = 195407, upload-time = "2025-06-09T22:55:54.048Z" },
{ url = "https://files.pythonhosted.org/packages/29/bd/760c5c6a60a4a2c55a421bc34a25ba3919d49dee411ddb9d1493bb51d46e/propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb", size = 196045, upload-time = "2025-06-09T22:55:55.485Z" },
{ url = "https://files.pythonhosted.org/packages/76/58/ced2757a46f55b8c84358d6ab8de4faf57cba831c51e823654da7144b13a/propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea", size = 208432, upload-time = "2025-06-09T22:55:56.884Z" },
{ url = "https://files.pythonhosted.org/packages/bb/ec/d98ea8d5a4d8fe0e372033f5254eddf3254344c0c5dc6c49ab84349e4733/propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb", size = 210100, upload-time = "2025-06-09T22:55:58.498Z" },
{ url = "https://files.pythonhosted.org/packages/56/84/b6d8a7ecf3f62d7dd09d9d10bbf89fad6837970ef868b35b5ffa0d24d9de/propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe", size = 200712, upload-time = "2025-06-09T22:55:59.906Z" },
{ url = "https://files.pythonhosted.org/packages/bf/32/889f4903ddfe4a9dc61da71ee58b763758cf2d608fe1decede06e6467f8d/propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1", size = 38187, upload-time = "2025-06-09T22:56:01.212Z" },
{ url = "https://files.pythonhosted.org/packages/67/74/d666795fb9ba1dc139d30de64f3b6fd1ff9c9d3d96ccfdb992cd715ce5d2/propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9", size = 42025, upload-time = "2025-06-09T22:56:02.875Z" },
{ url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" },
]
[[package]]
name = "pydantic"
version = "2.11.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" },
]
[[package]]
name = "pydantic-core"
version = "2.33.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" },
{ url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" },
{ url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" },
{ url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" },
{ url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" },
{ url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" },
{ url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" },
{ url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" },
{ url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" },
{ url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" },
{ url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" },
{ url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" },
{ url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" },
{ url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" },
{ url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" },
{ url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" },
{ url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" },
{ url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" },
{ url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" },
{ url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" },
{ url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" },
{ url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" },
{ url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" },
{ url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" },
{ url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" },
{ url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" },
{ url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" },
{ url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" },
{ url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" },
{ url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" },
{ url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" },
{ url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" },
{ url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" },
{ url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" },
{ url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" },
{ url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" },
{ url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" },
{ url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" },
{ url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" },
{ url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" },
{ url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" },
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
{ url = "https://files.pythonhosted.org/packages/53/ea/bbe9095cdd771987d13c82d104a9c8559ae9aec1e29f139e286fd2e9256e/pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d", size = 2028677, upload-time = "2025-04-23T18:32:27.227Z" },
{ url = "https://files.pythonhosted.org/packages/49/1d/4ac5ed228078737d457a609013e8f7edc64adc37b91d619ea965758369e5/pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954", size = 1864735, upload-time = "2025-04-23T18:32:29.019Z" },
{ url = "https://files.pythonhosted.org/packages/23/9a/2e70d6388d7cda488ae38f57bc2f7b03ee442fbcf0d75d848304ac7e405b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb", size = 1898467, upload-time = "2025-04-23T18:32:31.119Z" },
{ url = "https://files.pythonhosted.org/packages/ff/2e/1568934feb43370c1ffb78a77f0baaa5a8b6897513e7a91051af707ffdc4/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7", size = 1983041, upload-time = "2025-04-23T18:32:33.655Z" },
{ url = "https://files.pythonhosted.org/packages/01/1a/1a1118f38ab64eac2f6269eb8c120ab915be30e387bb561e3af904b12499/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4", size = 2136503, upload-time = "2025-04-23T18:32:35.519Z" },
{ url = "https://files.pythonhosted.org/packages/5c/da/44754d1d7ae0f22d6d3ce6c6b1486fc07ac2c524ed8f6eca636e2e1ee49b/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b", size = 2736079, upload-time = "2025-04-23T18:32:37.659Z" },
{ url = "https://files.pythonhosted.org/packages/4d/98/f43cd89172220ec5aa86654967b22d862146bc4d736b1350b4c41e7c9c03/pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3", size = 2006508, upload-time = "2025-04-23T18:32:39.637Z" },
{ url = "https://files.pythonhosted.org/packages/2b/cc/f77e8e242171d2158309f830f7d5d07e0531b756106f36bc18712dc439df/pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a", size = 2113693, upload-time = "2025-04-23T18:32:41.818Z" },
{ url = "https://files.pythonhosted.org/packages/54/7a/7be6a7bd43e0a47c147ba7fbf124fe8aaf1200bc587da925509641113b2d/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782", size = 2074224, upload-time = "2025-04-23T18:32:44.033Z" },
{ url = "https://files.pythonhosted.org/packages/2a/07/31cf8fadffbb03be1cb520850e00a8490c0927ec456e8293cafda0726184/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9", size = 2245403, upload-time = "2025-04-23T18:32:45.836Z" },
{ url = "https://files.pythonhosted.org/packages/b6/8d/bbaf4c6721b668d44f01861f297eb01c9b35f612f6b8e14173cb204e6240/pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e", size = 2242331, upload-time = "2025-04-23T18:32:47.618Z" },
{ url = "https://files.pythonhosted.org/packages/bb/93/3cc157026bca8f5006250e74515119fcaa6d6858aceee8f67ab6dc548c16/pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9", size = 1910571, upload-time = "2025-04-23T18:32:49.401Z" },
{ url = "https://files.pythonhosted.org/packages/5b/90/7edc3b2a0d9f0dda8806c04e511a67b0b7a41d2187e2003673a996fb4310/pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3", size = 1956504, upload-time = "2025-04-23T18:32:51.287Z" },
{ url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" },
{ url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" },
{ url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" },
{ url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" },
{ url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" },
{ url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" },
{ url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" },
{ url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" },
{ url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" },
{ url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" },
{ url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" },
{ url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" },
{ url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" },
{ url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" },
{ url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" },
{ url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" },
{ url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" },
{ url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" },
{ url = "https://files.pythonhosted.org/packages/08/98/dbf3fdfabaf81cda5622154fda78ea9965ac467e3239078e0dcd6df159e7/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101", size = 2024034, upload-time = "2025-04-23T18:33:32.843Z" },
{ url = "https://files.pythonhosted.org/packages/8d/99/7810aa9256e7f2ccd492590f86b79d370df1e9292f1f80b000b6a75bd2fb/pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64", size = 1858578, upload-time = "2025-04-23T18:33:34.912Z" },
{ url = "https://files.pythonhosted.org/packages/d8/60/bc06fa9027c7006cc6dd21e48dbf39076dc39d9abbaf718a1604973a9670/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d", size = 1892858, upload-time = "2025-04-23T18:33:36.933Z" },
{ url = "https://files.pythonhosted.org/packages/f2/40/9d03997d9518816c68b4dfccb88969756b9146031b61cd37f781c74c9b6a/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535", size = 2068498, upload-time = "2025-04-23T18:33:38.997Z" },
{ url = "https://files.pythonhosted.org/packages/d8/62/d490198d05d2d86672dc269f52579cad7261ced64c2df213d5c16e0aecb1/pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d", size = 2108428, upload-time = "2025-04-23T18:33:41.18Z" },
{ url = "https://files.pythonhosted.org/packages/9a/ec/4cd215534fd10b8549015f12ea650a1a973da20ce46430b68fc3185573e8/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6", size = 2069854, upload-time = "2025-04-23T18:33:43.446Z" },
{ url = "https://files.pythonhosted.org/packages/1a/1a/abbd63d47e1d9b0d632fee6bb15785d0889c8a6e0a6c3b5a8e28ac1ec5d2/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca", size = 2237859, upload-time = "2025-04-23T18:33:45.56Z" },
{ url = "https://files.pythonhosted.org/packages/80/1c/fa883643429908b1c90598fd2642af8839efd1d835b65af1f75fba4d94fe/pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039", size = 2239059, upload-time = "2025-04-23T18:33:47.735Z" },
{ url = "https://files.pythonhosted.org/packages/d4/29/3cade8a924a61f60ccfa10842f75eb12787e1440e2b8660ceffeb26685e7/pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27", size = 2066661, upload-time = "2025-04-23T18:33:49.995Z" },
]
[[package]]
name = "pygtrie"
version = "2.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b9/13/55deec25bf09383216fa7f1dfcdbfca40a04aa00b6d15a5cbf25af8fce5f/pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2", size = 39266, upload-time = "2022-07-16T14:29:47.459Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/cd/bd196b2cf014afb1009de8b0f05ecd54011d881944e62763f3c1b1e8ef37/pygtrie-2.5.0-py3-none-any.whl", hash = "sha256:8795cda8105493d5ae159a5bef313ff13156c5d4d72feddefacaad59f8c8ce16", size = 25099, upload-time = "2022-09-23T20:30:05.12Z" },
]
[[package]]
name = "python-dotenv"
version = "1.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "tomli"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
]
[[package]]
name = "typing-extensions"
version = "4.14.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
]
[[package]]
name = "win32-setctime"
version = "1.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" },
]
[[package]]
name = "yarl"
version = "1.20.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "multidict" },
{ name = "propcache" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/65/7fed0d774abf47487c64be14e9223749468922817b5e8792b8a64792a1bb/yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4", size = 132910, upload-time = "2025-06-10T00:42:31.108Z" },
{ url = "https://files.pythonhosted.org/packages/8a/7b/988f55a52da99df9e56dc733b8e4e5a6ae2090081dc2754fc8fd34e60aa0/yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a", size = 90644, upload-time = "2025-06-10T00:42:33.851Z" },
{ url = "https://files.pythonhosted.org/packages/f7/de/30d98f03e95d30c7e3cc093759982d038c8833ec2451001d45ef4854edc1/yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed", size = 89322, upload-time = "2025-06-10T00:42:35.688Z" },
{ url = "https://files.pythonhosted.org/packages/e0/7a/f2f314f5ebfe9200724b0b748de2186b927acb334cf964fd312eb86fc286/yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e", size = 323786, upload-time = "2025-06-10T00:42:37.817Z" },
{ url = "https://files.pythonhosted.org/packages/15/3f/718d26f189db96d993d14b984ce91de52e76309d0fd1d4296f34039856aa/yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73", size = 319627, upload-time = "2025-06-10T00:42:39.937Z" },
{ url = "https://files.pythonhosted.org/packages/a5/76/8fcfbf5fa2369157b9898962a4a7d96764b287b085b5b3d9ffae69cdefd1/yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e", size = 339149, upload-time = "2025-06-10T00:42:42.627Z" },
{ url = "https://files.pythonhosted.org/packages/3c/95/d7fc301cc4661785967acc04f54a4a42d5124905e27db27bb578aac49b5c/yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8", size = 333327, upload-time = "2025-06-10T00:42:44.842Z" },
{ url = "https://files.pythonhosted.org/packages/65/94/e21269718349582eee81efc5c1c08ee71c816bfc1585b77d0ec3f58089eb/yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23", size = 326054, upload-time = "2025-06-10T00:42:47.149Z" },
{ url = "https://files.pythonhosted.org/packages/32/ae/8616d1f07853704523519f6131d21f092e567c5af93de7e3e94b38d7f065/yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70", size = 315035, upload-time = "2025-06-10T00:42:48.852Z" },
{ url = "https://files.pythonhosted.org/packages/48/aa/0ace06280861ef055855333707db5e49c6e3a08840a7ce62682259d0a6c0/yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb", size = 338962, upload-time = "2025-06-10T00:42:51.024Z" },
{ url = "https://files.pythonhosted.org/packages/20/52/1e9d0e6916f45a8fb50e6844f01cb34692455f1acd548606cbda8134cd1e/yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2", size = 335399, upload-time = "2025-06-10T00:42:53.007Z" },
{ url = "https://files.pythonhosted.org/packages/f2/65/60452df742952c630e82f394cd409de10610481d9043aa14c61bf846b7b1/yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30", size = 338649, upload-time = "2025-06-10T00:42:54.964Z" },
{ url = "https://files.pythonhosted.org/packages/7b/f5/6cd4ff38dcde57a70f23719a838665ee17079640c77087404c3d34da6727/yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309", size = 358563, upload-time = "2025-06-10T00:42:57.28Z" },
{ url = "https://files.pythonhosted.org/packages/d1/90/c42eefd79d0d8222cb3227bdd51b640c0c1d0aa33fe4cc86c36eccba77d3/yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24", size = 357609, upload-time = "2025-06-10T00:42:59.055Z" },
{ url = "https://files.pythonhosted.org/packages/03/c8/cea6b232cb4617514232e0f8a718153a95b5d82b5290711b201545825532/yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13", size = 350224, upload-time = "2025-06-10T00:43:01.248Z" },
{ url = "https://files.pythonhosted.org/packages/ce/a3/eaa0ab9712f1f3d01faf43cf6f1f7210ce4ea4a7e9b28b489a2261ca8db9/yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8", size = 81753, upload-time = "2025-06-10T00:43:03.486Z" },
{ url = "https://files.pythonhosted.org/packages/8f/34/e4abde70a9256465fe31c88ed02c3f8502b7b5dead693a4f350a06413f28/yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16", size = 86817, upload-time = "2025-06-10T00:43:05.231Z" },
{ url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" },
{ url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" },
{ url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" },
{ url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" },
{ url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" },
{ url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" },
{ url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" },
{ url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" },
{ url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" },
{ url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" },
{ url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" },
{ url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" },
{ url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" },
{ url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" },
{ url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" },
{ url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" },
{ url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" },
{ url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" },
{ url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" },
{ url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" },
{ url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" },
{ url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" },
{ url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" },
{ url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" },
{ url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" },
{ url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" },
{ url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" },
{ url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" },
{ url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" },
{ url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" },
{ url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" },
{ url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" },
{ url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" },
{ url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" },
{ url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" },
{ url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" },
{ url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" },
{ url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" },
{ url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" },
{ url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" },
{ url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" },
{ url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" },
{ url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" },
{ url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" },
{ url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" },
{ url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" },
{ url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" },
{ url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" },
{ url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" },
{ url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" },
{ url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" },
{ url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" },
{ url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" },
{ url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" },
{ url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" },
{ url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" },
{ url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" },
{ url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" },
{ url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" },
{ url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" },
{ url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" },
{ url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" },
{ url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" },
{ url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" },
{ url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" },
{ url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" },
{ url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" },
{ url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" },
{ url = "https://files.pythonhosted.org/packages/01/75/0d37402d208d025afa6b5b8eb80e466d267d3fd1927db8e317d29a94a4cb/yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3", size = 134259, upload-time = "2025-06-10T00:45:29.882Z" },
{ url = "https://files.pythonhosted.org/packages/73/84/1fb6c85ae0cf9901046f07d0ac9eb162f7ce6d95db541130aa542ed377e6/yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b", size = 91269, upload-time = "2025-06-10T00:45:32.917Z" },
{ url = "https://files.pythonhosted.org/packages/f3/9c/eae746b24c4ea29a5accba9a06c197a70fa38a49c7df244e0d3951108861/yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983", size = 89995, upload-time = "2025-06-10T00:45:35.066Z" },
{ url = "https://files.pythonhosted.org/packages/fb/30/693e71003ec4bc1daf2e4cf7c478c417d0985e0a8e8f00b2230d517876fc/yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805", size = 325253, upload-time = "2025-06-10T00:45:37.052Z" },
{ url = "https://files.pythonhosted.org/packages/0f/a2/5264dbebf90763139aeb0b0b3154763239398400f754ae19a0518b654117/yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba", size = 320897, upload-time = "2025-06-10T00:45:39.962Z" },
{ url = "https://files.pythonhosted.org/packages/e7/17/77c7a89b3c05856489777e922f41db79ab4faf58621886df40d812c7facd/yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e", size = 340696, upload-time = "2025-06-10T00:45:41.915Z" },
{ url = "https://files.pythonhosted.org/packages/6d/55/28409330b8ef5f2f681f5b478150496ec9cf3309b149dab7ec8ab5cfa3f0/yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723", size = 335064, upload-time = "2025-06-10T00:45:43.893Z" },
{ url = "https://files.pythonhosted.org/packages/85/58/cb0257cbd4002828ff735f44d3c5b6966c4fd1fc8cc1cd3cd8a143fbc513/yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000", size = 327256, upload-time = "2025-06-10T00:45:46.393Z" },
{ url = "https://files.pythonhosted.org/packages/53/f6/c77960370cfa46f6fb3d6a5a79a49d3abfdb9ef92556badc2dcd2748bc2a/yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5", size = 316389, upload-time = "2025-06-10T00:45:48.358Z" },
{ url = "https://files.pythonhosted.org/packages/64/ab/be0b10b8e029553c10905b6b00c64ecad3ebc8ace44b02293a62579343f6/yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c", size = 340481, upload-time = "2025-06-10T00:45:50.663Z" },
{ url = "https://files.pythonhosted.org/packages/c5/c3/3f327bd3905a4916029bf5feb7f86dcf864c7704f099715f62155fb386b2/yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240", size = 336941, upload-time = "2025-06-10T00:45:52.554Z" },
{ url = "https://files.pythonhosted.org/packages/d1/42/040bdd5d3b3bb02b4a6ace4ed4075e02f85df964d6e6cb321795d2a6496a/yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee", size = 339936, upload-time = "2025-06-10T00:45:54.919Z" },
{ url = "https://files.pythonhosted.org/packages/0d/1c/911867b8e8c7463b84dfdc275e0d99b04b66ad5132b503f184fe76be8ea4/yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010", size = 360163, upload-time = "2025-06-10T00:45:56.87Z" },
{ url = "https://files.pythonhosted.org/packages/e2/31/8c389f6c6ca0379b57b2da87f1f126c834777b4931c5ee8427dd65d0ff6b/yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8", size = 359108, upload-time = "2025-06-10T00:45:58.869Z" },
{ url = "https://files.pythonhosted.org/packages/7f/09/ae4a649fb3964324c70a3e2b61f45e566d9ffc0affd2b974cbf628957673/yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d", size = 351875, upload-time = "2025-06-10T00:46:01.45Z" },
{ url = "https://files.pythonhosted.org/packages/8d/43/bbb4ed4c34d5bb62b48bf957f68cd43f736f79059d4f85225ab1ef80f4b9/yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06", size = 82293, upload-time = "2025-06-10T00:46:03.763Z" },
{ url = "https://files.pythonhosted.org/packages/d7/cd/ce185848a7dba68ea69e932674b5c1a42a1852123584bccc5443120f857c/yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00", size = 87385, upload-time = "2025-06-10T00:46:05.655Z" },
{ url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" },
]
Generated
+2831
View File
File diff suppressed because it is too large Load Diff
+70 -81
View File
@@ -1,10 +1,13 @@
[project]
[tool.poetry]
name = "nonebot2"
version = "2.5.0"
version = "2.3.2"
description = "An asynchronous python bot framework."
authors = [{ name = "yanyongyu", email = "yyy@nonebot.dev" }]
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
readme = "README.md"
homepage = "https://nonebot.dev/"
repository = "https://github.com/nonebot/nonebot2"
documentation = "https://nonebot.dev/"
keywords = ["bot", "qq", "qqbot", "mirai", "coolq"]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -13,86 +16,84 @@ classifiers = [
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
]
requires-python = ">=3.10, <4.0"
dependencies = [
"yarl >=1.7.2, <2.0.0",
"anyio >=4.4.0, <5.0.0",
"loguru >=0.6.0, <1.0.0",
"pygtrie >=2.4.1, <3.0.0",
"exceptiongroup >=1.2.2, <2.0.0",
"python-dotenv >=0.21.0, <2.0.0",
"typing-extensions >=4.6.0, <5.0.0",
"tomli >=2.0.1, <3.0.0; python_version < '3.11'",
"pydantic >=1.10.0, <3.0.0, !=2.5.0, !=2.5.1, !=2.10.0, !=2.10.1",
]
packages = [{ include = "nonebot" }]
include = ["nonebot/py.typed"]
[project.optional-dependencies]
websockets = ["websockets >=15.0"]
httpx = ["httpx[http2] >=0.26.0, <1.0.0"]
aiohttp = ["aiohttp[speedups] >=3.11.0, <4.0.0"]
quart = ["Quart >=0.18.0, <1.0.0", "uvicorn[standard] >=0.20.0, <1.0.0"]
fastapi = ["fastapi >=0.93.0, <1.0.0", "uvicorn[standard] >=0.20.0, <1.0.0"]
all = [
"websockets >=15.0",
"fastapi >=0.93.0, <1.0.0",
"httpx[http2] >=0.26.0, <1.0.0",
"aiohttp[speedups] >=3.11.0, <4.0.0",
"uvicorn[standard] >=0.20.0, <1.0.0",
]
[dependency-groups]
dev = [
{ include-group = "test" },
{ include-group = "docs" },
"ruff >=0.15.0, <0.16.0",
"nonemoji >=0.1.2, <0.2.0",
"pre-commit >=4.0.0, <5.0.0",
]
test = [
"trio >=0.27.0",
"nonebug >=0.4.1, <0.5.0",
"wsproto >=1.2.0, <2.0.0",
"werkzeug >=2.3.6, <4.0.0",
"pytest-cov >=7.0.0, <8.0.0",
"pytest-xdist >=3.0.2, <4.0.0",
"coverage-conditional-plugin >=0.9.0, <0.10.0",
]
docs = ["nb-autodoc >=1.0.4, <2.0.0"]
pydantic-v1 = ["pydantic >=1.10.0, <2.0.0"]
pydantic-v2 = ["pydantic >=2.0.0, <3.0.0"]
[project.urls]
Homepage = "https://nonebot.dev/"
Repository = "https://github.com/nonebot/nonebot2"
Documentation = "https://nonebot.dev/"
[tool.poetry.urls]
"Bug Tracker" = "https://github.com/nonebot/nonebot2/issues"
Changelog = "https://nonebot.dev/changelog"
Funding = "https://afdian.com/@nonebot"
"Changelog" = "https://nonebot.dev/changelog"
"Funding" = "https://afdian.net/@nonebot"
[tool.uv]
required-version = ">=0.8.0"
conflicts = [[{ group = "pydantic-v1" }, { group = "pydantic-v2" }]]
[tool.poetry.dependencies]
python = "^3.9"
yarl = "^1.7.2"
pygtrie = "^2.4.1"
loguru = ">=0.6.0,<1.0.0"
python-dotenv = ">=0.21.0,<2.0.0"
typing-extensions = ">=4.4.0,<5.0.0"
pydantic = ">=1.10.0,<3.0.0,!=2.5.0,!=2.5.1"
tomli = { version = "^2.0.1", python = "<3.11" }
[tool.uv.build-backend]
module-name = "nonebot"
module-root = ""
websockets = { version = ">=10.0", optional = true }
Quart = { version = ">=0.18.0,<1.0.0", optional = true }
fastapi = { version = ">=0.93.0,<1.0.0", optional = true }
aiohttp = { version = "^3.9.0b0", extras = ["speedups"], optional = true }
httpx = { version = ">=0.20.0,<1.0.0", extras = ["http2"], optional = true }
uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
"standard",
], optional = true }
[tool.poetry.group.dev.dependencies]
ruff = "^0.4.0"
isort = "^5.10.1"
black = "^24.0.0"
nonemoji = "^0.1.2"
pre-commit = "^3.0.0"
[tool.poetry.group.test.dependencies]
nonebot-test = { path = "./envs/test/", develop = false }
[tool.poetry.group.docs.dependencies]
nb-autodoc = "^1.0.0a5"
[tool.poetry.extras]
httpx = ["httpx"]
aiohttp = ["aiohttp"]
websockets = ["websockets"]
quart = ["quart", "uvicorn"]
fastapi = ["fastapi", "uvicorn"]
all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
[tool.pytest.ini_options]
asyncio_mode = "strict"
addopts = "--cov=nonebot --cov-report=term-missing"
filterwarnings = ["error", "ignore::DeprecationWarning"]
[tool.black]
line-length = 88
target-version = ["py39", "py310", "py311", "py312"]
include = '\.pyi?$'
extend-exclude = '''
'''
[tool.isort]
profile = "black"
line_length = 88
length_sort = true
skip_gitignore = true
force_sort_within_sections = true
src_paths = ["nonebot", "tests"]
extra_standard_library = ["typing_extensions"]
[tool.ruff]
line-length = 88
[tool.ruff.format]
line-ending = "lf"
target-version = "py39"
[tool.ruff.lint]
select = [
"F", # Pyflakes
"W", # pycodestyle warnings
"E", # pycodestyle errors
"I", # isort
"UP", # pyupgrade
"ASYNC", # flake8-async
"C4", # flake8-comprehensions
@@ -101,7 +102,6 @@ select = [
"PYI", # flake8-pyi
"PT", # flake8-pytest-style
"Q", # flake8-quotes
"TID", # flake8-tidy-imports
"RUF", # Ruff-specific rules
]
ignore = [
@@ -112,26 +112,15 @@ ignore = [
"RUF003", # ambiguous-unicode-character-comment
]
[tool.ruff.lint.isort]
force-sort-within-sections = true
known-first-party = ["nonebot", "tests/*"]
extra-standard-library = ["typing_extensions"]
[tool.ruff.lint.flake8-pytest-style]
fixture-parentheses = false
mark-parentheses = false
[tool.ruff.lint.pyupgrade]
keep-runtime-typing = true
[tool.pyright]
pythonVersion = "3.10"
pythonVersion = "3.9"
pythonPlatform = "All"
defineConstant = { PYDANTIC_V2 = true }
executionEnvironments = [
{ root = "./tests/python_3_12", pythonVersion = "3.12", extraPaths = [
"./",
] },
{ root = "./tests", extraPaths = [
"./",
] },
@@ -143,5 +132,5 @@ reportShadowedImports = false
disableBytesTypePromotions = true
[build-system]
requires = ["uv_build >=0.8.3, <0.10.0"]
build-backend = "uv_build"
requires = ["poetry_core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
+2 -4
View File
@@ -1,11 +1,9 @@
#!/usr/bin/env bash
set -e
#! /usr/bin/env bash
# cd to the root of the project
cd "$(dirname "$0")/.."
nb-autodoc nonebot \
poetry run nb-autodoc nonebot \
-s nonebot.plugins \
-u nonebot.internal \
-u nonebot.internal.*
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
#! /usr/bin/env bash
# cd to the root of the tests
cd "$(dirname "$0")/../tests"
# Run the tests
pytest -n auto --cov-append --cov-report xml --junitxml=./junit.xml $@
pytest -n auto --cov-append --cov-report xml $@
+12 -2
View File
@@ -1,4 +1,14 @@
#!/usr/bin/env bash
#! /usr/bin/env bash
# config poetry to install env in project
poetry config virtualenvs.in-project true
# setup dev environment
echo "Setting up dev environment"
uv sync --all-extras && uv run pre-commit install && yarn install
poetry install --all-extras && poetry run pre-commit install && yarn install
# setup pydantic v2 test environment
for env in $(find ./envs/ -maxdepth 1 -mindepth 1 -type d -not -name test); do
echo "Setting up $env environment"
(cd $env && poetry install --no-root)
done
+15
View File
@@ -0,0 +1,15 @@
#! /usr/bin/env bash
# update test env
echo "Updating test env..."
(cd ./envs/test/ && poetry update --lock)
# update dev env
echo "Updating dev env..."
poetry update
# update other envs
for env in $(find ./envs/ -maxdepth 1 -mindepth 1 -type d -not -name test); do
echo "Updating $env env..."
(cd $env && poetry update)
done
-2
View File
@@ -1,5 +1,4 @@
SIMPLE=simple
int_str=123
COMPLEX='
[1, 2, 3]
'
@@ -11,7 +10,6 @@ NESTED__C__C=3
NESTED__COMPLEX=[1, 2, 3]
NESTED_INNER__A=1
NESTED_INNER__B=2
ALIAS_SIMPLE=aliased_simple
OTHER_SIMPLE=simple
OTHER_NESTED={"a": 1}
OTHER_NESTED__B=2
+9 -45
View File
@@ -1,22 +1,18 @@
from collections.abc import Callable, Generator
from functools import wraps
import os
from pathlib import Path
import sys
import threading
from types import EllipsisType
from typing import TYPE_CHECKING, TypeVar
from typing_extensions import ParamSpec
from pathlib import Path
from typing import TYPE_CHECKING
from collections.abc import Generator
from nonebug import NONEBOT_INIT_KWARGS
import pytest
from nonebug import NONEBOT_INIT_KWARGS
from werkzeug.serving import BaseWSGIServer, make_server
from fake_server import request_handler
import nonebot
from nonebot import _resolve_combine_expr
from nonebot.config import Env
from fake_server import request_handler
from nonebot.drivers import URL, Driver
from nonebot import _resolve_combine_expr
os.environ["CONFIG_FROM_ENV"] = '{"test": "test"}'
os.environ["CONFIG_OVERRIDE"] = "new"
@@ -24,9 +20,6 @@ os.environ["CONFIG_OVERRIDE"] = "new"
if TYPE_CHECKING:
from nonebot.plugin import Plugin
P = ParamSpec("P")
R = TypeVar("R")
collect_ignore = ["plugins/", "dynamic/", "bad_plugins/"]
@@ -45,43 +38,14 @@ def load_driver(request: pytest.FixtureRequest) -> Driver:
return DriverClass(Env(environment=global_driver.env), global_driver.config)
@pytest.fixture(scope="session", params=[pytest.param("asyncio"), pytest.param("trio")])
def anyio_backend(request: pytest.FixtureRequest):
return request.param
def run_once(func: Callable[P, R]) -> Callable[P, R]:
result: R | EllipsisType = ...
@wraps(func)
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
nonlocal result
if result is not ...:
return result
result = func(*args, **kwargs)
return result
return _wrapper
@pytest.fixture(scope="session", autouse=True)
@run_once
def load_plugin(anyio_backend, nonebug_init: None) -> set["Plugin"]:
def load_plugin(nonebug_init: None) -> set["Plugin"]:
# preload global plugins
plugins: set["Plugin"] = set()
plugins |= nonebot.load_plugins(str(Path(__file__).parent / "plugins"))
if sys.version_info >= (3, 12):
# preload python 3.12 plugins
plugins |= nonebot.load_plugins(
str(Path(__file__).parent / "python_3_12" / "plugins")
)
return plugins
return nonebot.load_plugins(str(Path(__file__).parent / "plugins"))
@pytest.fixture(scope="session", autouse=True)
@run_once
def load_builtin_plugin(anyio_backend, 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")
+7 -12
View File
@@ -1,20 +1,15 @@
import base64
import json
import base64
import socket
from typing import TypeVar
from typing import Union, TypeVar
from wsproto.events import Ping
from werkzeug import Request, Response
from werkzeug.datastructures import MultiDict
from wsproto import ConnectionType, WSConnection
from wsproto.events import (
AcceptConnection,
BytesMessage,
CloseConnection,
Ping,
TextMessage,
)
from wsproto.events import Request as WSRequest
from wsproto.frame_protocol import CloseReason
from wsproto.events import Request as WSRequest
from wsproto import WSConnection, ConnectionType
from wsproto.events import TextMessage, BytesMessage, CloseConnection, AcceptConnection
K = TypeVar("K")
V = TypeVar("V")
@@ -36,7 +31,7 @@ def json_safe(string, content_type="application/octet-stream") -> str:
).decode("utf-8")
def flattern(d: "MultiDict[K, V]") -> dict[K, 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()}
-3
View File
@@ -1,3 +0,0 @@
[tool.nonebot]
plugins = []
plugin_dirs = []
+1 -3
View File
@@ -1,5 +1,3 @@
[tool.nonebot]
plugins = []
plugin_dirs = []
[tool.nonebot.plugins]
"@local" = []

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